From 5b2d5078767b2b0ed570b434693debca1f317883 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 20 Mar 2023 10:12:09 +0700 Subject: [PATCH 001/386] chore(web): script text alignment --- web/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/build.sh b/web/build.sh index f160f6b161..43341ad60d 100755 --- a/web/build.sh +++ b/web/build.sh @@ -34,7 +34,7 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ ":engine/device-detect Subset used for device-detection " \ ":engine/dom-utils A common subset of function used for DOM calculations, layout, etc" \ ":engine/element-wrappers Subset used to integrate with website elements" \ - ":engine/package-cache Subset used to collate keyboards and request them from the cloud" \ + ":engine/package-cache Subset used to collate keyboards and request them from the cloud" \ ":engine/main Builds all common code used by KMW's app/-level targets" \ ":engine/osk Builds the Web OSK module" -- GitLab From a917eec57bb2b7de9b0ad994bb349a76086d6757 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 21 Mar 2023 12:20:18 +0700 Subject: [PATCH 002/386] change(web): setActiveKeyboardAsync --- web/src/app/browser/src/contextManager.ts | 52 +++++++++++++++- web/src/app/webview/src/contextManager.ts | 33 +++++++++- web/src/engine/main/src/contextManagerBase.ts | 62 ++++++++++++++++++- 3 files changed, 143 insertions(+), 4 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 8ef1bf5ca8..cc84328c20 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -1,7 +1,7 @@ import { type Keyboard, Mock, OutputTarget } from '@keymanapp/keyboard-processor'; import { type KeyboardStub } from 'keyman/engine/package-cache'; import { - ContextManager as ContextManagerBase, + ContextManagerBase, type KeyboardInterface } from 'keyman/engine/main'; import { BrowserConfiguration } from './configuration.js'; @@ -58,4 +58,54 @@ export default class ContextManager extends ContextManagerBase { } return false; } + + /** + * Reflects the active 'target' upon which any `set activeKeyboard` operation will take place. + * When `null`, such operations will affect the global default; otherwise, such operations + * affect only the specified `target`. + */ + private get keyboardTarget(): OutputTarget { + // Remove `&& false` once the inlined section below is implemented. + if(this.activeTarget /* has 'independent keyboard mode activated' */ && false) { + return this.activeTarget; + } else { + return null; + } + } + + async setTargetActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub, target: OutputTarget): Promise { + if(!await this.deferredKeyboardActivationValid(kbd, metadata, target)) { + return false; + } else { + let activatingKeyboard = { + keyboard: await kbd, + metadata: metadata + }; + + if(target == this.keyboardTarget) { + // TODO: 'beforekeyboardchange' event + } + + // TODO: set THAT TARGET's active keyboard. May or may not be active! + + if(target == this.keyboardTarget) { + // TODO: 'keyboardchange' event + } + + /* + * Alternative to the three above TODOs: + * if (same condition met) + * this.activeKeyboard = activatingKeyboard + * else + * // manually set it for that one control; no events. + * + */ + + return true; + } + } + + async setActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub): Promise { + return this.setTargetActiveKeyboardAsync(kbd, metadata, this.keyboardTarget); + } } \ No newline at end of file diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 47e465ba42..6d4e20a3d9 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,5 +1,5 @@ import { type Keyboard, Mock } from '@keymanapp/keyboard-processor'; -import { type KeyboardStub } from 'keyman/engine/package-cache'; +import { KeyboardStub } from 'keyman/engine/package-cache'; import { ContextManagerBase, ContextManagerConfiguration } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; @@ -53,11 +53,40 @@ export default class ContextManager extends ContextManagerBase { set activeKeyboard(kbd: {keyboard: Keyboard, metadata: KeyboardStub}) { const priorEntry = this._activeKeyboard; - this._activeKeyboard = kbd; + + // Clone the stub before exposing it... + if(!this.confirmKeyboardChange(new KeyboardStub(kbd.metadata))) { + return; + } + + // Clone the object to prevent accidental by-reference changes. + this._activeKeyboard = {...kbd}; if(priorEntry.keyboard != kbd.keyboard || priorEntry.metadata != kbd.metadata) { this.emit('keyboardchange', kbd); this.resetContext(); } } + + async setActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub): Promise { + if(!this.confirmKeyboardChange) { + return false; + } + + // There is only the one target, so 'default global keyboard' use is fine. + if(!await this.deferredKeyboardActivationValid(kbd, metadata, null)) { + return false; + } else { + const activatingKeyboard = { + keyboard: await kbd, + metadata: metadata + }; + + this.activeKeyboard = activatingKeyboard; + + // The change may silently fail due to `set activeKeyboard`'s `confirmKeyboardChange` call. + return this.activeKeyboard.keyboard == activatingKeyboard.keyboard + && this.activeKeyboard.metadata == activatingKeyboard.metadata; + } + } } \ No newline at end of file diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index 93a54ee72f..269b91e8fd 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -5,7 +5,8 @@ import { PredictionContext } from '@keymanapp/input-processor'; interface EventMap { // target, then keyboard. - 'targetchange': (target: OutputTarget) => void; + 'targetchange': (target: OutputTarget) => boolean; + 'beforekeyboardchange': (metadata: KeyboardStub, abortChange: () => void) => void; 'keyboardchange': (kbd: {keyboard: Keyboard, metadata: KeyboardStub}) => void; } @@ -26,6 +27,12 @@ export interface ContextManagerConfiguration { readonly predictionContext: PredictionContext; } +interface PendingActivation { + target: OutputTarget, + keyboard: Promise, + stub: KeyboardStub; +} + export abstract class ContextManagerBase extends EventEmitter { abstract initialize(): void; @@ -34,6 +41,8 @@ export abstract class ContextManagerBase extends EventEmitter { private _predictionContext: PredictionContext; private _resetKeyState: (outputTarget?: OutputTarget) => void; + private pendingActivations: PendingActivation[] = []; + get predictionContext(): PredictionContext { return this._predictionContext; } @@ -79,4 +88,55 @@ export abstract class ContextManagerBase extends EventEmitter { abstract get activeKeyboard(): {keyboard: Keyboard, metadata: KeyboardStub}; abstract set activeKeyboard(kbd: {keyboard: Keyboard, metadata: KeyboardStub}); + abstract setActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub): Promise; + + /** + * Checks the pending keyboard-activation array for an entry corresponding to the specified + * OutputTarget. If found, also removes the entry for bookkeeping purposes. + * @param target The specific OutputTarget affected by the pending Keyboard activation. + * May be `null`, which corresponds to the global default Keyboard. + * @returns `true` if pending activation is still valid, `false` otherwise. + */ + private findAndPopActivation(target: OutputTarget): boolean { + // Array.findIndex requires Chrome 45+. :( + let activationIndex; + for(activationIndex = 0; activationIndex < this.pendingActivations.length; activationIndex++) { + if(this.pendingActivations[activationIndex].target == target) { + break; + } + } + + if(activationIndex == this.pendingActivations.length) { + return false; + } + + this.pendingActivations.splice(activationIndex, 1); + return true; + } + + protected confirmKeyboardChange(metadata: KeyboardStub): boolean { + const eventReturn = { + continue: true + }; + + this.emit('beforekeyboardchange', metadata, () => {eventReturn.continue = false}); + + return eventReturn.continue; + } + + protected async deferredKeyboardActivationValid(kbdPromise: Promise, metadata: KeyboardStub, target: OutputTarget): Promise { + const activation: PendingActivation = { + target: target, + keyboard: kbdPromise, + stub: metadata + }; + + // Invalidate existing requests for the specified target. + this.findAndPopActivation(target); + this.pendingActivations.push(activation); + await kbdPromise; + + // The keyboard-load is complete; is the activation still desired? + return this.findAndPopActivation(target); + } } \ No newline at end of file -- GitLab From c55ad3f5a0ef4c9c705057cca20ecd353f3f9425 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 23 Mar 2023 11:31:44 +0700 Subject: [PATCH 003/386] change(web): first-pass setActiveKeyboard modularization --- web/src/app/browser/src/configuration.ts | 8 ++ web/src/app/browser/src/keymanEngine.ts | 47 ++++++- web/src/app/webview/src/keymanEngine.ts | 34 ++++- web/src/engine/main/src/contextManagerBase.ts | 3 + web/src/engine/main/src/keymanEngine.ts | 116 +++++++++++++++++- .../package-cache/src/stubAndKeyboardCache.ts | 29 +++++ .../headless/packages/stubAndKeyboardCache.js | 22 ++++ 7 files changed, 250 insertions(+), 9 deletions(-) diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 5aa2fdf450..4e0c816859 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -3,18 +3,24 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/ export class BrowserConfiguration extends EngineConfiguration { private _ui: string; private _attachType: string; + private _useAlerts: boolean; initialize(options: Required) { this.initialize(options); this._ui = options.ui; this._attachType = options.attachType; + this._useAlerts = options.useAlerts; } get attachType() { return this._attachType; } + get shouldAlert(): boolean { + return this._useAlerts; + } + debugReport(): Record { const baseReport = super.debugReport(); baseReport.attachType = this.attachType; @@ -28,10 +34,12 @@ export class BrowserConfiguration extends EngineConfiguration { export interface BrowserInitOptionSpec extends InitOptionSpec { ui?: string; attachType?: 'auto' | 'manual' | ''; // If blank or undefined, attachType will be assigned to "auto" or "manual" + useAlerts?: boolean; } export const BrowserInitOptionDefaults: Required = { ui: '', attachType: '', + useAlerts: true, ...InitOptionDefaults } \ No newline at end of file diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index a63bccf299..9ee4f0a480 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -1,19 +1,58 @@ -import { EngineConfiguration, KeymanEngine as KeymanEngineBase } from 'keyman/engine/main'; +import { KeymanEngine as KeymanEngineBase } from 'keyman/engine/main'; import { ProcessorInitOptions } from "@keymanapp/keyboard-processor"; +import { type KeyboardStub } from "keyman/engine/package-cache"; +import { BrowserConfiguration } from './configuration.js'; import ContextManager from './contextManager.js'; -import DefaultOutput from './defaultOutput.js'; +import DefaultBrowserRules from './defaultBrowserRules.js'; import KeyEventKeyboard from './keyEventKeyboard.js'; export class KeymanEngine extends KeymanEngineBase { - constructor(worker: Worker, config: EngineConfiguration) { + constructor(worker: Worker, config: BrowserConfiguration) { super(worker, config, new ContextManager()); } protected processorConfiguration(): ProcessorInitOptions { return { keyboardInterface: this.interface, - defaultOutputRules: new DefaultOutput(this.contextManager) + defaultOutputRules: new DefaultBrowserRules(this.contextManager) }; }; + + protected async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise { + saveCookie ||= false; + + try { + await super.activateKeyboard(keyboardId, languageCode, saveCookie); + + if(saveCookie /* && this.contextManager.activeTarget does not have independent-keyboard mode active */) { + // TODO: persist the newly-activating keyboard's IDs for use as default upon page reload + } + + // TODO: app/browser - _SetTargDir (within its ContextManager) + } catch(err) { + // non-embedded: if keyboard activation failed, deactivate the keyboard. + + // Make sure we don't infinite-recursion should the deactivate somehow fail. + if(this.config.hostDevice.touchable) { + // Fallback behavior - if on a touch device, we need to keep a keyboard visible. + const defaultStub = this.keyboardRequisitioner.cache.defaultStub; + await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); + } else { + // Fallback behavior - if on a desktop device, the user still has a physical keyboard. + // Just clear out the active keyboard & OSK. + await this.activateKeyboard('', '', false).catch(() => {}); + } + + if((this.config as BrowserConfiguration).shouldAlert) { + // TODO: util.alert error report + } + + throw err; // since the site-dev consumer may want to do their own error-handling. + } + } + + protected onKeyboardAsyncLoadStart(requestedStub: KeyboardStub) { + // TODO: app/browser - display the loader UI if configured? + } } diff --git a/web/src/app/webview/src/keymanEngine.ts b/web/src/app/webview/src/keymanEngine.ts index 9ca764ec23..3753b0ebfb 100644 --- a/web/src/app/webview/src/keymanEngine.ts +++ b/web/src/app/webview/src/keymanEngine.ts @@ -1,8 +1,8 @@ -import { DeviceSpec } from '@keymanapp/keyboard-processor' +import { DeviceSpec, Keyboard } from '@keymanapp/keyboard-processor' import { KeymanEngine as KeymanEngineBase } from 'keyman/engine/main'; import { AnchoredOSKView, ViewConfiguration, StaticActivator } from 'keyman/engine/osk'; import { getAbsoluteX, getAbsoluteY } from 'keyman/engine/dom-utils'; -import { toPrefixedKeyboardId, toUnprefixedKeyboardId } from 'keyman/engine/package-cache'; +import { type KeyboardStub, toPrefixedKeyboardId, toUnprefixedKeyboardId } from 'keyman/engine/package-cache'; import { WebviewConfiguration, WebviewInitOptionDefaults, WebviewInitOptionSpec } from './configuration.js'; import ContextManager from './contextManager.js'; @@ -52,6 +52,36 @@ export class KeymanEngine extends KeymanEngineBase { + saveCookie ||= false; + + try { + await super.activateKeyboard(keyboardId, languageCode, saveCookie); + } catch(err) { + // Fallback behavior - we're embedded in a touch-device's webview, so we need to keep a keyboard visible. + const defaultStub = this.keyboardRequisitioner.cache.defaultStub; + await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); + + throw err; // since the consumer may want to do its own error-handling. + } + } + + protected async prepareKeyboardForActivation( + keyboardId: string, + languageCode?: string + ): Promise<{keyboard: Keyboard, metadata: KeyboardStub}> { + const originalKeyboard = this.contextManager.activeKeyboard; + const activatingKeyboard = await super.prepareKeyboardForActivation(keyboardId, languageCode); + + // Probably isn't necessary at this point - osk.refreshLayout() exists - but + // it's best to keep it around for now and verify later. + if(originalKeyboard.keyboard == activatingKeyboard.keyboard) { + activatingKeyboard.keyboard.refreshLayouts(); + } + + return activatingKeyboard; + } + // Functions that the old 'app/webview' equivalent had always provided to the WebView /** diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index 269b91e8fd..f968a62b4e 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -87,6 +87,9 @@ export abstract class ContextManagerBase extends EventEmitter { } abstract get activeKeyboard(): {keyboard: Keyboard, metadata: KeyboardStub}; + + // TODO: should `activateKeyboard` (on KeymanEngine) be relocated to within here? + // It seems to make sense, and was originally a goal. abstract set activeKeyboard(kbd: {keyboard: Keyboard, metadata: KeyboardStub}); abstract setActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub): Promise; diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index cc5f16a8eb..c0505aad0c 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -1,8 +1,8 @@ -import { DefaultRules, KeyboardKeymanGlobal, ProcessorInitOptions } from "@keymanapp/keyboard-processor"; +import { DefaultRules, type Keyboard, KeyboardKeymanGlobal, ProcessorInitOptions } from "@keymanapp/keyboard-processor"; import { DOMKeyboardLoader as KeyboardLoader } from "@keymanapp/keyboard-processor/dom-keyboard-loader"; import { InputProcessor, PredictionContext } from "@keymanapp/input-processor"; import { OSKView } from "keyman/engine/osk"; -import { KeyboardRequisitioner, ModelCache, ModelSpec } from "keyman/engine/package-cache"; +import { KeyboardRequisitioner, type KeyboardStub, ModelCache, ModelSpec } from "keyman/engine/package-cache"; import { EngineConfiguration, InitOptionSpec } from "./engineConfiguration.js"; import KeyboardInterface from "./keyboardInterface.js"; @@ -12,7 +12,10 @@ import HardKeyboardBase from "./hardKeyboard.js"; import { LegacyAPIEventEngine } from "./legacyAPIEvents.js"; import DOMCloudRequester from "keyman/engine/package-cache/dom-requester"; -export default class KeymanEngine implements KeyboardKeymanGlobal { +export default class KeymanEngine< + ContextManager extends ContextManagerBase, + HardKeyboard extends HardKeyboardBase +> implements KeyboardKeymanGlobal { readonly config: EngineConfiguration; readonly contextManager: ContextManager; readonly interface: KeyboardInterface; @@ -141,6 +144,7 @@ export default class KeymanEngine { + return this.activateKeyboard(keyboardId, languageCode, true); + } + + protected async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise { + saveCookie ||= false; + + // TODO: beforeKeyboardChange + // - this.osk.startHide(false), when needed, could be called via handler here... + // and on 'onAsyncKeyboardLoad' + // Also include an 'abort' check based upon it. + + this.contextManager.activeKeyboard = await this.prepareKeyboardForActivation(keyboardId, languageCode); + + // TODO: keyboardChange + // - this.osk.present() could totally be part of the handler for the event. + + this.osk.present(); + } + + /** + * Based on the provided keyboard id and language code, selects and (if necessary) loads the + * corresponding keyboard but does not activate it. + * + * This acts as a helper to `activateKeyboard`, helping to centralize and DRY out the actual + * activation of the requested keyboard. + * @param keyboardId + * @param languageCode + * @returns + */ + protected async prepareKeyboardForActivation( + keyboardId: string, + languageCode?: string + ): Promise<{keyboard: Keyboard, metadata: KeyboardStub}> { + // Set default language code + languageCode ||= ''; + + // Check that the saved keyboard is currently registered + let requestedStub = this.keyboardRequisitioner.cache.getStub(keyboardId, languageCode); + + // Mobile device addition: force selection of the first keyboard if none set + if(this.config.softDevice.touchable && !requestedStub) { + // Pick the oldest-registered stub as default. + requestedStub = this.keyboardRequisitioner.cache.defaultStub; + } else if(!requestedStub) { + // Hide OSK and do not update keyboard list if using internal keyboard (desktops) + this.osk?.startHide(false); + + return Promise.resolve({ + keyboard: null, + metadata: null + }); + } + + // Check if current keyboard matches requested keyboard, but not (necessarily) stub + if(keyboardId == this.contextManager.activeKeyboard.metadata.id) { + const keyboard = this.contextManager.activeKeyboard.keyboard; + // In this case, the keyboard is loaded; just update the stub. + + return Promise.resolve({ + keyboard: keyboard, + metadata: requestedStub + }); + } + + // Determine if the keyboard was previously loaded but is not active and use the prior load if so. + let keyboard: Keyboard; + if(keyboard = this.keyboardRequisitioner.cache.getKeyboardForStub(requestedStub)) { + return Promise.resolve({ + keyboard: keyboard, + metadata: requestedStub + }); + } else { + // async time - the keyboard has not yet been loaded. + + // Original implementation: also checked for CJK and kept OSKs activated pre-load, + // before the picker could ever show. We should be fine without it so long as + // a picker keyboard's OSK is kept activated post-load. + if(this.config.hostDevice.touchable && this.osk?.activationModel) { + this.osk.activationModel.enabled = true; + } + + this.osk?.startHide(false); + + // TODO: Maybe make this an event of sorts? ... which means extending EventEmitter, etc. + // We aren't adding a 'new' event API set quite yet, though. + this.onKeyboardAsyncLoadStart(requestedStub); + + let keyboardPromise = this.keyboardRequisitioner.cache.fetchKeyboardForStub(requestedStub); + + let promise = this.contextManager.setActiveKeyboardAsync(keyboardPromise, requestedStub); + return promise.then(async (stillValid) => { + if(!stillValid) { + return Promise.resolve(null); + } + + return { + keyboard: await keyboardPromise, + metadata: requestedStub + }; + }); + } + } + + protected onKeyboardAsyncLoadStart(requestedStub: KeyboardStub) { } } // Intent: define common behaviors for both primary app types; each then subclasses & extends where needed. \ No newline at end of file diff --git a/web/src/engine/package-cache/src/stubAndKeyboardCache.ts b/web/src/engine/package-cache/src/stubAndKeyboardCache.ts index 6ee0b3a9d4..0912440657 100644 --- a/web/src/engine/package-cache/src/stubAndKeyboardCache.ts +++ b/web/src/engine/package-cache/src/stubAndKeyboardCache.ts @@ -67,6 +67,35 @@ export default class StubAndKeyboardCache extends EventEmitter { return entry instanceof Promise ? null : entry; } + get defaultStub(): KeyboardStub { + /* See the following two StackOverflow links: + * - https://stackoverflow.com/a/23202095 + * - https://stackoverflow.com/a/5525820 + * + * Also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values#description + * + * As keyboard IDs are never purely numeric, any sufficiently-recent browser will + * maintain the order in which stubs were added to this cache. + * + * Note that if a keyboard is removed, its matching stubs are also removed, so the next most-recent + * property will take precedence. + * + * Might possibly fail to return the oldest registered stub for the oldest of supported browsers + * (i.e, Android 5.0), but will work for anything decently recent. Even then... we still supply + * _a_ keyboard. Just not in a way that will seem deterministic/controllable to site designers. + */ + const entries = Object.values(this.stubSetTable); + if(entries.length == 0) { + return undefined; + } else { + // Maps language codes to actual KeyboardStub entries. So... "stub table for the oldest registered keyboard". + const stubTable = entries[0]; + // First value = first registered stub for that first keyboard. + // Does not consider later-added stubs, but neither does removeKeyboard - removal is "all or nothing". + return Object.values(stubTable)[0]; // returns undefined if it does not exist. + } + } + addKeyboard(keyboard: Keyboard) { const keyboardID = prefixed(keyboard.id); this.keyboardTable[keyboardID] = keyboard; diff --git a/web/src/test/auto/headless/packages/stubAndKeyboardCache.js b/web/src/test/auto/headless/packages/stubAndKeyboardCache.js index 3ba9894d2b..03f6fbf5c5 100644 --- a/web/src/test/auto/headless/packages/stubAndKeyboardCache.js +++ b/web/src/test/auto/headless/packages/stubAndKeyboardCache.js @@ -40,6 +40,28 @@ describe('StubAndKeyboardCache', function () { assert.strictEqual(cache.findMatchingStub(new KeyboardStub('galaxie_hebrew_positional', 'he')), galaxie_stub); }); + it('can resolve original order of added stubs', () => { + const cache = new StubAndKeyboardCache(); + + assert.isNotOk(cache.defaultStub); + + // Could convert to run on all stubs, but... this should be fine as-is. + const khmer_angkor_stub = new KeyboardStub(JSON.parse(fs.readFileSync(require.resolve(`${rootCommonStubPath}/khmer_angkor.json`)))); + cache.addStub(khmer_angkor_stub); + + const galaxie_stub = new KeyboardStub(JSON.parse(fs.readFileSync(require.resolve(`${rootCommonStubPath}/galaxie_hebrew_positional.json`)))); + cache.addStub(galaxie_stub); + + const lao_2008_basic_stub = new KeyboardStub(JSON.parse(fs.readFileSync(require.resolve(`${rootCommonStubPath}/lao_2008_basic.json`)))); + cache.addStub(lao_2008_basic_stub); + + assert.strictEqual(cache.defaultStub, khmer_angkor_stub); + + cache.forgetKeyboard(khmer_angkor_stub.id); + + assert.strictEqual(cache.defaultStub, galaxie_stub); + }); + it('loads & caches `Keyboard`s, `Keyboard` promise pending resolution', async () => { let keyboardLoader = new NodeKeyboardLoader(new KeyboardHarness({}, MinimalKeymanGlobal)); const cache = new StubAndKeyboardCache(keyboardLoader); -- GitLab From acca3f0dbee52a6548f334cfe2a402ff87f63c95 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 23 Mar 2023 14:57:47 +0700 Subject: [PATCH 004/386] change(web): keyboard activation as part of ContextManager, related odds and ends --- .../src/keyboards/keyboardLoaderBase.ts | 46 ++- .../keyboards/loaders/domKeyboardLoader.ts | 11 +- .../keyboards/loaders/nodeKeyboardLoader.ts | 18 +- .../tests/node/keyboard-loading.js | 26 +- web/src/app/browser/src/contextManager.ts | 78 +++-- web/src/app/browser/src/keymanEngine.ts | 38 --- web/src/app/webview/src/contextManager.ts | 64 ++-- web/src/app/webview/src/keymanEngine.ts | 30 -- web/src/engine/main/src/contextManagerBase.ts | 204 ++++++++++-- web/src/engine/main/src/keymanEngine.ts | 144 +++----- .../namespaced-main/keyboards/kmwkeyboards.ts | 309 ------------------ .../package-cache/src/stubAndKeyboardCache.ts | 2 +- 12 files changed, 399 insertions(+), 571 deletions(-) diff --git a/common/web/keyboard-processor/src/keyboards/keyboardLoaderBase.ts b/common/web/keyboard-processor/src/keyboards/keyboardLoaderBase.ts index 6d3887246a..2595f37db6 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboardLoaderBase.ts +++ b/common/web/keyboard-processor/src/keyboards/keyboardLoaderBase.ts @@ -1,5 +1,6 @@ import Keyboard from "./keyboard.js"; import { KeyboardHarness } from "./keyboardHarness.js"; +import KeyboardProperties from "./keyboardProperties.js"; export default abstract class KeyboardLoaderBase { private _harness: KeyboardHarness; @@ -19,5 +20,48 @@ export default abstract class KeyboardLoaderBase { return promise; } - protected abstract loadKeyboardInternal(uri: string): Promise; + public loadKeyboardFromStub(stub: KeyboardProperties & { filename: string }) { + this.harness.install(); + let promise = this.loadKeyboardInternal(stub.filename, stub.id); + + promise = promise.catch((err: Error) => { + if(err == KeyboardLoaderBase.scriptErrorMessage(stub.filename)) { + // Enhance the error message. + err.message = KeyboardLoaderBase.scriptErrorMessage(stub); + } else if(err == KeyboardLoaderBase.missingErrorMessage(stub.filename)) { + // Same thing here. + err.message = KeyboardLoaderBase.scriptErrorMessage(stub); + } + + throw err; + }) + + return promise; + } + + protected abstract loadKeyboardInternal(uri: string, id?: string): Promise; + + protected static scriptErrorMessage(uri: string); + protected static scriptErrorMessage(metadata: KeyboardProperties & { filename: string }); + protected static scriptErrorMessage(arg: string | (KeyboardProperties & { filename: string })) { + if(typeof arg == "string") { + const uri = arg; + return `Error registering the keyboard script at ${uri}; it may contain an error.` + } else { + const stub = arg; + return `Error registering the ${stub.name} keyboard for ${stub.langName}; keyboard script at ${stub.filename} may contain an error.` + } + } + + protected static missingErrorMessage(uri: string); + protected static missingErrorMessage(metadata: KeyboardProperties & { filename: string }); + protected static missingErrorMessage(arg: string | (KeyboardProperties & { filename: string })) { + if(typeof arg == "string") { + const uri = arg; + return `Cannot find the keyboard at ${uri}.` + } else { + const stub = arg; + return `Cannot find the ${stub.name} keyboard for ${stub.langName} at ${stub.filename}.`; + } + } } \ No newline at end of file diff --git a/common/web/keyboard-processor/src/keyboards/loaders/domKeyboardLoader.ts b/common/web/keyboard-processor/src/keyboards/loaders/domKeyboardLoader.ts index 08dbac38ce..d753d37af9 100644 --- a/common/web/keyboard-processor/src/keyboards/loaders/domKeyboardLoader.ts +++ b/common/web/keyboard-processor/src/keyboards/loaders/domKeyboardLoader.ts @@ -28,7 +28,7 @@ export class DOMKeyboardLoader extends KeyboardLoaderBase { this.performCacheBusting = cacheBust || false; } - protected loadKeyboardInternal(uri: string): Promise { + protected loadKeyboardInternal(uri: string, id?: string): Promise { const promise = new ManagedPromise(); if(this.performCacheBusting) { @@ -38,15 +38,20 @@ export class DOMKeyboardLoader extends KeyboardLoaderBase { try { const document = this.harness._jsGlobal.document; const script = document.createElement('script'); + if(id) { + script.id = id; + } document.head.appendChild(script); - script.onerror = promise.reject; + script.onerror = () => { + promise.reject(new Error(KeyboardLoaderBase.missingErrorMessage(uri))); + } script.onload = () => { if(this.harness.loadedKeyboard) { const keyboard = this.harness.loadedKeyboard; this.harness.loadedKeyboard = null; promise.resolve(keyboard); } else { - promise.reject(); + promise.reject(new Error(KeyboardLoaderBase.scriptErrorMessage(uri))); } } diff --git a/common/web/keyboard-processor/src/keyboards/loaders/nodeKeyboardLoader.ts b/common/web/keyboard-processor/src/keyboards/loaders/nodeKeyboardLoader.ts index 6f7e7fa196..2f62274317 100644 --- a/common/web/keyboard-processor/src/keyboards/loaders/nodeKeyboardLoader.ts +++ b/common/web/keyboard-processor/src/keyboards/loaders/nodeKeyboardLoader.ts @@ -23,15 +23,21 @@ export class NodeKeyboardLoader extends KeyboardLoaderBase { } protected loadKeyboardInternal(uri: string): Promise { + // `fs` does not like 'file:///'; it IS "File System" oriented, after all, and wants a path, not a URI. + if(uri.indexOf('file:///') == 0) { + uri = uri.substring('file:///'.length); + } + + let script; + try { + script = new vm.Script(fs.readFileSync(uri).toString()); + } catch (err) { + return Promise.reject(new Error(KeyboardLoaderBase.missingErrorMessage(uri))); + } try { - // `fs` does not like 'file:///'; it IS "File System" oriented, after all, and wants a path, not a URI. - if(uri.indexOf('file:///') == 0) { - uri = uri.substring('file:///'.length); - } - const script = new vm.Script(fs.readFileSync(uri).toString()); script.runInContext(this.harness._jsGlobal); } catch (err) { - return Promise.reject(err); + return Promise.reject(new Error(KeyboardLoaderBase.scriptErrorMessage(uri))); } const keyboard = this.harness.loadedKeyboard; diff --git a/common/web/keyboard-processor/tests/node/keyboard-loading.js b/common/web/keyboard-processor/tests/node/keyboard-loading.js index fd29f08a27..1b0ccf1f70 100644 --- a/common/web/keyboard-processor/tests/node/keyboard-loading.js +++ b/common/web/keyboard-processor/tests/node/keyboard-loading.js @@ -4,12 +4,13 @@ import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardHarness, KeyboardInterface, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; +import { KeyboardHarness, KeyboardInterface, KeyboardLoaderBase, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; describe('Headless keyboard loading', function() { const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js'); const khmerPath = require.resolve('@keymanapp/common-test-resources/keyboards/khmer_angkor.js'); + const nonKeyboardPath = require.resolve('@keymanapp/common-test-resources/index.mjs'); // Common test suite setup. let device = { @@ -101,5 +102,28 @@ describe('Headless keyboard loading', function() { assert.equal(khmer_keyboard.id, "Keyboard_khmer_angkor"); }); + + it('throws distinct errors', async function() { + const invalidPath = 'totally_invalid_path.js'; + + let harness = new KeyboardInterface({}, MinimalKeymanGlobal); + let keyboardLoader = new NodeKeyboardLoader(harness); + let missingError; + try { + await keyboardLoader.loadKeyboardFromPath(invalidPath); + } catch (err) { + missingError = err; + assert.equal(missingError.message, KeyboardLoaderBase.missingErrorMessage(invalidPath)); + } + + let scriptLoadError; + try { + await keyboardLoader.loadKeyboardFromPath(nonKeyboardPath); + } catch (err) { + scriptLoadError = err; + } + + assert.notEqual(scriptLoadError.message, KeyboardLoaderBase.scriptErrorMessage(nonKeyboardPath)); + }); }) }); \ No newline at end of file diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index cc84328c20..4b4690f063 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -1,16 +1,31 @@ import { type Keyboard, Mock, OutputTarget } from '@keymanapp/keyboard-processor'; import { type KeyboardStub } from 'keyman/engine/package-cache'; +import { CookieSerializer } from 'keyman/engine/dom-utils'; import { ContextManagerBase, type KeyboardInterface } from 'keyman/engine/main'; import { BrowserConfiguration } from './configuration.js'; +interface KeyboardCookie { + current: string; +} + export default class ContextManager extends ContextManagerBase { private _activeKeyboard: {keyboard: Keyboard, metadata: KeyboardStub}; private config: BrowserConfiguration; + private cookieManager = new CookieSerializer('KeymanWeb_Keyboard'); initialize(): void { + this.on('keyboardasyncload', (stub, completion) => { + // TODO: app/browser - display the loader UI if configured? + // util.wait('Installing keyboard
' + kbdName); + + completion.then(() => { + // Cancel the loader UI. + }); + }); + // TBD: keyman.domManager.init (the page-integration parts) // CTRL+F: `// Exit initialization here if we're using an embedded code path.` // EVERYTHING after that block will likely go here - DOMManager's role always @@ -27,7 +42,7 @@ export default class ContextManager extends ContextManagerBase { return this._activeKeyboard; } - set activeKeyboard(kbd: {keyboard: Keyboard, metadata: KeyboardStub}) { + setKeyboardActiveForTarget(kbd: {keyboard: Keyboard, metadata: KeyboardStub}, target: OutputTarget) { throw new Error('Method not implemented.'); // depends on the target // if not set with an "independent keyboard", changes the global. @@ -64,7 +79,7 @@ export default class ContextManager extends ContextManagerBase { * When `null`, such operations will affect the global default; otherwise, such operations * affect only the specified `target`. */ - private get keyboardTarget(): OutputTarget { + protected get keyboardTarget(): OutputTarget { // Remove `&& false` once the inlined section below is implemented. if(this.activeTarget /* has 'independent keyboard mode activated' */ && false) { return this.activeTarget; @@ -73,39 +88,46 @@ export default class ContextManager extends ContextManagerBase { } } - async setTargetActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub, target: OutputTarget): Promise { - if(!await this.deferredKeyboardActivationValid(kbd, metadata, target)) { - return false; - } else { - let activatingKeyboard = { - keyboard: await kbd, - metadata: metadata - }; - if(target == this.keyboardTarget) { - // TODO: 'beforekeyboardchange' event + public async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise { + saveCookie ||= false; + const originalKeyboardTarget = this.keyboardTarget; + + try { + let result = await super.activateKeyboard(keyboardId, languageCode, saveCookie); + + if(saveCookie && !originalKeyboardTarget) { // if the active target uses global keyboard settings + this.cookieManager.save({current: `${keyboardId}:${languageCode}`}); } - // TODO: set THAT TARGET's active keyboard. May or may not be active! + // Only do these if the active keyboard-target still matches the original keyboard-target; + // otherwise, maintain what's correct for the currently active one. + if(originalKeyboardTarget == this.keyboardTarget) { + // TODO: app/browser - _SetTargDir (within its ContextManager) + // util.addStyleSheet(domManager.setAttachmentFontStyle(kbdStub.KF)); + // uiManager.justActivated = true; // TODO: Resolve without need for the cast. + } - if(target == this.keyboardTarget) { - // TODO: 'keyboardchange' event + return result; + } catch(err) { + // non-embedded: if keyboard activation failed, deactivate the keyboard. + + // Make sure we don't infinite-recursion should the deactivate somehow fail. + if(this.config.hostDevice.touchable) { + // Fallback behavior - if on a touch device, we need to keep a keyboard visible. + const defaultStub = this.keyboardCache.defaultStub; + await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); + } else { + // Fallback behavior - if on a desktop device, the user still has a physical keyboard. + // Just clear out the active keyboard & OSK. + await this.activateKeyboard('', '', false).catch(() => {}); } - /* - * Alternative to the three above TODOs: - * if (same condition met) - * this.activeKeyboard = activatingKeyboard - * else - * // manually set it for that one control; no events. - * - */ + if((this.config as BrowserConfiguration).shouldAlert) { + // TODO: util.alert error report + } - return true; + throw err; // since the site-dev consumer may want to do their own error-handling. } } - - async setActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub): Promise { - return this.setTargetActiveKeyboardAsync(kbd, metadata, this.keyboardTarget); - } } \ No newline at end of file diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 9ee4f0a480..0ddfda07ba 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -1,6 +1,5 @@ import { KeymanEngine as KeymanEngineBase } from 'keyman/engine/main'; import { ProcessorInitOptions } from "@keymanapp/keyboard-processor"; -import { type KeyboardStub } from "keyman/engine/package-cache"; import { BrowserConfiguration } from './configuration.js'; import ContextManager from './contextManager.js'; @@ -18,41 +17,4 @@ export class KeymanEngine extends KeymanEngineBase { - saveCookie ||= false; - - try { - await super.activateKeyboard(keyboardId, languageCode, saveCookie); - - if(saveCookie /* && this.contextManager.activeTarget does not have independent-keyboard mode active */) { - // TODO: persist the newly-activating keyboard's IDs for use as default upon page reload - } - - // TODO: app/browser - _SetTargDir (within its ContextManager) - } catch(err) { - // non-embedded: if keyboard activation failed, deactivate the keyboard. - - // Make sure we don't infinite-recursion should the deactivate somehow fail. - if(this.config.hostDevice.touchable) { - // Fallback behavior - if on a touch device, we need to keep a keyboard visible. - const defaultStub = this.keyboardRequisitioner.cache.defaultStub; - await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); - } else { - // Fallback behavior - if on a desktop device, the user still has a physical keyboard. - // Just clear out the active keyboard & OSK. - await this.activateKeyboard('', '', false).catch(() => {}); - } - - if((this.config as BrowserConfiguration).shouldAlert) { - // TODO: util.alert error report - } - - throw err; // since the site-dev consumer may want to do their own error-handling. - } - } - - protected onKeyboardAsyncLoadStart(requestedStub: KeyboardStub) { - // TODO: app/browser - display the loader UI if configured? - } } diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 6d4e20a3d9..f31582c562 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,4 +1,4 @@ -import { type Keyboard, Mock } from '@keymanapp/keyboard-processor'; +import { type Keyboard, Mock, OutputTarget } from '@keymanapp/keyboard-processor'; import { KeyboardStub } from 'keyman/engine/package-cache'; import { ContextManagerBase, ContextManagerConfiguration } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; @@ -51,42 +51,50 @@ export default class ContextManager extends ContextManagerBase { return this._activeKeyboard; } - set activeKeyboard(kbd: {keyboard: Keyboard, metadata: KeyboardStub}) { - const priorEntry = this._activeKeyboard; - - // Clone the stub before exposing it... - if(!this.confirmKeyboardChange(new KeyboardStub(kbd.metadata))) { - return; - } + setKeyboardActiveForTarget(kbd: {keyboard: Keyboard, metadata: KeyboardStub}, target: OutputTarget) { + // `target` is irrelevant for `app/webview`, as it'll only ever use 'global' keyboard settings. // Clone the object to prevent accidental by-reference changes. this._activeKeyboard = {...kbd}; + } - if(priorEntry.keyboard != kbd.keyboard || priorEntry.metadata != kbd.metadata) { - this.emit('keyboardchange', kbd); - this.resetContext(); - } + /** + * Reflects the active 'target' upon which any `set activeKeyboard` operation will take place. + * For app/webview... there's only one target, thus only a "global default" matters. + */ + protected get keyboardTarget(): Mock { + return null; } - async setActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub): Promise { - if(!this.confirmKeyboardChange) { - return false; - } - // There is only the one target, so 'default global keyboard' use is fine. - if(!await this.deferredKeyboardActivationValid(kbd, metadata, null)) { - return false; - } else { - const activatingKeyboard = { - keyboard: await kbd, - metadata: metadata - }; + public async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise { + try { + return await super.activateKeyboard(keyboardId, languageCode, saveCookie); + } catch(err) { + // Fallback behavior - we're embedded in a touch-device's webview, so we need to keep a keyboard visible. + const defaultStub = this.keyboardCache.defaultStub; + await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); - this.activeKeyboard = activatingKeyboard; + throw err; // since the consumer may want to do its own error-handling. + } + } - // The change may silently fail due to `set activeKeyboard`'s `confirmKeyboardChange` call. - return this.activeKeyboard.keyboard == activatingKeyboard.keyboard - && this.activeKeyboard.metadata == activatingKeyboard.metadata; + protected prepareKeyboardForActivation( + keyboardId: string, + languageCode?: string + ): {keyboard: Promise, metadata: KeyboardStub} { + const originalKeyboard = this.activeKeyboard; + const activatingKeyboard = super.prepareKeyboardForActivation(keyboardId, languageCode); + + // Probably isn't necessary at this point - osk.refreshLayout() exists - but + // it's best to keep it around for now and verify later. + if(originalKeyboard.metadata.id == activatingKeyboard.metadata.id) { + activatingKeyboard.keyboard = activatingKeyboard.keyboard.then((kbd) => { + kbd.refreshLayouts() + return kbd; + }); } + + return activatingKeyboard; } } \ No newline at end of file diff --git a/web/src/app/webview/src/keymanEngine.ts b/web/src/app/webview/src/keymanEngine.ts index 3753b0ebfb..3bec676679 100644 --- a/web/src/app/webview/src/keymanEngine.ts +++ b/web/src/app/webview/src/keymanEngine.ts @@ -52,36 +52,6 @@ export class KeymanEngine extends KeymanEngineBase { - saveCookie ||= false; - - try { - await super.activateKeyboard(keyboardId, languageCode, saveCookie); - } catch(err) { - // Fallback behavior - we're embedded in a touch-device's webview, so we need to keep a keyboard visible. - const defaultStub = this.keyboardRequisitioner.cache.defaultStub; - await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); - - throw err; // since the consumer may want to do its own error-handling. - } - } - - protected async prepareKeyboardForActivation( - keyboardId: string, - languageCode?: string - ): Promise<{keyboard: Keyboard, metadata: KeyboardStub}> { - const originalKeyboard = this.contextManager.activeKeyboard; - const activatingKeyboard = await super.prepareKeyboardForActivation(keyboardId, languageCode); - - // Probably isn't necessary at this point - osk.refreshLayout() exists - but - // it's best to keep it around for now and verify later. - if(originalKeyboard.keyboard == activatingKeyboard.keyboard) { - activatingKeyboard.keyboard.refreshLayouts(); - } - - return activatingKeyboard; - } - // Functions that the old 'app/webview' equivalent had always provided to the WebView /** diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index f968a62b4e..215ace507e 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -1,12 +1,13 @@ import EventEmitter from 'eventemitter3'; -import { type Keyboard, type KeyboardInterface, type OutputTarget } from '@keymanapp/keyboard-processor'; -import { type KeyboardStub } from 'keyman/engine/package-cache'; +import { DeviceSpec, ManagedPromise, type Keyboard, type KeyboardInterface, type OutputTarget } from '@keymanapp/keyboard-processor'; +import { StubAndKeyboardCache, type KeyboardStub } from 'keyman/engine/package-cache'; import { PredictionContext } from '@keymanapp/input-processor'; interface EventMap { // target, then keyboard. 'targetchange': (target: OutputTarget) => boolean; 'beforekeyboardchange': (metadata: KeyboardStub, abortChange: () => void) => void; + 'keyboardasyncload': (metadata: KeyboardStub, onload: Promise) => void; 'keyboardchange': (kbd: {keyboard: Keyboard, metadata: KeyboardStub}) => void; } @@ -18,13 +19,19 @@ export interface ContextManagerConfiguration { * * Does not reset option-stores, variable-stores, etc. */ - readonly resetKeyState: (outputTarget?: OutputTarget) => void; + readonly resetContext: (outputTarget?: OutputTarget) => void; /** * A predictive-state management object that interfaces the predictive-text banner * with the active context. */ readonly predictionContext: PredictionContext; + + /** + * The stub & keyboard curation cache holding preloaded keyboards and metadata useable + * to load those not yet loaded. + */ + readonly keyboardCache: StubAndKeyboardCache; } interface PendingActivation { @@ -34,12 +41,16 @@ interface PendingActivation { } export abstract class ContextManagerBase extends EventEmitter { + public static readonly TIMEOUT_THRESHOLD = 10000; + abstract initialize(): void; abstract get activeTarget(): OutputTarget; private _predictionContext: PredictionContext; - private _resetKeyState: (outputTarget?: OutputTarget) => void; + protected keyboardCache: StubAndKeyboardCache; + private _resetContext: (outputTarget?: OutputTarget) => void; + private _hostDevice: DeviceSpec; private pendingActivations: PendingActivation[] = []; @@ -47,18 +58,15 @@ export abstract class ContextManagerBase extends EventEmitter { return this._predictionContext; } - protected get resetKeyState(): (outputTarget?: OutputTarget) => void { - return this._resetKeyState; - } - constructor() { super(); } configure(config: ContextManagerConfiguration) { // TODO: Set in followup configuration method. Part of initialization? - this._resetKeyState = config.resetKeyState; + this._resetContext = config.resetContext; this._predictionContext = config.predictionContext; + this.keyboardCache = config.keyboardCache; } insertText(kbdInterface: KeyboardInterface, Ptext: string, PdeadKey: number) { @@ -82,16 +90,20 @@ export abstract class ContextManagerBase extends EventEmitter { } resetContext() { - this._resetKeyState(this.activeTarget); + this._resetContext(this.activeTarget); this.predictionContext.resetContext(); } abstract get activeKeyboard(): {keyboard: Keyboard, metadata: KeyboardStub}; + protected abstract get keyboardTarget(): OutputTarget; - // TODO: should `activateKeyboard` (on KeymanEngine) be relocated to within here? - // It seems to make sense, and was originally a goal. - abstract set activeKeyboard(kbd: {keyboard: Keyboard, metadata: KeyboardStub}); - abstract setActiveKeyboardAsync(kbd: Promise, metadata: KeyboardStub): Promise; + /** + * Ensures that newly activated keyboards are set correctly within managed context, possibly + * against inactive output targets. + * @param kbd + * @param target + */ + protected abstract setKeyboardActiveForTarget(kbd: {keyboard: Keyboard, metadata: KeyboardStub}, target: OutputTarget); /** * Checks the pending keyboard-activation array for an entry corresponding to the specified @@ -100,7 +112,7 @@ export abstract class ContextManagerBase extends EventEmitter { * May be `null`, which corresponds to the global default Keyboard. * @returns `true` if pending activation is still valid, `false` otherwise. */ - private findAndPopActivation(target: OutputTarget): boolean { + private findAndPopActivation(target: OutputTarget): PendingActivation { // Array.findIndex requires Chrome 45+. :( let activationIndex; for(activationIndex = 0; activationIndex < this.pendingActivations.length; activationIndex++) { @@ -110,11 +122,10 @@ export abstract class ContextManagerBase extends EventEmitter { } if(activationIndex == this.pendingActivations.length) { - return false; + return null; } - this.pendingActivations.splice(activationIndex, 1); - return true; + return this.pendingActivations.splice(activationIndex, 1)[0]; } protected confirmKeyboardChange(metadata: KeyboardStub): boolean { @@ -127,7 +138,11 @@ export abstract class ContextManagerBase extends EventEmitter { return eventReturn.continue; } - protected async deferredKeyboardActivationValid(kbdPromise: Promise, metadata: KeyboardStub, target: OutputTarget): Promise { + protected async deferredKeyboardActivation( + kbdPromise: Promise, + metadata: KeyboardStub, + target: OutputTarget + ): Promise { const activation: PendingActivation = { target: target, keyboard: kbdPromise, @@ -140,6 +155,155 @@ export abstract class ContextManagerBase extends EventEmitter { await kbdPromise; // The keyboard-load is complete; is the activation still desired? - return this.findAndPopActivation(target); + const activationAfterAwait = this.findAndPopActivation(target); + if(activationAfterAwait == activation) { + return activation; + } else { + return null; + } + } + + /** + * Change active keyboard to keyboard selected by (internal) name and language code + * + * Test if selected keyboard already loaded, and simply update active stub if so. + * Otherwise, insert a script to download and insert the keyboard from the repository + * or user-indicated file location. + * + * TODO: The old 'recorder' tool stubbed the old _SetActiveKeyboard method, but should now stub this + * instead. Or, perhaps one of the newer internal events. + * @param keyboardId + * @param languageCode + * @param saveCookie + * @returns + */ + public async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise { + const activatingKeyboard = this.prepareKeyboardForActivation(keyboardId, languageCode); + const originalKeyboardTarget = this.keyboardTarget; + + // Triggers `beforeKeyboardChange` event + // - this.osk.startHide(false), when needed, could be called via handler here... + // and on 'onAsyncKeyboardLoad' + // Also include an 'abort' check based upon it. + if(!this.confirmKeyboardChange(activatingKeyboard.metadata)) { + return false; + } + + const keyboard = await activatingKeyboard.keyboard; + if(keyboard == null && activatingKeyboard.metadata) { + // Cancelled - the activation was async and no longer valid. + return false; + } + + this.setKeyboardActiveForTarget({ + keyboard: keyboard, + metadata: activatingKeyboard.metadata + }, this.keyboardTarget); + + // Only trigger `keyboardchange` events when they will affect the active context. + if(this.keyboardTarget == originalKeyboardTarget) { + // Perform standard context-reset ops, including processNewContextEvent. + this.resetContext(); + // Will trigger KeymanEngine handler that passes keyboard to the OSK, displays it. + this.emit('keyboardchange', this.activeKeyboard); + } + + return true; + } + + /** + * Based on the provided keyboard id and language code, selects and (if necessary) loads the + * corresponding keyboard but does not activate it. + * + * This acts as a helper to `activateKeyboard`, helping to centralize and DRY out the actual + * activation of the requested keyboard. + * @param keyboardId + * @param languageCode + * @returns + */ + protected prepareKeyboardForActivation( + keyboardId: string, + languageCode?: string + ): {keyboard: Promise, metadata: KeyboardStub} { + // Set default language code + languageCode ||= ''; + + // Check that the saved keyboard is currently registered + let requestedStub = this.keyboardCache.getStub(keyboardId, languageCode); + + // Mobile device addition: force selection of the first keyboard if none set + if(this._hostDevice.touchable && !requestedStub) { + // Pick the oldest-registered stub as default. + requestedStub = this.keyboardCache.defaultStub; + } else if(!requestedStub) { + return { + keyboard: Promise.resolve(null), + metadata: null + }; + } + + // Check if current keyboard matches requested keyboard, but not (necessarily) stub + if(keyboardId == this.activeKeyboard.metadata.id) { + const keyboard = this.activeKeyboard.keyboard; + // In this case, the keyboard is loaded; just update the stub. + + return { + keyboard: Promise.resolve(keyboard), + metadata: requestedStub + }; + } + + // Determine if the keyboard was previously loaded but is not active; use the cached, pre-loaded version if so. + let keyboard: Keyboard; + if(keyboard = this.keyboardCache.getKeyboardForStub(requestedStub)) { + return { + keyboard: Promise.resolve(keyboard), + metadata: requestedStub + }; + } else { + // It's async time - the keyboard is not preloaded within the cache. Use the stub's data to load it. + + // Provide a Promise for completion of the async load process. + const completionPromise = new ManagedPromise(); + this.emit('keyboardasyncload', requestedStub, completionPromise.corePromise); + + let keyboardPromise = this.keyboardCache.fetchKeyboardForStub(requestedStub); + let timeoutPromise = new Promise((resolve, reject) => { + const timeoutMsg = `Sorry, the ${requestedStub.name} keyboard for ${requestedStub.langName} is not currently available.`; + window.setTimeout(() => reject(new Error(timeoutMsg)), ContextManagerBase.TIMEOUT_THRESHOLD); + }); + + let combinedPromise = Promise.race([keyboardPromise, timeoutPromise]); + + // Ensure the async-load Promise completes properly. + combinedPromise.then(() => completionPromise.resolve(null)); + combinedPromise.catch((err) => { + completionPromise.resolve(err); + throw err; + }); + + // Now the fun part: note the original call's parameters as a pending activation. + let promise = this.deferredKeyboardActivation(keyboardPromise, requestedStub, this.keyboardTarget); + return { + keyboard: promise.then(async (activation) => { + // Is the activation we requested still pending, or was it cancelled in favor of a + // different activation in some manner? + if(!activation) { + // If the user chose to load a different keyboard afterward that would affect the same + // output target, the activation is no longer valid. + return Promise.resolve(null); + } else if(activation.target == this.keyboardTarget && !this.confirmKeyboardChange(requestedStub)) { + // If still valid, but it would affect the active output target, we provide another chance + // to cancel the keyboard change - after all, we're in an async op. + return Promise.resolve(null); + } else { + // If still valid but it won't affect the currently-active output target, we don't ask to verify. + // It wouldn't affect the active context, so a corresponding event would be too unclear / confusing. + return keyboardPromise; + } + }), + metadata: requestedStub + } + } } } \ No newline at end of file diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index c0505aad0c..99b8aaeace 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -97,10 +97,11 @@ export default class KeymanEngine< this.processor = new InputProcessor(config.hostDevice, worker, this.processorConfiguration()); this.contextManager.configure({ - resetKeyState: (target) => { + resetContext: (target) => { this.processor.keyboardProcessor.resetContext(target); }, - predictionContext: new PredictionContext(this.processor.languageProcessor, this.processor.keyboardProcessor) + predictionContext: new PredictionContext(this.processor.languageProcessor, this.processor.keyboardProcessor), + keyboardCache: this.keyboardRequisitioner.cache }); // TODO: configure that context-manager! @@ -143,11 +144,44 @@ export default class KeymanEngine< contextManager.on('keyboardchange', (kbd) => { this.refreshModel(); + // Hide OSK and do not update keyboard list if using internal keyboard (desktops). + // Condition will not be met for touch form-factors; they force selection of a + // default keyboard. + if(kbd.keyboard == null && kbd.metadata == null) { + this.osk.startHide(false); + } + if(this.osk) { this.osk.setNeedsLayout(); this.osk.activeKeyboard = kbd; + this.osk.present(); } }); + + contextManager.on('keyboardasyncload', (metadata) => { + /* Original implementation pre-modularization: + * + * > Force OSK display for CJK keyboards (keyboards using a pick list) + * + * A matching subcondition in the block below will ensure that the OSK activates pre-load + * for CJK keyboards. Yes, even before a CJK picker could ever show. We should be fine + * without the CJK check so long as a picker keyboard's OSK is kept activated post-load, + * when the picker actually needs to be kept persistently-active. + * `metadata` would be relevant a the CJK-check, which was based on language codes. + * + * Of course, as mobile devices don't have guaranteed physical keyboards... we need to + * keep the OSK visible for them, hence the actual block below. + */ + if(this.config.hostDevice.touchable && this.osk?.activationModel) { + this.osk.activationModel.enabled = true; + // Also note: the OSKView.mayDisable method returns false when hostDevice.touchable = false. + // The .startHide() call below will check that method before actually starting an OSK hide. + } + + // Always (temporarily) hide the OSK when loading a new keyboard, to ensure + // that a failure to load doesn't leave the current OSK displayed + this.osk?.startHide(false); + }) // #endregion } @@ -235,111 +269,9 @@ export default class KeymanEngine< } } - async setActiveKeyboard(keyboardId: string, languageCode?: string): Promise { - return this.activateKeyboard(keyboardId, languageCode, true); - } - - protected async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise { - saveCookie ||= false; - - // TODO: beforeKeyboardChange - // - this.osk.startHide(false), when needed, could be called via handler here... - // and on 'onAsyncKeyboardLoad' - // Also include an 'abort' check based upon it. - - this.contextManager.activeKeyboard = await this.prepareKeyboardForActivation(keyboardId, languageCode); - - // TODO: keyboardChange - // - this.osk.present() could totally be part of the handler for the event. - - this.osk.present(); - } - - /** - * Based on the provided keyboard id and language code, selects and (if necessary) loads the - * corresponding keyboard but does not activate it. - * - * This acts as a helper to `activateKeyboard`, helping to centralize and DRY out the actual - * activation of the requested keyboard. - * @param keyboardId - * @param languageCode - * @returns - */ - protected async prepareKeyboardForActivation( - keyboardId: string, - languageCode?: string - ): Promise<{keyboard: Keyboard, metadata: KeyboardStub}> { - // Set default language code - languageCode ||= ''; - - // Check that the saved keyboard is currently registered - let requestedStub = this.keyboardRequisitioner.cache.getStub(keyboardId, languageCode); - - // Mobile device addition: force selection of the first keyboard if none set - if(this.config.softDevice.touchable && !requestedStub) { - // Pick the oldest-registered stub as default. - requestedStub = this.keyboardRequisitioner.cache.defaultStub; - } else if(!requestedStub) { - // Hide OSK and do not update keyboard list if using internal keyboard (desktops) - this.osk?.startHide(false); - - return Promise.resolve({ - keyboard: null, - metadata: null - }); - } - - // Check if current keyboard matches requested keyboard, but not (necessarily) stub - if(keyboardId == this.contextManager.activeKeyboard.metadata.id) { - const keyboard = this.contextManager.activeKeyboard.keyboard; - // In this case, the keyboard is loaded; just update the stub. - - return Promise.resolve({ - keyboard: keyboard, - metadata: requestedStub - }); - } - - // Determine if the keyboard was previously loaded but is not active and use the prior load if so. - let keyboard: Keyboard; - if(keyboard = this.keyboardRequisitioner.cache.getKeyboardForStub(requestedStub)) { - return Promise.resolve({ - keyboard: keyboard, - metadata: requestedStub - }); - } else { - // async time - the keyboard has not yet been loaded. - - // Original implementation: also checked for CJK and kept OSKs activated pre-load, - // before the picker could ever show. We should be fine without it so long as - // a picker keyboard's OSK is kept activated post-load. - if(this.config.hostDevice.touchable && this.osk?.activationModel) { - this.osk.activationModel.enabled = true; - } - - this.osk?.startHide(false); - - // TODO: Maybe make this an event of sorts? ... which means extending EventEmitter, etc. - // We aren't adding a 'new' event API set quite yet, though. - this.onKeyboardAsyncLoadStart(requestedStub); - - let keyboardPromise = this.keyboardRequisitioner.cache.fetchKeyboardForStub(requestedStub); - - let promise = this.contextManager.setActiveKeyboardAsync(keyboardPromise, requestedStub); - return promise.then(async (stillValid) => { - if(!stillValid) { - return Promise.resolve(null); - } - - return { - keyboard: await keyboardPromise, - metadata: requestedStub - }; - }); - } + async setActiveKeyboard(keyboardId: string, languageCode?: string): Promise { + return this.contextManager.activateKeyboard(keyboardId, languageCode, true); } - - protected onKeyboardAsyncLoadStart(requestedStub: KeyboardStub) { } } // Intent: define common behaviors for both primary app types; each then subclasses & extends where needed. \ No newline at end of file diff --git a/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts b/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts index 4abce41d5c..311f2c8744 100644 --- a/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts +++ b/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts @@ -189,315 +189,6 @@ namespace com.keyman.keyboards { return p; } - /** - * Change active keyboard to keyboard selected by (internal) name and language code - * - * Test if selected keyboard already loaded, and simply update active stub if so. - * Otherwise, insert a script to download and insert the keyboard from the repository - * or user-indicated file location. - * - * Note that the test-case oriented 'recorder' stubs this method to provide active - * keyboard stub information. If changing this function, please ensure the recorder is - * not affected. - * - * @param {string} PInternalName - * @param {string=} PLgCode - * @param {boolean=} saveCookie - */ - _SetActiveKeyboard(PInternalName: string, PLgCode?: string, saveCookie?: boolean): Promise { - let n; - - let keyman = com.keyman.singleton; - - var util = keyman.util; - var domManager = keyman.domManager; - var osk = keyman.osk; - - let activeKeyboard = keyman.core.activeKeyboard; - - // Set default language code - if(arguments.length < 2 || (!PLgCode)) { - PLgCode='---'; - } - - // Check that the saved keyboard is currently registered - for(n=0; n < this.keyboardStubs.length; n++) { - if(PInternalName == this.keyboardStubs[n]['KI']) { - if(PLgCode == this.keyboardStubs[n]['KLC'] || PLgCode == '---') break; - // This 'n' is used in the condition for the block below... and that's all. - } - } - - // Mobile device addition: force selection of the first keyboard if none set - if(util.device.touchable && (PInternalName == '' || PInternalName == null || n >= this.keyboardStubs.length)) { - if(this.keyboardStubs.length != 0) { - PInternalName=this.keyboardStubs[0]['KI']; - PLgCode=this.keyboardStubs[0]['KLC']; - } - } - - // Save name of keyboard (with language code) as a cookie - if(arguments.length > 2 && saveCookie) { - this.saveCurrentKeyboard(PInternalName,PLgCode); - } - - // Check if requested keyboard and stub are currently active - if(this.activeStub && activeKeyboard && activeKeyboard.id == PInternalName - && this.activeStub['KI'] == PInternalName //this part of test should not be necessary, but keep anyway - && this.activeStub['KLC'] == PLgCode && !this.keymanweb.mustReloadKeyboard - ) return Promise.resolve(); - - // Check if current keyboard matches requested keyboard, but not stub - if(activeKeyboard && (activeKeyboard.id == PInternalName)) { - // If so, simply update the active stub - for(let Ln=0; Ln 0) { - var Ps = this.keyboardStubs[0]; - this._SetActiveKeyboard(Ps['KI'], Ps['KLC'], true); - } - }.bind(this); - - loadingStub.asyncLoader.timer = window.setTimeout(loadingStub.asyncLoader.callback, 10000); - - //Display the loading delay bar (Note: only append 'keyboard' if not included in name.) - if(!this.keymanweb.isEmbedded) { - util.wait('Installing keyboard
' + kbdName); - } - - // Installing the script immediately does not work reliably if two keyboards are - // loaded in succession if there is any delay in downloading the script. - // It works much more reliably if deferred (KMEW-101, build 356) - // The effect of a delay can also be tested, for example, by setting the timeout to 5000 - var manager = this; - loadingStub.asyncLoader.promise = new Promise(function(resolve, reject) { - window.setTimeout(function(){ - manager.installKeyboard(resolve, reject, loadingStub); - // To be modularized: activation after the keyboard's loaded. - },0); - }); - } - this.activeStub=this.keyboardStubs[Ln]; - return this.keyboardStubs[Ln].asyncLoader.promise; - } - } - this.keymanweb.domManager._SetTargDir(this.keymanweb.domManager.lastActiveElement); // I2077 - LTR/RTL timing - } - - // Initialize the OSK (provided that the base code has been loaded) - if(osk) { - osk._Load(); - util.addStyleSheet(domManager.setAttachmentFontStyle(this.activeStub.KF)); - } - return Promise.resolve(); - } - - /** - * Install a keyboard script that has been downloaded from a keyboard server - * Operates as the core of a Promise, hence the 'resolve' and 'reject' parameters. - * - * @param {Object} kbdStub keyboard stub to be loaded. - * - **/ - installKeyboard(resolve: () => void, reject: (message?: string) => void, kbdStub: KeyboardStub) { - var util = this.keymanweb.util; - var osk = this.keymanweb.osk; - - var Lscript = util._CreateElement('script'); - Lscript.charset="UTF-8"; // KMEW-89 - Lscript.type = 'text/javascript'; - - // Preserve any namespaced IDs by use of the script's id tag attribute! - if(this.keymanweb.isEmbedded) { - Lscript.id = kbdStub['KI']; - } - - var kbdFile = kbdStub['KF']; - var kbdLang = kbdStub['KL']; - var kbdName = kbdStub['KN']; - - const scriptSrc = this.keymanweb.getKeyboardPath(kbdFile); - - var manager = this; - let core = com.keyman.singleton.core; - let domManager = com.keyman.singleton.domManager; - - // Add a handler for cases where the new + - + KP; + IP-->KP; + Utils["common/web/utils"]; + KP---->Utils; + Wordbreakers["common/models/wordbreakers"]; + Models["common/models/templates"]; + Models-->Utils; + LMWorker["common/web/lm-worker"]; + LMWorker-->Models; + LMWorker-->Wordbreakers; + LMLayer["common/predictive-text"]; + LMLayer-->LMWorker; + IP-->LMLayer; + + subgraph PredText["WebWorker + its interface"] + LMLayer; + LMWorker; + Models; + Wordbreakers; + end + + subgraph Headless["Fully headless components"] + direction LR + KP; + IP; + Utils; + PredText; + end + + subgraph ClassicWeb["Previously unmodularized components"] + Device[web/src/engine/device-detect]; + Device----->Utils; + Elements[web/src/engine/element-wrappers]; + Elements-->KP; + KeyboardCache[web/src/engine/package-cache]; + KeyboardCache-->IP; + DomUtils[web/src/engine/dom-utils]; + DomUtils-->Utils; + OSK-->DomUtils; + OSK---->IP; + Configuration[web/src/engine/paths]; + Configuration-->OSK; + CommonEngine[web/src/engine/main]; + CommonEngine-->Configuration; + CommonEngine-->Device; + CommonEngine-->KeyboardCache; + CommonEngine-->OSK; + Attachment[web/src/engine/attachment]; + Attachment-->DomUtils; + Attachment-->Elements; + end + + subgraph WebEngine["Keyman Engine for Web (top-level libraries)"] + Browser[web/src/app/browser]; + WebView[web/src/app/webview]; + + WebView--->CommonEngine; + + Browser--->CommonEngine; + Browser-->Attachment; + end +``` \ No newline at end of file -- GitLab From 1341183964303a945efde6aaa328b8d8bb0ba61f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 17 May 2023 08:28:52 +0700 Subject: [PATCH 166/386] chore(web): oh right, builder_warn is a thing --- web/build.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/build.sh b/web/build.sh index bc0811314e..b11223ca79 100755 --- a/web/build.sh +++ b/web/build.sh @@ -105,6 +105,10 @@ builder_run_child_actions build:app/browser builder_run_child_actions test +if builder_has_action build:app/browser; then + builder_warn "Modularization work is not yet complete; consumers may find needed API or components to be missing" +fi + if builder_has_action build:app/ui; then builder_die "Modularization work is not yet complete; builds dependent on this will fail." fi -- GitLab From 73b8db7f818bd13c7d719e9f96d56cfd6cdb269a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 2 May 2023 16:03:17 +0700 Subject: [PATCH 167/386] feat(web): intial app/browser context management unit tests --- web/src/app/browser/build-bundler.js | 16 ++ web/src/app/browser/src/test-index.ts | 2 + web/src/engine/events/build-bundler.js | 24 +++ .../auto/dom/cases/browser/contextManager.js | 204 ++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 web/src/app/browser/src/test-index.ts create mode 100644 web/src/engine/events/build-bundler.js create mode 100644 web/src/test/auto/dom/cases/browser/contextManager.js diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 786f30a0db..3d855f92ed 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -62,3 +62,19 @@ await esbuild.build({ treeShaking: true, tsconfig: './tsconfig.json' }); + +await esbuild.build({ + bundle: true, + sourcemap: true, + minify: false, + format: "esm", + nodePaths: ['../../../../node_modules'], + entryPoints: { + 'index': '../../../build/app/browser/obj/test-index.js', + }, + outfile: '../../../build/app/browser/lib/index.mjs', + plugins: [ es5ClassAnnotationAsPurePlugin ], + target: "es5", + treeShaking: true, + tsconfig: './tsconfig.json' +}); \ No newline at end of file diff --git a/web/src/app/browser/src/test-index.ts b/web/src/app/browser/src/test-index.ts new file mode 100644 index 0000000000..1a7a97c296 --- /dev/null +++ b/web/src/app/browser/src/test-index.ts @@ -0,0 +1,2 @@ +export { BrowserConfiguration } from './configuration.js'; +export { default as ContextManager } from "./contextManager.js"; \ No newline at end of file diff --git a/web/src/engine/events/build-bundler.js b/web/src/engine/events/build-bundler.js new file mode 100644 index 0000000000..6c6f45c869 --- /dev/null +++ b/web/src/engine/events/build-bundler.js @@ -0,0 +1,24 @@ +/* + * Note: while this file is not meant to exist long-term, it provides a nice + * low-level proof-of-concept for esbuild bundling of the various Web submodules. + * + * Add some extra code at the end of src/index.ts and run it to verify successful bundling! + */ + +import esbuild from 'esbuild'; +import { spawn } from 'child_process'; + +await esbuild.build({ + bundle: true, + sourcemap: true, + format: "esm", + nodePaths: ['../../../../node_modules'], + entryPoints: { + 'index': '../../../build/engine/events/obj/index.js', + }, + external: ['fs', 'vm'], + outdir: '../../../build/engine/events/lib/', + outExtension: { '.js': '.mjs' }, + tsconfig: './tsconfig.json', + target: "es5" +}); diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js new file mode 100644 index 0000000000..fc068f79b8 --- /dev/null +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -0,0 +1,204 @@ +import { ContextManager } from '/@keymanapp/keyman/build/app/browser/lib/index.mjs'; +import { LegacyEventEmitter } from '/@keymanapp/keyman/build/engine/events/lib/index.mjs'; +import { StubAndKeyboardCache } from '/@keymanapp/keyman/build/engine/package-cache/lib/index.mjs'; + +import timedPromise from '../../timedPromise.mjs'; + +const assert = chai.assert; + +const TEST_PHYSICAL_DEVICE = { + formFactor: 'desktop', + OS: 'windows', + browser: 'native', + touchable: false +}; + +function promiseForIframeLoad(iframe) { + // Chrome makes this first case tricky - it initializes all iframes with a 'complete' about:blank + // before loading the actual href. (https://stackoverflow.com/a/36155560) + if(iframe.contentDocument + && iframe.contentDocument.readyState === 'complete' + && iframe.contentDocument.body.innerHTML) { + return Promise.resolve(); + } else { + return new Promise((resolve, reject) => { + iframe.addEventListener('load', resolve); + iframe.addEventListener('error', reject); + }); + } +} + +function dispatchFocus(eventName, elem) { + let event = new FocusEvent(eventName, {relatedTarget: elem}); + elem.dispatchEvent(event); +} + +// The replaced methods sometimes fail in unit-testing setups, possibly due to the very short time intervals involved. +// The replacements suffice to trigger the same effects. +function upgradeFocus(elem) { + elem.blur = () => { + dispatchFocus('blur', elem); + } + + elem.focus = () => { + document.activeElement?.blur(); + dispatchFocus('focus', elem); + } +} + +describe.only('app/browser: ContextManager', function () { + this.timeout(__karma__.config.args.find((arg) => arg.type == "timeouts").standard); + + let contextManager; + + beforeEach(async () => { + // Loads a common fixture and ensures all relevant elements are attached. + fixture.setBase('fixtures'); + fixture.load("a-bit-of-everything.html"); + + // Note: iframes require additional time to resolve. + await promiseForIframeLoad(document.getElementById('iframe')); + + // Give the design-mode iframe a bit of time to set itself up properly. + // Note: it is thus important that whatever sends the `install` command has also + // alloted a brief window of time like this as well. + await timedPromise(20); + + // Load the page fully before we init ContextManager. + // Note: we provide an incomplete 'mock' of BrowserConfiguration here. + contextManager = new ContextManager({ + // Needed during keyboard-loading. + deferForInitialization: Promise.resolve(), + hostDevice: TEST_PHYSICAL_DEVICE, + attachType: 'auto', + // signalUser may be relevant for some tests. + }, () => new LegacyEventEmitter()); + + contextManager.configure({ + keyboardCache: new StubAndKeyboardCache(/* keyboard loader currently unset */), + predictionContext: { + // we're dummying this one out. + resetContext: () => {} + }, + resetContext: () => {} + }); + + // Allows us to bypass some funky unit-testing focus/blur issues by synthetically triggering the underlying events. + // Doesn't seem necessary for standard use... just when unit testing. (Not sure why.) + contextManager.page.on('enabled', (elem) => { + // will need better handling for design-mode iframes and content-editables, probably. + upgradeFocus(elem); + }); + + // Pre-attaches to the text fixture's elements. + contextManager.initialize(); + }); + + afterEach(() => { + // The main reason we set `ContextManager` in `beforeEach` - to make cleanup after + // each test round much simpler to maintain. + contextManager?.shutdown(); + contextManager = null; + + fixture.cleanup(); + }); + + describe('focus management', () => { + it('initializes with no target active', () => { + assert.isNotOk(contextManager.activeTarget); + }); + + it('no active -> input.focus()', () => { + const input = document.getElementById('input'); + dispatchFocus('focus', input); + + // todo: set stub for changedtarget, verify + + assert.equal(contextManager.activeTarget?.getElement(), input); + }); + + it('input.focus() -> input.blur() -> textarea.focus()', () => { + const input = document.getElementById('input'); + dispatchFocus('focus', input); + + dispatchFocus('blur', input); + assert.equal(contextManager.activeTarget?.getElement(), null); + + const textarea = document.getElementById('textarea'); + dispatchFocus('focus', textarea); + + // todo: set stub for changedtarget, verify + + assert.equal(contextManager.activeTarget?.getElement(), textarea); + }); + + it('input.focus() -> input.blur() [no focus maintenance] -> restore => input', () => { + const input = document.getElementById('input'); + dispatchFocus('focus', input); + dispatchFocus('blur', input); + + assert.equal(contextManager.activeTarget?.getElement(), null); + + contextManager.restoreLastActiveTarget(); + + assert.equal(contextManager.activeTarget?.getElement(), input); + + // todo: set stub for changedtarget, verify + }); + + it('input.focus() -> input.blur() [w/ focus maintenance] -> restore => input', () => { + const input = document.getElementById('input'); + dispatchFocus('focus', input); + + contextManager.focusAssistant.maintainingFocus = true; + dispatchFocus('blur', input); + + // b/c is 'maintained' + assert.equal(contextManager.activeTarget?.getElement(), input); + + // THIS block (of 3 lines) should probably be its own, separate test. + contextManager.focusAssistant.maintainingFocus = false; + assert.equal(contextManager.activeTarget?.getElement(), null); + contextManager.focusAssistant.maintainingFocus = true; + // end THIS block. + + contextManager.restoreLastActiveTarget(); + contextManager.focusAssistant.maintainingFocus = true; + // TODO: assert that no event was fired - we never changed target. + // - yes, even if using THIS block... but that's more a longstanding bug there. + + assert.equal(contextManager.activeTarget?.getElement(), input); + + // todo: set stub for changedtarget, verify + // + }); + + it('input.blur() [w/ focus maintenance] -> restore [w/ restoring] => input', () => { + const input = document.getElementById('input'); + dispatchFocus('focus', input); + + contextManager.focusAssistant.maintainingFocus = true; + dispatchFocus('blur', input); + assert.equal(contextManager.activeTarget?.getElement(), input); + + contextManager.focusAssistant.maintainingFocus = false; + assert.equal(contextManager.activeTarget?.getElement(), null); + + contextManager.focusAssistant.restoringFocus = true; + // Original implementation assumes `.focus()` will work normally. This often + // doesn't work nicely during unit tests, though. + contextManager.restoreLastActiveTarget(); + + assert.equal(contextManager.activeTarget?.getElement(), input); + + // todo: set stub for changedtarget, verify + // Verify that no 'changedtarget' event happens for the `restore` call. + }); + }); + + describe('keyboard management', () => { + it('initializes in global-keyboard mode', () => { + assert.isNotOk(contextManager.keyboardTarget); + }); + }); +}); \ No newline at end of file -- GitLab From 09b8b0f6abb8c355b2ffe93a22e7dba5051780fe Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 3 May 2023 09:03:50 +0700 Subject: [PATCH 168/386] chore(web): more concise test names --- .../auto/dom/cases/browser/contextManager.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index fc068f79b8..4469e98444 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -104,11 +104,11 @@ describe.only('app/browser: ContextManager', function () { }); describe('focus management', () => { - it('initializes with no target active', () => { + it('initial state: null', () => { assert.isNotOk(contextManager.activeTarget); }); - it('no active -> input.focus()', () => { + it('change: null -> input', () => { const input = document.getElementById('input'); dispatchFocus('focus', input); @@ -117,7 +117,18 @@ describe.only('app/browser: ContextManager', function () { assert.equal(contextManager.activeTarget?.getElement(), input); }); - it('input.focus() -> input.blur() -> textarea.focus()', () => { + it('change: input -> null', () => { + // Setup: from prior test + const input = document.getElementById('input'); + dispatchFocus('focus', input); + assert.equal(contextManager.activeTarget?.getElement(), input); + + // actual test + dispatchFocus('blur', input); + assert.equal(contextManager.activeTarget?.getElement(), undefined); + }); + + it('change: input -> textarea', () => { const input = document.getElementById('input'); dispatchFocus('focus', input); @@ -132,7 +143,7 @@ describe.only('app/browser: ContextManager', function () { assert.equal(contextManager.activeTarget?.getElement(), textarea); }); - it('input.focus() -> input.blur() [no focus maintenance] -> restore => input', () => { + it('restoration: input (no flags set)', () => { const input = document.getElementById('input'); dispatchFocus('focus', input); dispatchFocus('blur', input); @@ -146,7 +157,7 @@ describe.only('app/browser: ContextManager', function () { // todo: set stub for changedtarget, verify }); - it('input.focus() -> input.blur() [w/ focus maintenance] -> restore => input', () => { + it('restoration: input (`maintaining`)', () => { const input = document.getElementById('input'); dispatchFocus('focus', input); @@ -173,7 +184,7 @@ describe.only('app/browser: ContextManager', function () { // }); - it('input.blur() [w/ focus maintenance] -> restore [w/ restoring] => input', () => { + it('restoration: input (`restoring`)', () => { const input = document.getElementById('input'); dispatchFocus('focus', input); -- GitLab From 8d9aebf13fd796338f42d32d556f7a489664af29 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 3 May 2023 09:22:29 +0700 Subject: [PATCH 169/386] feat(web): adds event-related checks to some recent unit tests --- .../auto/dom/cases/browser/contextManager.js | 85 ++++++++++++++----- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 4469e98444..b1cf778edd 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -3,6 +3,7 @@ import { LegacyEventEmitter } from '/@keymanapp/keyman/build/engine/events/lib/i import { StubAndKeyboardCache } from '/@keymanapp/keyman/build/engine/package-cache/lib/index.mjs'; import timedPromise from '../../timedPromise.mjs'; +import sinon from '/node_modules/sinon/pkg/sinon-esm.js'; const assert = chai.assert; @@ -109,79 +110,121 @@ describe.only('app/browser: ContextManager', function () { }); it('change: null -> input', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + const input = document.getElementById('input'); dispatchFocus('focus', input); - // todo: set stub for changedtarget, verify + assert.equal(contextManager.activeTarget?.getElement(), input, ".activeTarget not updated when element gained focus"); - assert.equal(contextManager.activeTarget?.getElement(), input); + // Check our expectations re: the `targetchange` event. + assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); + const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(outputTarget.getElement(), input, '.activeTarget does not match the newly-focused element'); }); it('change: input -> null', () => { // Setup: from prior test + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + const input = document.getElementById('input'); dispatchFocus('focus', input); assert.equal(contextManager.activeTarget?.getElement(), input); // actual test dispatchFocus('blur', input); - assert.equal(contextManager.activeTarget?.getElement(), undefined); + assert.equal(contextManager.activeTarget, null, '.activeTarget not updated when element lost focus'); + + // Check our expectations re: the `targetchange` event. + assert.isTrue(targetchange.calledTwice, 'targetchange event not raised'); + const outputTarget = targetchange.secondCall.args[0]; // Should be null, since we lost focus. + assert.equal(outputTarget, null, 'targetchange event did not indicate clearing of .activeTarget'); }); it('change: input -> textarea', () => { + // Setup: from prior test + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + const input = document.getElementById('input'); dispatchFocus('focus', input); dispatchFocus('blur', input); assert.equal(contextManager.activeTarget?.getElement(), null); + // And now the new stuff. const textarea = document.getElementById('textarea'); dispatchFocus('focus', textarea); - // todo: set stub for changedtarget, verify + assert.equal(contextManager.activeTarget?.getElement(), textarea, ".activeTarget not updated when element gained focus"); - assert.equal(contextManager.activeTarget?.getElement(), textarea); + // Check our expectations re: the `targetchange` event. + assert.isTrue(targetchange.calledThrice, 'targetchange event not raised'); + const outputTarget = targetchange.thirdCall.args[0]; // Should be an `Input` instance. + assert.equal(outputTarget.getElement(), textarea, '.activeTarget does not match the newly-focused element'); }); it('restoration: input (no flags set)', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + const input = document.getElementById('input'); - dispatchFocus('focus', input); - dispatchFocus('blur', input); + dispatchFocus('focus', input); // 1 + dispatchFocus('blur', input); // 2 assert.equal(contextManager.activeTarget?.getElement(), null); - contextManager.restoreLastActiveTarget(); + contextManager.restoreLastActiveTarget(); // 3 assert.equal(contextManager.activeTarget?.getElement(), input); - // todo: set stub for changedtarget, verify + assert.isTrue(targetchange.calledThrice); }); it('restoration: input (`maintaining`)', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + const input = document.getElementById('input'); - dispatchFocus('focus', input); + dispatchFocus('focus', input); // 1 contextManager.focusAssistant.maintainingFocus = true; - dispatchFocus('blur', input); + dispatchFocus('blur', input); // ignored + + // assert.isTrue(targetchange.calledOnce, 'targetchange called on blur during maintaining state'); // b/c is 'maintained' assert.equal(contextManager.activeTarget?.getElement(), input); - // THIS block (of 3 lines) should probably be its own, separate test. - contextManager.focusAssistant.maintainingFocus = false; - assert.equal(contextManager.activeTarget?.getElement(), null); - contextManager.focusAssistant.maintainingFocus = true; - // end THIS block. - contextManager.restoreLastActiveTarget(); + + assert.equal(contextManager.activeTarget?.getElement(), input); + + // Since we never 'lost' focus due to the 'maintaining' state, we should only + // have the initial 'targetchange' raise. + // assert.isTrue(targetchange.calledOnce, 'targetchange called during restoration of maintained state'); + }); + + it('loss: input (on clear of `maintaining`)', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + + const input = document.getElementById('input'); + dispatchFocus('focus', input); // 1 + contextManager.focusAssistant.maintainingFocus = true; - // TODO: assert that no event was fired - we never changed target. - // - yes, even if using THIS block... but that's more a longstanding bug there. + dispatchFocus('blur', input); // ignored + // b/c is 'maintained' assert.equal(contextManager.activeTarget?.getElement(), input); - // todo: set stub for changedtarget, verify - // + contextManager.focusAssistant.maintainingFocus = false; + assert.equal(contextManager.activeTarget?.getElement(), null); + contextManager.focusAssistant.maintainingFocus = true; + + // assert.isTrue(targetchange.calledTwice); }); it('restoration: input (`restoring`)', () => { -- GitLab From 3200649912de269319eed2a0feb7c1c1861cdadd Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 3 May 2023 09:46:59 +0700 Subject: [PATCH 170/386] fix(web): .activeTarget, event interactions with focus states --- .../app/browser/src/context/focusAssistant.ts | 34 ++++++++++++--- web/src/app/browser/src/contextManager.ts | 16 ++++++- .../auto/dom/cases/browser/contextManager.js | 43 +++++++++++-------- 3 files changed, 68 insertions(+), 25 deletions(-) diff --git a/web/src/app/browser/src/context/focusAssistant.ts b/web/src/app/browser/src/context/focusAssistant.ts index 8e83e12ce9..38673a0e71 100644 --- a/web/src/app/browser/src/context/focusAssistant.ts +++ b/web/src/app/browser/src/context/focusAssistant.ts @@ -1,3 +1,5 @@ +import EventEmitter from "eventemitter3"; + /** * The return object documented for * https://help.keyman.com/developer/engine/web/16.0/reference/core/getUIState. @@ -22,13 +24,23 @@ export class FocusStateAPIObject { } } +interface EventMap { + /** + * Called immediately after the `maintainingFocus` flag is cleared. + * @returns + */ + 'maintainingend': () => void; +} + // Formerly handled under "UIManager". /** * This class provides fields and methods useful for assisting context management. Control focus (and * thus, activation of the corresponding OutputTarget) should not be lost to non-context components of * KMW, such as the OSK or a keyboard selector. */ -export class FocusAssistant { +export class FocusAssistant extends EventEmitter { + private _maintainingFocus: boolean = false; // ActivatingKeymanWebUI - Does the OSK have active focus / an active interaction? + /* * Long-term idea here: about all of the relevant OSK events that would interact with this have "enter" and * "leave" variants - we could take a stack of `Promise`s. On a `Promise` fulfillment, remove it from the @@ -43,9 +55,24 @@ export class FocusAssistant { * * While the flag is active, the context-management system should not deactivate an OutputTarget upon * its element's loss of focus within the page unless setting a different OutputTarget as active. + * + * TODO: (potential) Future enhancement - this should not be possible to set if there is no currently-active + * context target to maintain. */ // Formerly `isActivating`. - maintainingFocus: boolean = false; // ActivatingKeymanWebUI - Does the OSK have active focus / an active interaction? + public get maintainingFocus(): boolean { + return this._maintainingFocus; + } + + public set maintainingFocus(value: boolean) { + const priorValue = this._maintainingFocus; + this._maintainingFocus = value; + + // Needed to properly update .activeTarget upon loss of maintaining-focus state. + if(priorValue && !value) { + this.emit('maintainingend'); + } + } /* * Long-term idea here: as (aside from OSK title/resize bar interactions) it's always used to actively @@ -102,9 +129,6 @@ export class FocusAssistant { */ focusTimer: number; - constructor() { - } - /** * Function getUIState * Scope Public diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index ccda09499e..9c5456613e 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -61,6 +61,13 @@ export default class ContextManager extends ContextManagerBase { + // Basically, if the maintaining state were the reason we still had an `activeTarget`... + if(!this.activeTarget && this.mostRecentTarget) { + this.emit('targetchange', this.activeTarget); + } + }); } get apiEvents(): LegacyEventEmitter { @@ -163,6 +170,10 @@ export default class ContextManager extends ContextManagerBase { + /* + * Assumption: the maintainingFocus flag may only be set when there is a current target. + * This is not enforced proactively at present, but the assumption should hold. (2023-05-03) + */ const maintainingFocus = this.focusAssistant.maintainingFocus; return this.currentTarget || (maintainingFocus ? this.mostRecentTarget : null); } @@ -185,6 +196,7 @@ export default class ContextManager extends ContextManagerBase) { const previousTarget = this.mostRecentTarget; + const originalTarget = this.activeTarget; // may differ, depending on focus state. // We condition on 'priorElement' below as a check to allow KMW to set a default active keyboard. let hadRecentElement = !!previousTarget; @@ -213,7 +225,9 @@ export default class ContextManager extends ContextManagerBase { @@ -188,14 +189,15 @@ describe.only('app/browser: ContextManager', function () { contextManager.on('targetchange', targetchange); const input = document.getElementById('input'); - dispatchFocus('focus', input); // 1 + dispatchFocus('focus', input); + assert.isTrue(targetchange.calledOnce); contextManager.focusAssistant.maintainingFocus = true; dispatchFocus('blur', input); // ignored - - // assert.isTrue(targetchange.calledOnce, 'targetchange called on blur during maintaining state'); + assert.isTrue(targetchange.calledOnce); // b/c is 'maintained' + assert.isTrue(targetchange.calledOnce, 'targetchange called on blur during maintaining state'); assert.equal(contextManager.activeTarget?.getElement(), input); contextManager.restoreLastActiveTarget(); @@ -204,7 +206,7 @@ describe.only('app/browser: ContextManager', function () { // Since we never 'lost' focus due to the 'maintaining' state, we should only // have the initial 'targetchange' raise. - // assert.isTrue(targetchange.calledOnce, 'targetchange called during restoration of maintained state'); + assert.isTrue(targetchange.calledOnce, 'targetchange called during restoration of maintained state'); }); it('loss: input (on clear of `maintaining`)', () => { @@ -212,41 +214,44 @@ describe.only('app/browser: ContextManager', function () { contextManager.on('targetchange', targetchange); const input = document.getElementById('input'); - dispatchFocus('focus', input); // 1 + dispatchFocus('focus', input); + assert.isTrue(targetchange.calledOnce); contextManager.focusAssistant.maintainingFocus = true; dispatchFocus('blur', input); // ignored // b/c is 'maintained' + assert.isTrue(targetchange.calledOnce); assert.equal(contextManager.activeTarget?.getElement(), input); contextManager.focusAssistant.maintainingFocus = false; - assert.equal(contextManager.activeTarget?.getElement(), null); - contextManager.focusAssistant.maintainingFocus = true; + assert.isTrue(targetchange.calledTwice); - // assert.isTrue(targetchange.calledTwice); + assert.equal(contextManager.activeTarget?.getElement(), null); }); it('restoration: input (`restoring`)', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + const input = document.getElementById('input'); - dispatchFocus('focus', input); + dispatchFocus('focus', input); // 1 + assert.isTrue(targetchange.calledOnce); contextManager.focusAssistant.maintainingFocus = true; dispatchFocus('blur', input); + assert.isTrue(targetchange.calledOnce); // 'maintaining' state assert.equal(contextManager.activeTarget?.getElement(), input); contextManager.focusAssistant.maintainingFocus = false; + assert.isTrue(targetchange.calledTwice); assert.equal(contextManager.activeTarget?.getElement(), null); contextManager.focusAssistant.restoringFocus = true; - // Original implementation assumes `.focus()` will work normally. This often - // doesn't work nicely during unit tests, though. contextManager.restoreLastActiveTarget(); + assert.isTrue(targetchange.calledThrice); assert.equal(contextManager.activeTarget?.getElement(), input); - - // todo: set stub for changedtarget, verify - // Verify that no 'changedtarget' event happens for the `restore` call. }); }); -- GitLab From 6a41ca95a9a6ce50d7e2f84457c09012bdc356e4 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 3 May 2023 10:09:14 +0700 Subject: [PATCH 171/386] feat(web): forgetActiveElement unit test, related fixes --- web/src/app/browser/src/contextManager.ts | 18 ++++++++--- .../auto/dom/cases/browser/contextManager.js | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 9c5456613e..ed14b7d938 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -191,6 +191,11 @@ export default class ContextManager extends ContextManagerBase { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + + const input = document.getElementById('input'); + dispatchFocus('focus', input); + assert.isTrue(targetchange.calledOnce); + + contextManager.focusAssistant.maintainingFocus = true; + contextManager.focusAssistant.restoringFocus = true; + contextManager.forgetActiveTarget(); + // The 'forget' operation is **aggressive**. Perma-forget. + assert.isNotOk(contextManager.lastActiveTarget); + + assert.isTrue(targetchange.calledTwice); + assert.equal(contextManager.activeTarget?.getElement(), null); + // Again, the 'forget' operation is **aggressive**. Clears all focus-maintenance states. + // After all, there's no longer any prior target to maintain or restore - it's forgotten. + assert.equal(contextManager.focusAssistant.maintainingFocus, false); + assert.equal(contextManager.focusAssistant.restoringFocus, false); + + dispatchFocus('blur', input); // Should be 100% ignored + assert.isTrue(targetchange.calledTwice); // there should be no effect. + assert.equal(contextManager.activeTarget?.getElement(), null); + // If we aren't careful, we can accidentally 'unforget' the element here! + assert.isNotOk(contextManager.lastActiveTarget, "post-forget target blur restored .lastActiveTarget"); + + contextManager.restoreLastActiveTarget(); + assert.isTrue(targetchange.calledTwice); // there should be no effect. + assert.equal(contextManager.activeTarget?.getElement(), null); + }); + it('restoration: input (`maintaining`)', () => { const targetchange = sinon.fake(); contextManager.on('targetchange', targetchange); -- GitLab From b03a8c1832c04cb064d044d17b5d2f51a967f6b0 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 3 May 2023 12:37:51 +0700 Subject: [PATCH 172/386] feat(web): adds some active-keyboard management unit tests --- web/src/engine/main/src/contextManagerBase.ts | 2 +- .../auto/dom/cases/browser/contextManager.js | 194 +++++++++++++++++- web/src/test/auto/dom/kbdLoader.mjs | 9 + 3 files changed, 201 insertions(+), 4 deletions(-) diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index 13bf33091a..d939f037a5 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -313,7 +313,7 @@ export abstract class ContextManagerBase // `beforeKeyboardChange` - first call this.emit('beforekeyboardchange', requestedStub); - const defermentPromise = this.engineConfig.deferForInitialization.corePromise.then(() => { + const defermentPromise = this.engineConfig.deferForInitialization.then(() => { // Provide a Promise for completion of the async load process. const completionPromise = new ManagedPromise(); this.emit('keyboardasyncload', requestedStub, completionPromise.corePromise); diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index e96899e178..cdcc47f93b 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -1,6 +1,10 @@ import { ContextManager } from '/@keymanapp/keyman/build/app/browser/lib/index.mjs'; import { LegacyEventEmitter } from '/@keymanapp/keyman/build/engine/events/lib/index.mjs'; -import { StubAndKeyboardCache } from '/@keymanapp/keyman/build/engine/package-cache/lib/index.mjs'; +import { StubAndKeyboardCache, toPrefixedKeyboardId as prefixed } from '/@keymanapp/keyman/build/engine/package-cache/lib/index.mjs'; + +import { KeyboardHarness, MinimalKeymanGlobal } from '/@keymanapp/keyboard-processor/build/lib/index.mjs'; +import { DOMKeyboardLoader } from '/@keymanapp/keyboard-processor/build/lib/dom-keyboard-loader.mjs'; +import { loadKeyboardsFromStubs } from '../../kbdLoader.mjs'; import timedPromise from '../../timedPromise.mjs'; import sinon from '/node_modules/sinon/pkg/sinon-esm.js'; @@ -14,6 +18,37 @@ const TEST_PHYSICAL_DEVICE = { touchable: false }; +function assertPromiseResolved(promise, timeout) { + // Ensure timeout is initialized to a numeric value. + // If undefined or 0, expects instant resolution. + timeout ||= 0; + timeout >= 0 ? timeout : 0; + + return new Promise((resolve, reject) => { + let fulfilled = false; + + promise.then(() => { + if(!fulfilled) { + // resolve(); + resolve(); + fulfilled = true; + } + }).catch((err) => { + if(!fulfilled) { + reject(err); + fulfilled = true; + } + }); + + timedPromise(timeout).then(() => { + if(!fulfilled) { + reject(new Error("The Promise failed to reach fulfillment during the allotted time")); + fulfilled = true; + } + }); + }); +} + function promiseForIframeLoad(iframe) { // Chrome makes this first case tricky - it initializes all iframes with a 'complete' about:blank // before loading the actual href. (https://stackoverflow.com/a/36155560) @@ -50,8 +85,17 @@ function upgradeFocus(elem) { describe.only('app/browser: ContextManager', function () { this.timeout(__karma__.config.args.find((arg) => arg.type == "timeouts").standard); + /** + * Holds a test-specific instance of ContextManager. + */ let contextManager; + /** + * Holds the test-specific instance of the stub & keyboard cache used by the + * current test's `contextManager`. + */ + let keyboardCache; + beforeEach(async () => { // Loads a common fixture and ensures all relevant elements are attached. fixture.setBase('fixtures'); @@ -70,13 +114,17 @@ describe.only('app/browser: ContextManager', function () { contextManager = new ContextManager({ // Needed during keyboard-loading. deferForInitialization: Promise.resolve(), - hostDevice: TEST_PHYSICAL_DEVICE, + hostDevice: {... TEST_PHYSICAL_DEVICE}, attachType: 'auto', // signalUser may be relevant for some tests. }, () => new LegacyEventEmitter()); + // Needed for the keyboard tests later. + const keyboardLoader = new DOMKeyboardLoader(new KeyboardHarness(window, MinimalKeymanGlobal)); + keyboardCache = new StubAndKeyboardCache(keyboardLoader); + contextManager.configure({ - keyboardCache: new StubAndKeyboardCache(/* keyboard loader currently unset */), + keyboardCache: keyboardCache, predictionContext: { // we're dummying this one out. resetContext: () => {} @@ -100,10 +148,12 @@ describe.only('app/browser: ContextManager', function () { // each test round much simpler to maintain. contextManager?.shutdown(); contextManager = null; + keyboardCache = null; fixture.cleanup(); }); + // ---------------------------- Start of suite 1 ------------------------------- describe('focus management', () => { it('initial state: null', () => { assert.isNotOk(contextManager.activeTarget); @@ -287,9 +337,147 @@ describe.only('app/browser: ContextManager', function () { }); }); + // ------------------------- Second suite: keyboard-related tests -------------------------- describe('keyboard management', () => { + let apiStubs; + + /** + * Preloaded versions of the keyboards useful for bypassing loading times / allowing synchronicity + * within individual test definitions. + */ + let KEYBOARDS; + + before(async () => { + // Defined here just in case they move later; it'll trigger a failed test on 'before', rather + // than crashing while setting up the tests. + apiStubs = [ + __json__['/keyboards/khmer_angkor'], + __json__['/keyboards/lao_2008_basic'], + __json__['/keyboards/test_chirality'], + __json__['/keyboards/test_deadkeys'] + ]; + + KEYBOARDS = await loadKeyboardsFromStubs(apiStubs, '/'); + }); + + beforeEach(() => { + // Since `contextManager` and `keyboardCache` are replaced `beforeEach` test, we need to prep + // the stubs here. They'll all be available, as stubs, within the cache. + for(let key in KEYBOARDS) { + keyboardCache.addStub(KEYBOARDS[key].metadata); + } + }); + + // At the start of each test, preloaded versions of the keyboards are available BUT NOT in the cache, + // while their stubs for all are fully preloaded and within the cache. Test assertions against + // test keyboard objects themselves are greatly facilitated by having known, preloaded instances. + it('initializes in global-keyboard mode', () => { assert.isNotOk(contextManager.keyboardTarget); }); + + it('activate: without .activeTarget, null -> null', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardchange', keyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + + await contextManager.activateKeyboard('', ''); + // When no keyboard is set, the keyboard-metadata pair object itself should be null. + assert.equal(contextManager.activeKeyboard, null); + // Even though it's to effectively the same keyboard, we reload it (in case its stub + // has been replaced) + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.equal(keyboardchange.firstCall.args[0], null); + }); + + it('activate: without .activeTarget, preloaded keyboard', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardchange', keyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + + await contextManager.activateKeyboard('khmer_angkor', 'km'); + // The instance itself may differ, but the .keyboard and .metadata entries will + // be matching instances thanks to preloading. + assert.deepEqual(contextManager.activeKeyboard, KEYBOARDS.khmer_angkor); + + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.deepEqual(keyboardchange.firstCall.args[0], KEYBOARDS.khmer_angkor); + assert.strictEqual(keyboardchange.firstCall.args[0], contextManager.activeKeyboard); + }); + + it('activate: without .activeTarget, loads keyboard', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // No preloading - `contextManager` should be able to handle it so long as + // a matching stub already exists. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + // The instance itself may differ, but the .keyboard and .metadata entries will + // be matching instances thanks to preloading. + assert.isTrue(beforekeyboardchange.calledTwice); // Matches pre-modularized KMW behavior. + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.calledOnce); + assert.equal(contextManager.activeKeyboard.metadata.id, 'khmer_angkor'); + assert.equal(contextManager.activeKeyboard.keyboard.id, prefixed('khmer_angkor')); + assert.equal(contextManager.activeKeyboard.metadata.langId, 'km'); + + await assertPromiseResolved(keyboardasyncload.firstCall.args[1]); + }); + + it('activate: without .activeTarget, missing definition (desktop)', async () => { + // Setup + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + // Actual test + await contextManager.activateKeyboard('not_defined', 'n/a'); + + // Fallback behavior: deactivate the keyboard entirely (if on desktop) + assert.equal(contextManager.activeKeyboard, null); + }); + + it('activate: without .activeTarget, missing definition (touch)', async () => { + // Hacky override - activate touch mode. + contextManager.engineConfig.hostDevice.touchable = true; + + // Setup + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + // Actual test + await contextManager.activateKeyboard('not_defined', 'n/a'); + + // Fallback behavior: activate the first registered stub (if on touch) + assert.equal(contextManager.activeKeyboard.metadata.id, 'khmer_angkor'); + }); + + it('reactivate: re-requests the already-active keyboard', async () => { + // Setup + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + // Actual test + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + assert.equal(contextManager.activeKeyboard.metadata.id, 'lao_2008_basic'); + assert.isTrue(true); + }); }); }); \ No newline at end of file diff --git a/web/src/test/auto/dom/kbdLoader.mjs b/web/src/test/auto/dom/kbdLoader.mjs index bae9385e7d..152a6c5274 100644 --- a/web/src/test/auto/dom/kbdLoader.mjs +++ b/web/src/test/auto/dom/kbdLoader.mjs @@ -19,6 +19,11 @@ export function loadKeyboardsFromStubs(apiStubs, baseDir) { let keyboards = {}; let priorPromise = Promise.resolve(); for(let stub of apiStubs) { + // We are keeping this strictly sequential because we don't have sandboxed + // loading yet; lack of sandboxing means that all loading keyboards compete + // for the same harness endpoint when loading. + // + // tl;dr: because otherwise, race condition. priorPromise = priorPromise.then(() => { // Adds closure to capture 'id' for async completion use. let overwriteLoader = (id, path) => { @@ -31,6 +36,10 @@ export function loadKeyboardsFromStubs(apiStubs, baseDir) { keyboard: overwriteLoader(stub.id, baseDir + stub.filename), metadata: new KeyboardProperties(stub) }; + + // Because tests closer to top-level KMW will expect a filename entry. + keyboards[stub.id].metadata.filename = baseDir + stub.filename; + return keyboards[stub.id].keyboard; }); } -- GitLab From 717070dcccd80fd93362257b3d275d2003b406c9 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 3 May 2023 12:40:45 +0700 Subject: [PATCH 173/386] feat(web): fleshes out one of the incomplete unit tests --- .../test/auto/dom/cases/browser/contextManager.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index cdcc47f93b..7d8c860182 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -469,6 +469,13 @@ describe.only('app/browser: ContextManager', function () { }); it('reactivate: re-requests the already-active keyboard', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + // Setup keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); await contextManager.activateKeyboard('lao_2008_basic', 'lo'); @@ -477,7 +484,13 @@ describe.only('app/browser: ContextManager', function () { await contextManager.activateKeyboard('lao_2008_basic', 'lo'); assert.equal(contextManager.activeKeyboard.metadata.id, 'lao_2008_basic'); - assert.isTrue(true); + + // Even though it's to effectively the same keyboard, we reload it (in case its stub + // has been replaced) + assert.isTrue(beforekeyboardchange.calledTwice); + assert.isTrue(keyboardchange.calledTwice); + assert.isTrue(keyboardasyncload.notCalled); + assert.deepEqual(keyboardchange.secondCall.args[0], keyboardchange.firstCall.args[0]); }); }); }); \ No newline at end of file -- GitLab From 08bbcf4393b3b2cb0cf1df0c4ef9b607ce327226 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 3 May 2023 16:28:37 +0700 Subject: [PATCH 174/386] feat(web): error on activating without a stub, fleshes out related unit tests --- web/src/app/browser/src/contextManager.ts | 40 +++++-- web/src/app/webview/src/contextManager.ts | 17 ++- web/src/engine/main/src/contextManagerBase.ts | 27 +++-- .../auto/dom/cases/browser/contextManager.js | 108 +++++++++++++++++- 4 files changed, 163 insertions(+), 29 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index ed14b7d938..0315b5ba64 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -338,10 +338,32 @@ export default class ContextManager extends ContextManagerBase { saveCookie ||= false; const originalKeyboardTarget = this.keyboardTarget; + // Must do here b/c of fallback behavior stuff defined below. + // If the default keyboard is requested, load that. May vary based on form-factor, which is + // part of what .getFallbackCodes() handles. + if(!keyboardId) { + keyboardId = this.getFallbackCodes().id; + languageCode = this.getFallbackCodes().langId; + } + try { let result = await super.activateKeyboard(keyboardId, languageCode, saveCookie); @@ -365,17 +387,10 @@ export default class ContextManager extends ContextManagerBase { // Make sure we don't infinite-recursion should the deactivate somehow fail. - if(this.engineConfig.hostDevice.touchable) { - // Fallback behavior - if on a touch device, we need to keep a keyboard visible. - const defaultStub = this.keyboardCache.defaultStub; - if(defaultStub.id != keyboardId || defaultStub.langId != languageCode) { - await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); - } // else "We already failed, so give up." - } else { - // Fallback behavior - if on a desktop device, the user still has a physical keyboard. - // Just clear out the active keyboard & OSK. - await this.activateKeyboard('', '', false).catch(() => {}); - } + const fallbackCodes = this.getFallbackCodes(); + if((fallbackCodes.id != keyboardId)) { + await this.activateKeyboard(fallbackCodes.id, fallbackCodes.langId, true).catch(() => {}); + } // else "We already failed, so give up." } this.engineConfig.signalUser?.wait(); // clear the wait message box, either way. @@ -394,9 +409,10 @@ export default class ContextManager extends ContextManagerBase { + // If the default keyboard is requested, load that. May vary based on form-factor, which is + // part of what .getFallbackCodes() handles. + if(!keyboardId) { + keyboardId = this.getFallbackCodes().id; + languageCode = this.getFallbackCodes().langId; + } + try { return await super.activateKeyboard(keyboardId, languageCode, saveCookie); } catch(err) { // Fallback behavior - we're embedded in a touch-device's webview, so we need to keep a keyboard visible. - const defaultStub = this.keyboardCache.defaultStub; - if(defaultStub.id != keyboardId || defaultStub.langId != languageCode) { - await this.activateKeyboard(defaultStub.id, defaultStub.langId, true).catch(() => {}); + const fallbackCodes = this.getFallbackCodes(); + if(fallbackCodes.id != keyboardId) { + await this.activateKeyboard(fallbackCodes.id, fallbackCodes.langId, true).catch(() => {}); } // else "We already failed, so give up." throw err; // since the consuming method / API-caller may want to do its own error-handling. diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index d939f037a5..abe78495ac 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -191,6 +191,11 @@ export abstract class ContextManagerBase } } + protected abstract getFallbackCodes(): { + id: string, + langId: string + }; + /** * Change active keyboard to keyboard selected by (internal) name and language code * @@ -206,7 +211,11 @@ export abstract class ContextManagerBase * @returns */ public async activateKeyboard(keyboardId: string, languageCode?: string, saveCookie?: boolean): Promise { + // TODO: relocate default keyboard behavior here once we can also move core error handling for + // unfound stubs here. + const activatingKeyboard = this.prepareKeyboardForActivation(keyboardId, languageCode); + const originalKeyboardTarget = this.keyboardTarget; const keyboard = await activatingKeyboard.keyboard; @@ -278,15 +287,15 @@ export abstract class ContextManagerBase languageCode == ''; } - // Mobile device addition: force selection of the first keyboard if none set - if(this.engineConfig.hostDevice.touchable && !requestedStub) { - // Pick the oldest-registered stub as default. - requestedStub = this.keyboardCache.defaultStub; - } else if(!requestedStub) { - return { - keyboard: Promise.resolve(null), - metadata: null - }; + if(!requestedStub) { + if(keyboardId) { + throw new Error("No matching stub has been registered."); + } else { + return { + keyboard: Promise.resolve(null), + metadata: null + } + } } // Check if current keyboard matches requested keyboard, but not (necessarily) stub diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 7d8c860182..4d40ecadb1 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -82,6 +82,21 @@ function upgradeFocus(elem) { } } +async function blockConsoleAndAwait(asyncClosure) { + const originalConsole = window.console; + window.console = { + log: sinon.fake(), + warn: sinon.fake(), + error: sinon.fake() + } + + try { + await asyncClosure(); + } finally { + window.console = originalConsole; + } +} + describe.only('app/browser: ContextManager', function () { this.timeout(__karma__.config.args.find((arg) => arg.type == "timeouts").standard); @@ -96,7 +111,22 @@ describe.only('app/browser: ContextManager', function () { */ let keyboardCache; + let originalConsole; + + before(() => { + originalConsole = window.console; + }); + + after(() => { + window.console = originalConsole; + }) + beforeEach(async () => { + // const console = window.console = {}; + // console.log = () => {}; + // console.warn = () => {}; + // console.error = () => {}; + // Loads a common fixture and ensures all relevant elements are attached. fixture.setBase('fixtures'); fixture.load("a-bit-of-everything.html"); @@ -144,8 +174,15 @@ describe.only('app/browser: ContextManager', function () { }); afterEach(() => { + // Since certain user tests change this. + contextManager.engineConfig.hostDevice.touchable = false; + // The main reason we set `ContextManager` in `beforeEach` - to make cleanup after - // each test round much simpler to maintain. + // each test round much simpler to maintain. If not reset, the unit test stuff can + // collapse due to side-effects - certain elements only attach if `touchable == false`. + // + // ... I could probably just use an input element and textarea element fixture for those + // tests and be fine, rather than the full gamut. contextManager?.shutdown(); contextManager = null; keyboardCache = null; @@ -376,7 +413,7 @@ describe.only('app/browser: ContextManager', function () { assert.isNotOk(contextManager.keyboardTarget); }); - it('activate: without .activeTarget, null -> null', async () => { + it('activate: without .activeTarget, null -> null (desktop)', async () => { const beforekeyboardchange = sinon.fake(); const keyboardchange = sinon.fake(); const keyboardasyncload = sinon.fake(); @@ -395,6 +432,27 @@ describe.only('app/browser: ContextManager', function () { assert.equal(keyboardchange.firstCall.args[0], null); }); + it('activate: without .activeTarget, null -> null (touch)', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardchange', keyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + + // Hacky override - activate touch mode. + contextManager.engineConfig.hostDevice.touchable = true; + + await contextManager.activateKeyboard('', ''); + // When no keyboard is set, the keyboard-metadata pair object itself should be null. + assert.equal(contextManager.activeKeyboard?.metadata?.id, 'khmer_angkor'); + + assert.isTrue(beforekeyboardchange.calledTwice); // khmer_angkor is dynamically loaded. + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.calledOnce); // Again, dynamically loaded. + assert.equal(keyboardchange.firstCall.args[0].metadata.id, 'khmer_angkor'); + }); + it('activate: without .activeTarget, preloaded keyboard', async () => { const beforekeyboardchange = sinon.fake(); const keyboardchange = sinon.fake(); @@ -442,18 +500,45 @@ describe.only('app/browser: ContextManager', function () { }); it('activate: without .activeTarget, missing definition (desktop)', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + // Setup keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); await contextManager.activateKeyboard('lao_2008_basic', 'lo'); // Actual test - await contextManager.activateKeyboard('not_defined', 'n/a'); + try { + await blockConsoleAndAwait(() => contextManager.activateKeyboard('not_defined', 'n/a')); + assert.fail(); + } catch (err) { + // Good, an error surfaced. + // Could make assertions about the error? + } // Fallback behavior: deactivate the keyboard entirely (if on desktop) assert.equal(contextManager.activeKeyboard, null); + + // The two requests - initial setup, then to the default keyboard. No attempts + // are made for the erroneous keyboard because no matching stub could be found. + + assert.isTrue(beforekeyboardchange.calledTwice); // Requested twice. + assert.isTrue(keyboardchange.calledTwice); // Actually does change the keyboard twice + assert.isTrue(keyboardasyncload.notCalled); }); it('activate: without .activeTarget, missing definition (touch)', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + // Hacky override - activate touch mode. contextManager.engineConfig.hostDevice.touchable = true; @@ -462,10 +547,23 @@ describe.only('app/browser: ContextManager', function () { await contextManager.activateKeyboard('lao_2008_basic', 'lo'); // Actual test - await contextManager.activateKeyboard('not_defined', 'n/a'); + try { + await blockConsoleAndAwait(() => contextManager.activateKeyboard('not_defined', 'n/a')); + assert.fail(); + } catch (err) { + // Good, an error surfaced. + // Could make assertions about the error? + } // Fallback behavior: activate the first registered stub (if on touch) - assert.equal(contextManager.activeKeyboard.metadata.id, 'khmer_angkor'); + assert.equal(contextManager.activeKeyboard?.metadata.id, 'khmer_angkor'); + + // The two requests - initial setup, then to the default keyboard. No attempts + // are made for the erroneous keyboard because no matching stub could be found. + + assert.isTrue(beforekeyboardchange.calledThrice); // Requested three times - the latter two for `khmer_angkor` b/c async. + assert.isTrue(keyboardchange.calledTwice); // Actually does change the keyboard twice + assert.isTrue(keyboardasyncload.calledOnce); }); it('reactivate: re-requests the already-active keyboard', async () => { -- GitLab From f3a3522d107607ca042893331c9df416ac74a283 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 5 May 2023 12:34:53 +0700 Subject: [PATCH 175/386] feat(web): keyboard-load with focus-change interaction unit tests, related polish --- web/src/app/browser/src/contextManager.ts | 3 +- web/src/engine/main/src/contextManagerBase.ts | 5 + .../auto/dom/cases/browser/contextManager.js | 513 ++++++++++++------ 3 files changed, 343 insertions(+), 178 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 0315b5ba64..7fb54b5719 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -457,7 +457,8 @@ export default class ContextManager extends ContextManagerBase // TODO: relocate default keyboard behavior here once we can also move core error handling for // unfound stubs here. + // If there was a previous activation attempt set and still active for the specified keyboard target, + // cancel it. For exmaple, if the user selects a preloaded keyboard after having tried to select one + // still async-loading, we should go with the later setting - the preloaded one. + this.findAndPopActivation(this.keyboardTarget); + const activatingKeyboard = this.prepareKeyboardForActivation(keyboardId, languageCode); const originalKeyboardTarget = this.keyboardTarget; diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 4d40ecadb1..b95aa0cb97 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -97,6 +97,27 @@ async function blockConsoleAndAwait(asyncClosure) { } } +async function withDelayedFetching(keyboardLoader, time, closure) { + time || 0; + if(time < 0) { + time = 0; + } + + const originalLoad = keyboardLoader.loadKeyboardInternal; + + const fetchIntercept = async (...args) => { + const retVal = originalLoad.call(keyboardLoader, ...args); + keyboardLoader.loadKeyboardInternal = originalLoad; + + await timedPromise(time); + + return retVal; + } + + keyboardLoader.loadKeyboardInternal = fetchIntercept; + await closure(); +} + describe.only('app/browser: ContextManager', function () { this.timeout(__karma__.config.args.find((arg) => arg.type == "timeouts").standard); @@ -111,6 +132,12 @@ describe.only('app/browser: ContextManager', function () { */ let keyboardCache; + /** + * Holds the test-specific instance of the keyboard loader used by the current + * test's `keyboardCache`. + */ + let keyboardLoader; + let originalConsole; before(() => { @@ -150,7 +177,7 @@ describe.only('app/browser: ContextManager', function () { }, () => new LegacyEventEmitter()); // Needed for the keyboard tests later. - const keyboardLoader = new DOMKeyboardLoader(new KeyboardHarness(window, MinimalKeymanGlobal)); + keyboardLoader = new DOMKeyboardLoader(new KeyboardHarness(window, MinimalKeymanGlobal)); keyboardCache = new StubAndKeyboardCache(keyboardLoader); contextManager.configure({ @@ -374,6 +401,8 @@ describe.only('app/browser: ContextManager', function () { }); }); + + // ------------------------- Second suite: keyboard-related tests -------------------------- describe('keyboard management', () => { let apiStubs; @@ -413,182 +442,312 @@ describe.only('app/browser: ContextManager', function () { assert.isNotOk(contextManager.keyboardTarget); }); - it('activate: without .activeTarget, null -> null (desktop)', async () => { - const beforekeyboardchange = sinon.fake(); - const keyboardchange = sinon.fake(); - const keyboardasyncload = sinon.fake(); - contextManager.on('beforekeyboardchange', beforekeyboardchange); - contextManager.on('keyboardchange', keyboardchange); - contextManager.on('keyboardasyncload', keyboardasyncload); - - await contextManager.activateKeyboard('', ''); - // When no keyboard is set, the keyboard-metadata pair object itself should be null. - assert.equal(contextManager.activeKeyboard, null); - // Even though it's to effectively the same keyboard, we reload it (in case its stub - // has been replaced) - assert.isTrue(beforekeyboardchange.calledOnce); - assert.isTrue(keyboardchange.calledOnce); - assert.isTrue(keyboardasyncload.notCalled); - assert.equal(keyboardchange.firstCall.args[0], null); - }); - - it('activate: without .activeTarget, null -> null (touch)', async () => { - const beforekeyboardchange = sinon.fake(); - const keyboardchange = sinon.fake(); - const keyboardasyncload = sinon.fake(); - contextManager.on('beforekeyboardchange', beforekeyboardchange); - contextManager.on('keyboardchange', keyboardchange); - contextManager.on('keyboardasyncload', keyboardasyncload); - - // Hacky override - activate touch mode. - contextManager.engineConfig.hostDevice.touchable = true; - - await contextManager.activateKeyboard('', ''); - // When no keyboard is set, the keyboard-metadata pair object itself should be null. - assert.equal(contextManager.activeKeyboard?.metadata?.id, 'khmer_angkor'); - - assert.isTrue(beforekeyboardchange.calledTwice); // khmer_angkor is dynamically loaded. - assert.isTrue(keyboardchange.calledOnce); - assert.isTrue(keyboardasyncload.calledOnce); // Again, dynamically loaded. - assert.equal(keyboardchange.firstCall.args[0].metadata.id, 'khmer_angkor'); - }); - - it('activate: without .activeTarget, preloaded keyboard', async () => { - const beforekeyboardchange = sinon.fake(); - const keyboardchange = sinon.fake(); - const keyboardasyncload = sinon.fake(); - contextManager.on('beforekeyboardchange', beforekeyboardchange); - contextManager.on('keyboardchange', keyboardchange); - contextManager.on('keyboardasyncload', keyboardasyncload); - - keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); - - await contextManager.activateKeyboard('khmer_angkor', 'km'); - // The instance itself may differ, but the .keyboard and .metadata entries will - // be matching instances thanks to preloading. - assert.deepEqual(contextManager.activeKeyboard, KEYBOARDS.khmer_angkor); - - assert.isTrue(beforekeyboardchange.calledOnce); - assert.isTrue(keyboardchange.calledOnce); - assert.isTrue(keyboardasyncload.notCalled); - assert.deepEqual(keyboardchange.firstCall.args[0], KEYBOARDS.khmer_angkor); - assert.strictEqual(keyboardchange.firstCall.args[0], contextManager.activeKeyboard); - }); - - it('activate: without .activeTarget, loads keyboard', async () => { - const beforekeyboardchange = sinon.fake(); - const keyboardchange = sinon.fake(); - const keyboardasyncload = sinon.fake(); - contextManager.on('beforekeyboardchange', beforekeyboardchange); - contextManager.on('keyboardasyncload', keyboardasyncload); - contextManager.on('keyboardchange', keyboardchange); - - // No preloading - `contextManager` should be able to handle it so long as - // a matching stub already exists. - await contextManager.activateKeyboard('khmer_angkor', 'km'); - - // The instance itself may differ, but the .keyboard and .metadata entries will - // be matching instances thanks to preloading. - assert.isTrue(beforekeyboardchange.calledTwice); // Matches pre-modularized KMW behavior. - assert.isTrue(keyboardchange.calledOnce); - assert.isTrue(keyboardasyncload.calledOnce); - assert.equal(contextManager.activeKeyboard.metadata.id, 'khmer_angkor'); - assert.equal(contextManager.activeKeyboard.keyboard.id, prefixed('khmer_angkor')); - assert.equal(contextManager.activeKeyboard.metadata.langId, 'km'); - - await assertPromiseResolved(keyboardasyncload.firstCall.args[1]); - }); - - it('activate: without .activeTarget, missing definition (desktop)', async () => { - const beforekeyboardchange = sinon.fake(); - const keyboardchange = sinon.fake(); - const keyboardasyncload = sinon.fake(); - contextManager.on('beforekeyboardchange', beforekeyboardchange); - contextManager.on('keyboardasyncload', keyboardasyncload); - contextManager.on('keyboardchange', keyboardchange); - - // Setup - keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); - await contextManager.activateKeyboard('lao_2008_basic', 'lo'); - - // Actual test - try { - await blockConsoleAndAwait(() => contextManager.activateKeyboard('not_defined', 'n/a')); - assert.fail(); - } catch (err) { - // Good, an error surfaced. - // Could make assertions about the error? - } - - // Fallback behavior: deactivate the keyboard entirely (if on desktop) - assert.equal(contextManager.activeKeyboard, null); - - // The two requests - initial setup, then to the default keyboard. No attempts - // are made for the erroneous keyboard because no matching stub could be found. - - assert.isTrue(beforekeyboardchange.calledTwice); // Requested twice. - assert.isTrue(keyboardchange.calledTwice); // Actually does change the keyboard twice - assert.isTrue(keyboardasyncload.notCalled); - }); - - it('activate: without .activeTarget, missing definition (touch)', async () => { - const beforekeyboardchange = sinon.fake(); - const keyboardchange = sinon.fake(); - const keyboardasyncload = sinon.fake(); - contextManager.on('beforekeyboardchange', beforekeyboardchange); - contextManager.on('keyboardasyncload', keyboardasyncload); - contextManager.on('keyboardchange', keyboardchange); - - // Hacky override - activate touch mode. - contextManager.engineConfig.hostDevice.touchable = true; - - // Setup - keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); - await contextManager.activateKeyboard('lao_2008_basic', 'lo'); - - // Actual test - try { - await blockConsoleAndAwait(() => contextManager.activateKeyboard('not_defined', 'n/a')); - assert.fail(); - } catch (err) { - // Good, an error surfaced. - // Could make assertions about the error? - } - - // Fallback behavior: activate the first registered stub (if on touch) - assert.equal(contextManager.activeKeyboard?.metadata.id, 'khmer_angkor'); - - // The two requests - initial setup, then to the default keyboard. No attempts - // are made for the erroneous keyboard because no matching stub could be found. - - assert.isTrue(beforekeyboardchange.calledThrice); // Requested three times - the latter two for `khmer_angkor` b/c async. - assert.isTrue(keyboardchange.calledTwice); // Actually does change the keyboard twice - assert.isTrue(keyboardasyncload.calledOnce); - }); - - it('reactivate: re-requests the already-active keyboard', async () => { - const beforekeyboardchange = sinon.fake(); - const keyboardchange = sinon.fake(); - const keyboardasyncload = sinon.fake(); - contextManager.on('beforekeyboardchange', beforekeyboardchange); - contextManager.on('keyboardasyncload', keyboardasyncload); - contextManager.on('keyboardchange', keyboardchange); - - // Setup - keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); - await contextManager.activateKeyboard('lao_2008_basic', 'lo'); - - // Actual test - await contextManager.activateKeyboard('lao_2008_basic', 'lo'); - - assert.equal(contextManager.activeKeyboard.metadata.id, 'lao_2008_basic'); - - // Even though it's to effectively the same keyboard, we reload it (in case its stub - // has been replaced) - assert.isTrue(beforekeyboardchange.calledTwice); - assert.isTrue(keyboardchange.calledTwice); - assert.isTrue(keyboardasyncload.notCalled); - assert.deepEqual(keyboardchange.secondCall.args[0], keyboardchange.firstCall.args[0]); + describe('global-keyboard mode only' , () => { + it('activate: without .activeTarget, null -> null (desktop)', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardchange', keyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + + await contextManager.activateKeyboard('', ''); + // When no keyboard is set, the keyboard-metadata pair object itself should be null. + assert.equal(contextManager.activeKeyboard, null); + // Even though it's to effectively the same keyboard, we reload it (in case its stub + // has been replaced) + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.equal(keyboardchange.firstCall.args[0], null); + }); + + it('activate: without .activeTarget, null -> null (touch)', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardchange', keyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + + // Hacky override - activate touch mode. + contextManager.engineConfig.hostDevice.touchable = true; + + await contextManager.activateKeyboard('', ''); + // When no keyboard is set, the keyboard-metadata pair object itself should be null. + assert.equal(contextManager.activeKeyboard?.metadata?.id, 'khmer_angkor'); + + assert.isTrue(beforekeyboardchange.calledTwice); // khmer_angkor is dynamically loaded. + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.calledOnce); // Again, dynamically loaded. + assert.equal(keyboardchange.firstCall.args[0].metadata.id, 'khmer_angkor'); + }); + + it('activate: without .activeTarget, preloaded keyboard', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardchange', keyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + + await contextManager.activateKeyboard('khmer_angkor', 'km'); + // The instance itself may differ, but the .keyboard and .metadata entries will + // be matching instances thanks to preloading. + assert.deepEqual(contextManager.activeKeyboard, KEYBOARDS.khmer_angkor); + + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.deepEqual(keyboardchange.firstCall.args[0], KEYBOARDS.khmer_angkor); + assert.strictEqual(keyboardchange.firstCall.args[0], contextManager.activeKeyboard); + }); + + it('activate: without .activeTarget, loads keyboard', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // No preloading - `contextManager` should be able to handle it so long as + // a matching stub already exists. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + // The instance itself may differ, but the .keyboard and .metadata entries will + // be matching instances thanks to preloading. + assert.isTrue(beforekeyboardchange.calledTwice); // Matches pre-modularized KMW behavior. + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.calledOnce); + assert.equal(contextManager.activeKeyboard.metadata.id, 'khmer_angkor'); + assert.equal(contextManager.activeKeyboard.keyboard.id, prefixed('khmer_angkor')); + assert.equal(contextManager.activeKeyboard.metadata.langId, 'km'); + + await assertPromiseResolved(keyboardasyncload.firstCall.args[1]); + }); + + it('activate: without .activeTarget, missing definition (desktop)', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Setup + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + // Actual test + try { + await blockConsoleAndAwait(() => contextManager.activateKeyboard('not_defined', 'n/a')); + assert.fail(); + } catch (err) { + // Good, an error surfaced. + // Could make assertions about the error? + } + + // Fallback behavior: deactivate the keyboard entirely (if on desktop) + assert.equal(contextManager.activeKeyboard, null); + + // The two requests - initial setup, then to the default keyboard. No attempts + // are made for the erroneous keyboard because no matching stub could be found. + + assert.isTrue(beforekeyboardchange.calledTwice); // Requested twice. + assert.isTrue(keyboardchange.calledTwice); // Actually does change the keyboard twice + assert.isTrue(keyboardasyncload.notCalled); + }); + + it('activate: without .activeTarget, missing definition (touch)', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Hacky override - activate touch mode. + contextManager.engineConfig.hostDevice.touchable = true; + + // Setup + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + // Actual test + try { + await blockConsoleAndAwait(() => contextManager.activateKeyboard('not_defined', 'n/a')); + assert.fail(); + } catch (err) { + // Good, an error surfaced. + // Could make assertions about the error? + } + + // Fallback behavior: activate the first registered stub (if on touch) + assert.equal(contextManager.activeKeyboard?.metadata.id, 'khmer_angkor'); + + // The two requests - initial setup, then to the default keyboard. No attempts + // are made for the erroneous keyboard because no matching stub could be found. + + assert.isTrue(beforekeyboardchange.calledThrice); // Requested three times - the latter two for `khmer_angkor` b/c async. + assert.isTrue(keyboardchange.calledTwice); // Actually does change the keyboard twice + assert.isTrue(keyboardasyncload.calledOnce); + }); + + it('reactivate: re-requests the already-active keyboard', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Setup + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + // Actual test + await contextManager.activateKeyboard('lao_2008_basic', 'lo'); + + assert.equal(contextManager.activeKeyboard.metadata.id, 'lao_2008_basic'); + + // Even though it's to effectively the same keyboard, we reload it (in case its stub + // has been replaced) + assert.isTrue(beforekeyboardchange.calledTwice); + assert.isTrue(keyboardchange.calledTwice); + assert.isTrue(keyboardasyncload.notCalled); + assert.deepEqual(keyboardchange.secondCall.args[0], keyboardchange.firstCall.args[0]); + }); + + it('focus gained during activation', async () => { + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + const fetchPromise = contextManager.activateKeyboard('khmer_angkor', 'km'); + + // Before we sync up, we shift the active context target. + // Fortunately, this operation is synchronous... so no race conditions here. + // + // As there was no prior element to consider, this change of focus does not attempt + // to re-apply existing settings in the midst of the prior line's activation. + const input = document.getElementById('input'); + dispatchFocus('focus', input); + + await fetchPromise; + + // The instance itself may differ, but the .keyboard and .metadata entries will + // be matching instances thanks to preloading. + assert.isTrue(beforekeyboardchange.calledTwice); // Matches pre-modularized KMW behavior. + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.calledOnce); + assert.equal(contextManager.activeKeyboard.metadata.id, 'khmer_angkor'); + assert.equal(contextManager.activeKeyboard.keyboard.id, prefixed('khmer_angkor')); + assert.equal(contextManager.activeKeyboard.metadata.langId, 'km'); + + await assertPromiseResolved(keyboardasyncload.firstCall.args[1]); + }); + + it('focus changed fully after keyboard activation', async () => { + // We activate a keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const textarea = document.getElementById('textarea'); + dispatchFocus('focus', textarea); + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Before we sync up, we shift the active context target. + // Fortunately, this operation is synchronous... so no race conditions here. + // + // Since a prior context was active, KMW will reapply the "current" (before + // activation) keyboard during the focus change (b/c _FocusKeyboardSettings). + const input = document.getElementById('input'); + dispatchFocus('blur', textarea); + dispatchFocus('focus', input); + + // Allows the _FocusKeyboardSettings trigger to resolve. + await timedPromise(25); + + // No need to 'keyboardchange' when the same keyboard is kept active. + assert.isTrue(beforekeyboardchange.notCalled); + assert.isTrue(keyboardchange.notCalled); + assert.isTrue(keyboardasyncload.notCalled); + }); + + it('focus changed during activation', async () => { + const textarea = document.getElementById('textarea'); + dispatchFocus('focus', textarea); + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Adds a delay to the activate keyboard call, to help prevent race conditions below. + const fetchPromise = withDelayedFetching(keyboardLoader, 25, () => contextManager.activateKeyboard('khmer_angkor', 'km')); + + /* + * There's a resolved-promise .then() before the `keyboardasyncload` event can fire; + * in the main engine, this is used to defer until the engine is initialized + * sufficiently enough to proceed. + */ + await Promise.resolve(); + + assert.isTrue(beforekeyboardchange.calledOnce); // Matches pre-modularized KMW behavior. + assert.isTrue(keyboardchange.notCalled); + // Is triggered via Promise.then() - it's how the main engine ensures actual load + // attempts are deferred until after engine init. + assert.isTrue(keyboardasyncload.calledOnce); + + // Before we sync up, we shift the active context target. + // + // Since a prior context was active, KMW will reapply the "current" (before + // activation) keyboard during the focus change (b/c _FocusKeyboardSettings). + // Which triggers a separate `activateKeyboard` call... which can fortunately + // resolve-near instantly. + // + // Therefore, we must prevent race conditions on resolution order... + // hence the `withDelayedFetching` method. + const input = document.getElementById('input'); + dispatchFocus('blur', textarea); + dispatchFocus('focus', input); + + await timedPromise(10); + + // No need to 'keyboardchange' when the same keyboard is kept active. + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.notCalled); + // Is triggered via Promise.then() - it's how the main engine ensures actual load + // attempts are deferred until after engine init. + assert.isTrue(keyboardasyncload.calledOnce); + + // And now we let all the async stuff resolve. + await fetchPromise; + + // The instance itself may differ, but the .keyboard and .metadata entries will + // be matching instances thanks to preloading. + assert.isTrue(beforekeyboardchange.calledTwice); // +1: after async load completed. + assert.isTrue(keyboardchange.calledOnce); // +1: after async load completed. + assert.isTrue(keyboardasyncload.calledOnce); + assert.equal(contextManager.activeKeyboard.metadata.id, 'khmer_angkor'); + assert.equal(contextManager.activeKeyboard.keyboard.id, prefixed('khmer_angkor')); + assert.equal(contextManager.activeKeyboard.metadata.langId, 'km'); + + await assertPromiseResolved(keyboardasyncload.firstCall.args[1]); + }); }); }); }); \ No newline at end of file -- GitLab From 25839d0268a8c6bd493f8bb849b07fcb8522cca9 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 5 May 2023 14:52:37 +0700 Subject: [PATCH 176/386] feat(web): independent-mode keyboard unit tests --- web/src/app/browser/src/contextManager.ts | 18 +- .../auto/dom/cases/browser/contextManager.js | 249 ++++++++++++++++++ 2 files changed, 264 insertions(+), 3 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 7fb54b5719..2ce0bb8ab2 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -325,6 +325,10 @@ export default class ContextManager extends ContextManagerBase { // We activate a keyboard before proceeding. + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); await contextManager.activateKeyboard('khmer_angkor', 'km'); const textarea = document.getElementById('textarea'); @@ -749,5 +754,249 @@ describe.only('app/browser: ContextManager', function () { await assertPromiseResolved(keyboardasyncload.firstCall.args[1]); }); }); + + describe('independent-keyboard mode', () => { + //const = + + it('mode activation', async () => { + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + + // We activate a keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + const textarea = document.getElementById('textarea'); + const target = outputTargetForElement(textarea); + contextManager.setKeyboardForTarget(target, 'lao_2008_basic', 'lo'); + + // As we haven't yet focused the affected target, no keyboard-change events should have triggered yet. + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.notCalled); + assert.isTrue(keyboardchange.notCalled); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // No need to 'keyboardchange' when the same keyboard is kept active. + assert.equal(contextManager.keyboardTarget, target); + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.lao_2008_basic.metadata); + + // Spin off into separate test! + + const input = document.getElementById('input'); + dispatchFocus('blur', textarea); + dispatchFocus('focus', input); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.calledTwice); + assert.isTrue(keyboardchange.calledTwice); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + }); + + it('focus change away, to global-mode target', async () => { + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + + // We activate a keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const textarea = document.getElementById('textarea'); + const target = outputTargetForElement(textarea); + contextManager.setKeyboardForTarget(target, 'lao_2008_basic', 'lo'); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // Actual test: transitioning focus from an independent-mode target + // to a global-mode target. + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + const input = document.getElementById('input'); + dispatchFocus('blur', textarea); + dispatchFocus('focus', input); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + }); + + it('mode deactivation (target inactive)', async () => { + // Written under the assumption that prior tests in the set pass. + + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + + // We activate a keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const textarea = document.getElementById('textarea'); + const target = outputTargetForElement(textarea); + contextManager.setKeyboardForTarget(target, 'lao_2008_basic', 'lo'); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // Transition away to a different element. + const input = document.getElementById('input'); + dispatchFocus('blur', textarea); + dispatchFocus('focus', input); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // Actual test: transitioning focus from an independent-mode target + // to a global-mode target. + contextManager.setKeyboardForTarget(target, '', ''); + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + dispatchFocus('blur', input); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.notCalled); + assert.isTrue(keyboardchange.notCalled); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + }); + + it('mode deactivation (target active)', async () => { + // Written under the assumption that prior tests in the set pass. + + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + + // We activate a keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const textarea = document.getElementById('textarea'); + const target = outputTargetForElement(textarea); + contextManager.setKeyboardForTarget(target, 'lao_2008_basic', 'lo'); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Actual test: transitioning focus from an independent-mode target + // to a global-mode target. + contextManager.setKeyboardForTarget(target, '', ''); + + // Allow the indirect keyboard-change operation to resolve. + await timedPromise(10); + + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard?.metadata, KEYBOARDS.khmer_angkor.metadata); + }); + + it('change of target\'s set keyboard', async () => { + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + keyboardCache.addKeyboard(KEYBOARDS.test_chirality.keyboard); + + // We activate a keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const textarea = document.getElementById('textarea'); + const target = outputTargetForElement(textarea); + contextManager.setKeyboardForTarget(target, 'lao_2008_basic', 'lo'); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // Actual test: transitioning focus from an independent-mode target + // to a global-mode target. + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + await contextManager.activateKeyboard('test_chirality', 'en'); + + // Aspect 1: the current keyboard has changed + assert.equal(contextManager.keyboardTarget, target); + assert.isTrue(beforekeyboardchange.calledOnce); + assert.isTrue(keyboardchange.calledOnce); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.test_chirality.metadata); + + const input = document.getElementById('input'); + dispatchFocus('blur', textarea); + dispatchFocus('focus', input); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // Aspect 2: ... without affecting the global keyboard's setting. + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.calledTwice); + assert.isTrue(keyboardchange.calledTwice); + assert.isTrue(keyboardasyncload.notCalled); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + }); + + // To ensure that upon async load, the global keyboard isn't affected. + // That is, blurring an independent-mode, focusing a global-mode, after activating on + // the independent mode + // + // A TODO for the future; gotta triage it for now. + it.skip('focus changed during activation', async () => {}); + + // A general TODO for the future - setting off three async activations before the first completes + // should have #3 and ONLY #3 report successful loading / `keyboardchange`. + it.skip('cancels pending activations if replaced', async () => {}); + }); }); }); \ No newline at end of file -- GitLab From 6c0d4f130dd9bd2063fbcabecf59745c2d6a8e2d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 May 2023 08:32:46 +0700 Subject: [PATCH 177/386] docs(web): thoughts toward future unit tests --- .../auto/dom/cases/browser/contextManager.js | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index e8d73c46dc..a7acdc714a 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -987,11 +987,22 @@ describe.only('app/browser: ContextManager', function () { assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); }); - // To ensure that upon async load, the global keyboard isn't affected. - // That is, blurring an independent-mode, focusing a global-mode, after activating on - // the independent mode - // - // A TODO for the future; gotta triage it for now. + /* + * TODO: A test to ensure that upon async load, the global keyboard isn't affected. + * That is, blurring an independent-mode, focusing a global-mode, after activating on + * the independent mode. + * + * In pseudocode, the test should: + * + * 1. Have a global keyboard set + * 2. Have an independent-mode control set + have it focused + * 3. Start an async change-of-keyboard with the control from #2 focused + * 4. Change control to one in global-mode. + * 5. Let promise fulfill + * 6. Verify that the original global keyboard is still active and that the + * control from #4 is still the activeTarget + * 7. Swap to the control from #2, verify that it uses the newly-set keyboard. + */ it.skip('focus changed during activation', async () => {}); // A general TODO for the future - setting off three async activations before the first completes -- GitLab From 88f62e467b7a7251cc2d95412a00b2d546a65122 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 May 2023 09:48:17 +0700 Subject: [PATCH 178/386] feat(web): focus check for design-iframe, editables --- .../auto/dom/cases/browser/contextManager.js | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index a7acdc714a..7eeb7d8f4d 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -242,6 +242,64 @@ describe.only('app/browser: ContextManager', function () { assert.equal(outputTarget.getElement(), input, '.activeTarget does not match the newly-focused element'); }); + it('change: null -> textarea', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + + const textarea = document.getElementById('textarea'); + // Assumes we're testing with Chrome, not Firefox - the latter needs to be + // against the contentDocument, not its .body, I think. + dispatchFocus('focus', textarea); + + assert.equal(contextManager.activeTarget?.getElement(), textarea, ".activeTarget not updated when element gained focus"); + + // Check our expectations re: the `targetchange` event. + assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); + const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(outputTarget.getElement(), textarea, '.activeTarget does not match the newly-focused element'); + }); + + it('change: null -> designIframe', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + + const iframe = document.getElementById('design-iframe'); + + // Assumes we're testing with Chrome, not Firefox - the latter needs to be + // against the contentDocument, not its .body, I think. + // + // Either way, note that focus is handled specially for design-iframes, thus + // we need slightly different focus-dispatch here. + // + // Possible future improvement: OutputTarget.focusElement (property)? + // Though that may be affected by the Chrome vs Firefox bit noted above. + dispatchFocus('focus', iframe.contentDocument.body); + + assert.equal(contextManager.activeTarget?.getElement(), iframe, ".activeTarget not updated when element gained focus"); + + // Check our expectations re: the `targetchange` event. + assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); + const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(outputTarget.getElement(), iframe, '.activeTarget does not match the newly-focused element'); + }); + + it('change: null -> contentEditable', () => { + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + + const editable = document.getElementById('editable'); + // Assumes we're testing with Chrome, not Firefox - the latter needs to be + // against the contentDocument, not its .body, I think. + dispatchFocus('focus', editable); + + assert.equal(contextManager.activeTarget?.getElement(), editable, ".activeTarget not updated when element gained focus"); + + // Check our expectations re: the `targetchange` event. + assert.isTrue(targetchange.calledOnce, 'targetchange event not raised'); + const outputTarget = targetchange.firstCall.args[0]; // Should be an `Input` instance. + assert.equal(outputTarget.getElement(), editable, '.activeTarget does not match the newly-focused element'); + }); + it('change: input -> null', () => { // Setup: from prior test const targetchange = sinon.fake(); @@ -1007,7 +1065,7 @@ describe.only('app/browser: ContextManager', function () { // A general TODO for the future - setting off three async activations before the first completes // should have #3 and ONLY #3 report successful loading / `keyboardchange`. - it.skip('cancels pending activations if replaced', async () => {}); + it.skip('cancels pending activations when replaced', async () => {}); }); }); }); \ No newline at end of file -- GitLab From b1c95469f4349755aac88bfbbe7a22eca298bded Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 May 2023 10:26:43 +0700 Subject: [PATCH 179/386] fix(web): complex focus-change + indep-mode kbd issue, user test --- web/src/engine/main/src/contextManagerBase.ts | 2 +- .../auto/dom/cases/browser/contextManager.js | 106 +++++++++++++++--- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index 00082cf73c..4eec56110a 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -253,7 +253,7 @@ export abstract class ContextManagerBase }; } - this.activateKeyboardForTarget(kbdStubPair, this.keyboardTarget); + this.activateKeyboardForTarget(kbdStubPair, originalKeyboardTarget); // Only trigger `keyboardchange` events when they will affect the active context. if(this.keyboardTarget == originalKeyboardTarget) { diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 7eeb7d8f4d..31414ae23f 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -760,7 +760,9 @@ describe.only('app/browser: ContextManager', function () { contextManager.on('keyboardchange', keyboardchange); // Adds a delay to the activate keyboard call, to help prevent race conditions below. - const fetchPromise = withDelayedFetching(keyboardLoader, 25, () => contextManager.activateKeyboard('khmer_angkor', 'km')); + const fetchPromise = withDelayedFetching(keyboardLoader, 25, () => { + return contextManager.activateKeyboard('khmer_angkor', 'km') + }); /* * There's a resolved-promise .then() before the `keyboardasyncload` event can fire; @@ -1045,23 +1047,91 @@ describe.only('app/browser: ContextManager', function () { assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); }); - /* - * TODO: A test to ensure that upon async load, the global keyboard isn't affected. - * That is, blurring an independent-mode, focusing a global-mode, after activating on - * the independent mode. - * - * In pseudocode, the test should: - * - * 1. Have a global keyboard set - * 2. Have an independent-mode control set + have it focused - * 3. Start an async change-of-keyboard with the control from #2 focused - * 4. Change control to one in global-mode. - * 5. Let promise fulfill - * 6. Verify that the original global keyboard is still active and that the - * control from #4 is still the activeTarget - * 7. Swap to the control from #2, verify that it uses the newly-set keyboard. - */ - it.skip('focus changed during activation', async () => {}); + it('focus changed during activation on independent-mode target', async () => { + // Only pre-load the 'base' global keyboard. + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); + + // We activate the global keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const textarea = document.getElementById('textarea'); + const target = outputTargetForElement(textarea); + contextManager.setKeyboardForTarget(target, 'lao_2008_basic', 'lo'); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // Actual test: transitioning focus from an independent-mode target + // to a global-mode target. + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Core of the test: We're changing the active keyboard for an independent-mode target. + const asyncLoad = withDelayedFetching(keyboardLoader, 50, () => { + return contextManager.activateKeyboard('test_chirality', 'en'); + }); // We're simulating a 50 ms delay on the loading of the keyboard script itself. + + await Promise.resolve(); + + // Aspect 1: the current keyboard has not yet changed + assert.equal(contextManager.keyboardTarget, target); + assert.isTrue(beforekeyboardchange.calledOnce); // +1 + assert.isTrue(keyboardchange.notCalled); // is delayed 50 ms, so not yet. + assert.isTrue(keyboardasyncload.calledOnce); // The async load has already started. + // There's just artificial loading delay, is all. + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.lao_2008_basic.metadata); + + // Aspect 2: swap to a global-mode target, verify expectations. + const input = document.getElementById('input'); + dispatchFocus('blur', textarea); + dispatchFocus('focus', input); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.calledTwice); // +1: re-activating the global keyboard + assert.isTrue(keyboardchange.calledOnce); // +1: same + assert.isTrue(keyboardasyncload.calledOnce); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + + // Aspect 3: Sync up - without the original, independent-mode target selected. + await asyncLoad; + + // ...and verify that the active keyboard has not changed, since the target for + // activation is not itself active. + assert.equal(contextManager.keyboardTarget, null); + assert.isTrue(beforekeyboardchange.calledTwice); // +1: re-activating the global keyboard + assert.isTrue(keyboardchange.calledOnce); // +1: same + assert.isTrue(keyboardasyncload.calledOnce); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + + // BUT the async load component should be resolved. + await assertPromiseResolved(keyboardasyncload.firstCall.args[1], 0); + + // Aspect 4: swap BACK to the async-loading keyboard's OutputTarget, which should + // now be fully set to the keyboard that had been requested for activation upon it. + dispatchFocus('blur', input); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // And, final expectations: + assert.equal(contextManager.keyboardTarget, target); + assert.isTrue(beforekeyboardchange.calledThrice); // +1: activating the independent-mode kbd + assert.isTrue(keyboardchange.calledTwice); // +1: same + assert.isTrue(keyboardasyncload.calledOnce); + // Is the new keyboard, rather than the original one. + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.test_chirality.metadata); + }); // A general TODO for the future - setting off three async activations before the first completes // should have #3 and ONLY #3 report successful loading / `keyboardchange`. -- GitLab From 1a3d056881d1943b02e8c2b7030c0d0b0b4dd0e1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 May 2023 10:48:10 +0700 Subject: [PATCH 180/386] feat(web): adds activation-replacement unit test --- web/src/engine/main/src/contextManagerBase.ts | 2 + .../auto/dom/cases/browser/contextManager.js | 88 ++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index 4eec56110a..f5a5d1056b 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -187,6 +187,8 @@ export abstract class ContextManagerBase if(activationAfterAwait == activation) { return activation; } else { + // Restore the popped element; it doesn't match the current activation attempt. + this.pendingActivations.push(activationAfterAwait); return null; } } diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 31414ae23f..30dd1efd09 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -1133,9 +1133,91 @@ describe.only('app/browser: ContextManager', function () { assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.test_chirality.metadata); }); - // A general TODO for the future - setting off three async activations before the first completes - // should have #3 and ONLY #3 report successful loading / `keyboardchange`. - it.skip('cancels pending activations when replaced', async () => {}); + it('cancels pending activations when replaced', async () => { + /* + * This delay serves two purposes: + * 1. Ensures that the keyboards have delayed aspects to their operation + * 2. Is long enough that even OS-triggered context-switching effects should + * not prevent said 'delayed aspects'. + * - Race-condition prevention re: the two separate fetch requests. + */ + const FETCH_DELAY = 200; // ms + + // Only pre-load the 'base' global + initial-set keyboard. + keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); + + // We activate the global keyboard before proceeding. + await contextManager.activateKeyboard('khmer_angkor', 'km'); + + const textarea = document.getElementById('textarea'); + const target = outputTargetForElement(textarea); + // Matches the current global keyboard, but still sets it to independent-mode. + contextManager.setKeyboardForTarget(target, 'khmer_angkor', 'km'); + dispatchFocus('focus', textarea); + + // Allows any _FocusKeyboardSettings stuff trigger to resolve. + await timedPromise(10); + + // Actual test: transitioning focus from an independent-mode target + // to a global-mode target. + + const beforekeyboardchange = sinon.fake(); + const keyboardchange = sinon.fake(); + const keyboardasyncload = sinon.fake(); + contextManager.on('beforekeyboardchange', beforekeyboardchange); + contextManager.on('keyboardasyncload', keyboardasyncload); + contextManager.on('keyboardchange', keyboardchange); + + // Time to start the first load request: + const firstActivation = withDelayedFetching(keyboardLoader, FETCH_DELAY, () => { + return contextManager.activateKeyboard('lao_2008_basic', 'lo'); + //return contextManager.activateKeyboard('test_chirality', 'en'); + }); // We're simulating the delay on the loading of the keyboard script itself. + + await Promise.resolve(); + + // Aspect 1: the current keyboard has not yet changed + assert.equal(contextManager.keyboardTarget, target); + assert.isTrue(beforekeyboardchange.calledOnce); // +1 + assert.isTrue(keyboardchange.notCalled); // is delayed 50 ms, so not yet. + assert.isTrue(keyboardasyncload.calledOnce); // The async load has already started. + // There's just artificial loading delay, is all. + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + + + // So, how are things handled if we fire off a SECOND load request before the first finishes? + const secondActivation = withDelayedFetching(keyboardLoader, FETCH_DELAY, () => { + return contextManager.activateKeyboard('test_chirality', 'en'); + }); // We're simulating the delay on the loading of the keyboard script itself. + + // Note that the two activation calls are only separated by a single await Promise.resolve(); + // there should be no notable time interval between the two attempts, thus no opportunity for + // the first to resolve before the second has started. + // + // Ideally, we could go with a much shorter fetch delay, but we should play it safe here in case + // of OS context switching. + + await Promise.resolve(); + + // Aspect 2: the current keyboard STILL has not yet changed - still delayed. + assert.equal(contextManager.keyboardTarget, target); + assert.isTrue(beforekeyboardchange.calledTwice); // +1 + assert.isTrue(keyboardchange.notCalled); // both should still be delayed. + assert.isTrue(keyboardasyncload.calledTwice); // The async load has already started. + // There's just artificial loading delay, is all. + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.khmer_angkor.metadata); + + // Since we don't want any assertions subject to race conditions. + await Promise.all([firstActivation, secondActivation]); + + // Critical bit: the `lao` activation should appear to have auto-canceled; this is because + // when its keyboard loaded, we'd already requested the `test_chirality` keyboard. + assert.equal(contextManager.keyboardTarget, target); + assert.isTrue(beforekeyboardchange.calledThrice); // +1 + assert.isTrue(keyboardchange.calledOnce); // There should be no attempt to swap to the lao kbd. + assert.isTrue(keyboardasyncload.calledTwice); + assert.strictEqual(contextManager.activeKeyboard.metadata, KEYBOARDS.test_chirality.metadata); + }); }); }); }); \ No newline at end of file -- GitLab From da17a9b1c1bf978b2d128aab90c9c5011822f9ad Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 May 2023 10:57:06 +0700 Subject: [PATCH 181/386] chore(web): some tidying, removal of old version for decently-converted unit tests --- .../auto/dom/cases/browser/contextManager.js | 4 +- .../auto/integrated/cases/attachmentAPI.js | 119 ------------------ 2 files changed, 1 insertion(+), 122 deletions(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 30dd1efd09..c8442f8129 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -122,7 +122,7 @@ async function withDelayedFetching(keyboardLoader, time, closure) { await closure(); } -describe.only('app/browser: ContextManager', function () { +describe('app/browser: ContextManager', function () { this.timeout(__karma__.config.args.find((arg) => arg.type == "timeouts").standard); /** @@ -816,8 +816,6 @@ describe.only('app/browser: ContextManager', function () { }); describe('independent-keyboard mode', () => { - //const = - it('mode activation', async () => { keyboardCache.addKeyboard(KEYBOARDS.khmer_angkor.keyboard); keyboardCache.addKeyboard(KEYBOARDS.lao_2008_basic.keyboard); diff --git a/web/src/test/auto/integrated/cases/attachmentAPI.js b/web/src/test/auto/integrated/cases/attachmentAPI.js index d5df9f25e0..7d2c869c6d 100644 --- a/web/src/test/auto/integrated/cases/attachmentAPI.js +++ b/web/src/test/auto/integrated/cases/attachmentAPI.js @@ -34,34 +34,6 @@ // }, testconfig.timeouts.eventDelay); // }); -// it("Attachment/Detachment", function(done) { -// // Since we're in 'manual', we start detached. -// var ele = document.getElementById(DynamicElements.addInput()); - -// window.setTimeout(function() { -// // Ensure we didn't auto-attach. -// DynamicElements.assertDetached(ele); -// let eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(DynamicElements.keyCommand); - -// var val = ele.value; -// ele.value = ""; -// assert.equal(val, DynamicElements.disabledOutput, "'Detached' element performed keystroke processing!"); - -// keyman.attachToControl(ele); -// DynamicElements.assertAttached(ele); // Happens in-line, since we directly request the attachment. - -// eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(DynamicElements.keyCommand); - -// val = retrieveAndReset(ele); - -// assert.equal(val, DynamicElements.enabledLaoOutput, "'Attached' element did not perform keystroke processing!"); - -// done(); -// }, testconfig.timeouts.eventDelay); -// }); - // it("Enablement/Disablement", function(done) { // // Since we're in 'manual', we start detached. // var ele = document.getElementById(DynamicElements.addInput()); @@ -90,95 +62,4 @@ // }, testconfig.timeouts.eventDelay); // }, testconfig.timeouts.eventDelay); // }); - -// it("Keyboard Management (active control)", function() { -// // It appears that event generation + inline event dispatching is a bit time-intensive on some browsers. -// this.timeout(testconfig.timeouts.standard * 2); - -// var input = document.getElementById(DynamicElements.addInput()); -// var textarea = document.getElementById(DynamicElements.addText()); - -// keyman.attachToControl(input); -// keyman.attachToControl(textarea); - -// keyman.setActiveElement(input); -// // We assume from the other tests that running on the Lao keyboard will give proper output. -// // It'd be a redundant check. - -// // Set control with independent keyboard. -// keyman.setKeyboardForControl(input, "khmer_angkor", "km"); -// var eventDriver = new KMWRecorder.BrowserDriver(input); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(input); -// assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW did not use control's keyboard settings!"); - -// // Swap to a global-linked control... -// keyman.setActiveElement(textarea); -// eventDriver = new KMWRecorder.BrowserDriver(textarea); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(textarea); -// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not use manage keyboard settings correctly for global-linked control!"); - -// // Swap back and check that the settings persist. -// keyman.setActiveElement(input); -// eventDriver = new KMWRecorder.BrowserDriver(input); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(input); -// assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW forgot control's independent keyboard settings!"); - -// // Finally, clear the independent setting. -// keyman.setKeyboardForControl(input, null, null); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(input); -// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not properly clear control's independent keyboard settings!"); -// }); - -// it("Keyboard Management (inactive control)", function() { -// // It appears that event generation + inline event dispatching is a bit time-intensive on some browsers. -// this.timeout(testconfig.timeouts.standard * 2); - -// var input = document.getElementById(DynamicElements.addInput()); -// var textarea = document.getElementById(DynamicElements.addText()); - -// keyman.attachToControl(input); -// keyman.attachToControl(textarea); - -// // We assume from the other tests that running on the Lao keyboard will give proper output. -// // It'd be a redundant check. - -// // We are testing that setting a specific keyboard for an inactive control does not affect -// // the currently active control. The textarea control will be manually set to Khmer, -// // and the input control will get the document default of Lao. - -// keyman.setActiveElement(input); -// // Set textarea control with independent keyboard khmer_angkor. -// keyman.setKeyboardForControl(textarea, "khmer_angkor", "km"); -// var eventDriver = new KMWRecorder.BrowserDriver(input); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(input); -// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW set independent keyboard for the incorrect control!"); - -// // Swap to the textarea control with its overridden khmer_angkor keyboard... -// keyman.setActiveElement(textarea); -// eventDriver = new KMWRecorder.BrowserDriver(textarea); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(textarea); -// assert.equal(val, DynamicElements.enabledKhmerOutput, "KMW did not properly store keyboard for the previously-inactive control!"); - -// // Swap back to the input control and check that the settings persist. -// keyman.setActiveElement(input); -// keyman.setKeyboardForControl(textarea, null, null); - -// eventDriver = new KMWRecorder.BrowserDriver(input); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(input); -// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW made a strange error when clearing an inactive control's keyboard setting!"); - -// keyman.setActiveElement(textarea); -// // Finally, after clearing the independent setting, check that we are back to Lao output as expected for the textarea -// eventDriver = new KMWRecorder.BrowserDriver(textarea); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(textarea); -// assert.equal(val, DynamicElements.enabledLaoOutput, "KMW did not properly clear control's independent keyboard settings!"); -// }); // }); \ No newline at end of file -- GitLab From e54f7970e8678c29f63cbf4243e7ca51b6090336 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 May 2023 12:14:20 +0700 Subject: [PATCH 182/386] fix(web): issues with enable, disable while attached; final conversion of attachmentAPI.js unit tests --- .../cases/attachment/pageContextAttachment.js | 59 +++++++++++++++++ .../auto/dom/cases/browser/contextManager.js | 23 +++++++ .../auto/integrated/cases/attachmentAPI.js | 65 ------------------- 3 files changed, 82 insertions(+), 65 deletions(-) delete mode 100644 web/src/test/auto/integrated/cases/attachmentAPI.js diff --git a/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js b/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js index a418a3983f..abc1999e2c 100644 --- a/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js +++ b/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js @@ -74,6 +74,65 @@ describe('KMW element-attachment logic', function () { attacher.shutdown(); }); + it('disablement: of input', async function () { + fixture.load("input-and-disabled-text.html"); + const attacher = this.attacher; + attacher.install(false); + + let detached = []; + attacher.on('disabled', (elem) => detached.push(elem)); + + attacher.disableControl(document.getElementById('input')); + + // Give the MutationObservers time to trigger. + await timedPromise(10); + + assert.sameMembers(detached.map((elem) => elem.id), ['input']); + assert.sameMembers(attacher.inputList.map((elem) => elem.id), []); + attacher.shutdown(); + }); + + it('enablement: of (kmw-disabled) textarea', async function () { + // NOTE: At the initial point when this test was written, KMW would not attach to + // any kmw-disabled elements. You'd have to attach first, then disable to have an + // attached-but-disabled state. + // + // Ideally, we could pre-attach in a disabled state - using "input-and-disabled-text.html", + // but KMW isn't there yet. + fixture.load("input-and-text.html"); + const attacher = this.attacher; + attacher.install(false); + + // Events are only tracked after this point. + + let attached = []; + let detached = []; + attacher.on('enabled', (elem) => attached.push(elem)); + attacher.on('disabled', (elem) => detached.push(elem)); + + const textarea = document.getElementById('textarea'); + attacher.disableControl(textarea); + + // Give the MutationObservers time to trigger. + await timedPromise(10); + + // Verify setup, just to be safe. + assert.sameMembers(detached.map((elem) => elem.id), ['textarea']); + assert.sameMembers(attacher.inputList.map((elem) => elem.id), ['input']); + + // And, setup complete. + + // So NOW we can test re-enablement. + attacher.enableControl(textarea); + + // Give the MutationObservers time to trigger. + await timedPromise(10); + + assert.sameMembers(attached.map((elem) => elem.id), ['textarea']); + assert.sameMembers(attacher.inputList.map((elem) => elem.id), ['input', 'textarea']); + attacher.shutdown(); + }); + it('detachment: from auto-attached control', function () { fixture.load("input-and-text.html"); const attacher = this.attacher; diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index c8442f8129..f4fedf12d8 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -319,6 +319,29 @@ describe('app/browser: ContextManager', function () { assert.equal(outputTarget, null, 'targetchange event did not indicate clearing of .activeTarget'); }); + it('change: input disabled, -> null', async () => { + // Setup: from prior test + const targetchange = sinon.fake(); + contextManager.on('targetchange', targetchange); + + const input = document.getElementById('input'); + dispatchFocus('focus', input); + assert.equal(contextManager.activeTarget?.getElement(), input); + + // actual test + contextManager.page.disableControl(input); + + // Relies on a MutationObserver (for 'kmw-disabled' CSS class name checks) to resolve + await timedPromise(10); + + assert.equal(contextManager.activeTarget, null, '.activeTarget not updated when element KMW-disabled'); + + // Check our expectations re: the `targetchange` event. + assert.isTrue(targetchange.calledTwice, 'targetchange event not raised'); + const outputTarget = targetchange.secondCall.args[0]; // Should be null, since we lost focus. + assert.equal(outputTarget, null, 'targetchange event did not indicate clearing of .activeTarget'); + }); + it('change: input -> textarea', () => { // Setup: from prior test const targetchange = sinon.fake(); diff --git a/web/src/test/auto/integrated/cases/attachmentAPI.js b/web/src/test/auto/integrated/cases/attachmentAPI.js deleted file mode 100644 index 7d2c869c6d..0000000000 --- a/web/src/test/auto/integrated/cases/attachmentAPI.js +++ /dev/null @@ -1,65 +0,0 @@ -// var assert = chai.assert; - -// describe('Attachment API', function() { -// this.timeout(testconfig.timeouts.standard); - -// before(function() { -// assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); -// fixture.setBase('fixtures'); - -// this.timeout(testconfig.timeouts.scriptLoad * 3); -// return setupKMW({ attachType:'manual' }, testconfig.timeouts.scriptLoad).then(() => { -// const kbd1 = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad, { passive: true }); -// const kbd2 = loadKeyboardFromJSON("/keyboards/khmer_angkor.json", testconfig.timeouts.scriptLoad, { passive: true }); -// return Promise.all([kbd1, kbd2]).then(() => { -// return keyman.setActiveKeyboard("lao_2008_basic", "lo"); -// }); -// }); -// }); - -// after(function() { -// keyman.removeKeyboards('lao_2008_basic'); -// keyman.removeKeyboards('khmer_angkor'); -// teardownKMW(); -// }); - -// beforeEach(function() { -// fixture.load("robustAttachment.html"); -// }); - -// afterEach(function(done) { -// fixture.cleanup(); -// window.setTimeout(function(){ -// done(); -// }, testconfig.timeouts.eventDelay); -// }); - -// it("Enablement/Disablement", function(done) { -// // Since we're in 'manual', we start detached. -// var ele = document.getElementById(DynamicElements.addInput()); -// window.setTimeout(function() { -// keyman.attachToControl(ele); -// keyman.disableControl(ele); - -// // It appears that mobile devices do not instantly trigger the MutationObserver, so we need a small timeout -// // for the change to take effect. -// window.setTimeout(function() { -// DynamicElements.assertAttached(ele); -// let eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(ele); -// assert.equal(val, DynamicElements.disabledOutput, "'Disabled' element performed keystroke processing!"); - -// keyman.enableControl(ele); -// window.setTimeout(function() { -// DynamicElements.assertAttached(ele); // Happens in-line, since we directly request the attachment. -// let eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(DynamicElements.keyCommand); -// val = retrieveAndReset(ele); -// assert.equal(val, DynamicElements.enabledLaoOutput, "'Enabled' element did not perform keystroke processing!"); -// done(); -// }, testconfig.timeouts.eventDelay); -// }, testconfig.timeouts.eventDelay); -// }, testconfig.timeouts.eventDelay); -// }); -// }); \ No newline at end of file -- GitLab From 8dceb1737fd3a40536a513a53517cf058ecd4c0d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 May 2023 12:50:43 +0700 Subject: [PATCH 183/386] chore(web): minor cleanup --- .../test/auto/dom/cases/browser/contextManager.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index f4fedf12d8..e4fe867828 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -142,22 +142,7 @@ describe('app/browser: ContextManager', function () { */ let keyboardLoader; - let originalConsole; - - before(() => { - originalConsole = window.console; - }); - - after(() => { - window.console = originalConsole; - }) - beforeEach(async () => { - // const console = window.console = {}; - // console.log = () => {}; - // console.warn = () => {}; - // console.error = () => {}; - // Loads a common fixture and ensures all relevant elements are attached. fixture.setBase('fixtures'); fixture.load("a-bit-of-everything.html"); -- GitLab From 0e8081ddb80480aeb1f6ba16cdd7379e2cd87fc8 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 17 May 2023 09:09:12 +0700 Subject: [PATCH 184/386] chore(web): cleans up accidentally-duplicated comment --- web/src/test/auto/dom/cases/browser/contextManager.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index e4fe867828..683d0fe920 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -232,8 +232,6 @@ describe('app/browser: ContextManager', function () { contextManager.on('targetchange', targetchange); const textarea = document.getElementById('textarea'); - // Assumes we're testing with Chrome, not Firefox - the latter needs to be - // against the contentDocument, not its .body, I think. dispatchFocus('focus', textarea); assert.equal(contextManager.activeTarget?.getElement(), textarea, ".activeTarget not updated when element gained focus"); @@ -273,8 +271,6 @@ describe('app/browser: ContextManager', function () { contextManager.on('targetchange', targetchange); const editable = document.getElementById('editable'); - // Assumes we're testing with Chrome, not Firefox - the latter needs to be - // against the contentDocument, not its .body, I think. dispatchFocus('focus', editable); assert.equal(contextManager.activeTarget?.getElement(), editable, ".activeTarget not updated when element gained focus"); -- GitLab From ee3e9d36b651f9b0fd6bd8893f5736344bef710f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 12 May 2023 13:45:27 +0700 Subject: [PATCH 185/386] fix(web): linkage of keyboard font to attached controls --- web/src/app/browser/src/contextManager.ts | 7 +- .../attachment/src/pageContextAttachment.ts | 127 +++++++++++++++++- web/src/engine/dom-utils/src/stylesheets.ts | 2 +- web/src/engine/main/src/keymanEngine.ts | 4 +- .../engine/namespaced-main/dom/domManager.ts | 47 ------- 5 files changed, 133 insertions(+), 54 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 2ce0bb8ab2..384f7c2d0f 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -304,6 +304,10 @@ export default class ContextManager extends ContextManagerBase { public readonly document: Document; protected readonly owner: HTMLIFrameElement; + private baseFont: string = ''; + private appliedFont: string = ''; + private stylesheetManager: StylesheetManager; public get window(): Window { return this.document.defaultView; @@ -143,6 +152,7 @@ export class PageContextAttachment extends EventEmitter { super(); this.options = options; this.document = document; + this.stylesheetManager = new StylesheetManager(this.document.body); } // Note: `install()` must be separate from construction - otherwise, there's no time @@ -156,6 +166,7 @@ export class PageContextAttachment extends EventEmitter { // This field gets referenced by any non-design iframes detected during _SetupDocument. // Thus, we must initialize it now. this.manualAttach = manualAttach; + this.baseFont = this.getBaseFont(); if(!this.manualAttach) { this._SetupDocument(this.document.documentElement); @@ -1039,6 +1050,115 @@ export class PageContextAttachment extends EventEmitter { this.inputModeObserver?.disconnect(); } + /** + * Get the user-specified (or default) font for the first mapped input or textarea element + * before applying any keymanweb styles or classes + * + * @return {string} + */ + getBaseFont() { + var ipInput = document.getElementsByTagName<'input'>('input'), + ipTextArea=document.getElementsByTagName<'textarea'>('textarea'), + n=0,fs,fsDefault='Arial,sans-serif'; + + // Find the first input element (if it exists) + if(ipInput.length == 0 && ipTextArea.length == 0) { + n=0; + } else if(ipInput.length > 0 && ipTextArea.length == 0) { + n=1; + } else if(ipInput.length == 0 && ipTextArea.length > 0) { + n=2; + } else { + var firstInput = ipInput[0]; + var firstTextArea = ipTextArea[0]; + + if(firstInput.offsetTop < firstTextArea.offsetTop) { + n=1; + } else if(firstInput.offsetTop > firstTextArea.offsetTop) { + n=2; + } else if(firstInput.offsetLeft < firstTextArea.offsetLeft) { + n=1; + } else if(firstInput.offsetLeft > firstTextArea.offsetLeft) { + n=2; + } + } + + // Grab that font! + switch(n) { + case 0: + fs=fsDefault; + case 1: + fs = getComputedStyle(ipInput[0]).fontFamily || ''; + case 2: + fs = getComputedStyle(ipTextArea[0]).fontFamily || ''; + } + if(typeof(fs) == 'undefined' || fs == 'monospace') { + fs=fsDefault; + } + + return fs; + } + + /** + * Add or replace the style sheet used to set the font for input elements + * + * @param {Object} kfd KFont font descriptor + * @return {string} + * + **/ + buildAttachmentFontStyle(keyboardFontDescriptor: InternalKeyboardFont): string { + let kfd = keyboardFontDescriptor; + + // Get name of font to be applied + let fontName = this.baseFont; + if (kfd && typeof (kfd.family) != 'undefined') { + fontName = kfd['family']; // If we have a font set by the keyboard, prioritize that over the base font. + } + + // Unquote font name in base font (if quoted) + fontName = fontName.replace(/\u0022/g, ''); + + // Set font family chain for mapped elements and remove any double quotes + // font-family: maintains the base font as a fallback. + var rx = new RegExp('\\s?' + fontName + ',?'), fontFamily = this.appliedFont.replace(/\u0022/g, ''); + + // Remove base font name from chain if present + fontFamily = fontFamily.replace(rx, ''); + fontFamily = fontFamily.replace(/,$/, ''); + + // Then replace it at the head of the chain + if (fontFamily == '') { + fontFamily = fontName; + } else { + fontFamily = fontName + ',' + fontFamily; + } + + // Re-insert quotes around individual font names + fontFamily = '"' + fontFamily.replace(/\,\s?/g, '","') + '"'; + + // Add to the stylesheet, quoted, and with !important to override any explicit style + let s = '.keymanweb-font{\nfont-family:' + fontFamily + ' !important;\n}\n'; + + // Store the current font chain (with quote-delimited font names) + this.appliedFont = fontFamily; + + // Return the style string + return s; + } + + setAttachmentFont( + keyboardFontDescriptor: InternalKeyboardFont, + fontRoot: string, + os: DeviceSpec.OperatingSystem + ) { + this.stylesheetManager.unlinkAll(); + this.stylesheetManager.addStyleSheetForFont(keyboardFontDescriptor, fontRoot, os); + this.stylesheetManager.linkStylesheet(createStyleSheet(this.buildAttachmentFontStyle(keyboardFontDescriptor))); + + // Future note: might be worth propagating to any child documents (embedded iframes) via + // our child instances of this class. (via `this.embeddedPageContexts`) + } + shutdown() { // Embedded pages first - that way, each page can handle its own inputs, rather than having // the top-level instance handle all attached elements. @@ -1053,6 +1173,7 @@ export class PageContextAttachment extends EventEmitter { this.enablementObserver?.disconnect(); this.attachmentObserver?.disconnect(); this.inputModeObserver?.disconnect(); + this.stylesheetManager?.unlinkAll(); for(let input of this.inputList) { try { @@ -1070,4 +1191,4 @@ export class PageContextAttachment extends EventEmitter { console.error(e); } } -} \ No newline at end of file +} diff --git a/web/src/engine/dom-utils/src/stylesheets.ts b/web/src/engine/dom-utils/src/stylesheets.ts index 0c72e1e3a1..886a3816a9 100644 --- a/web/src/engine/dom-utils/src/stylesheets.ts +++ b/web/src/engine/dom-utils/src/stylesheets.ts @@ -37,7 +37,7 @@ export class StylesheetManager { **/ addStyleSheetForFont(fd: KeyboardFont, fontPathRoot: string, os?: DeviceSpec.OperatingSystem) { // Test if a valid font descriptor - if(typeof(fd) == 'undefined') { + if(!fd) { return; } diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index 8930be3dcf..686e9f7108 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -125,8 +125,8 @@ export default class KeymanEngine< this.core.activeKeyboard = kbd?.keyboard; this.legacyAPIEvents.callEvent('keyboardchange', { - internalName: kbd?.metadata.id, - languageCode: kbd?.metadata.langId + internalName: kbd?.metadata.id ?? '', + languageCode: kbd?.metadata.langId ?? '' }); // Hide OSK and do not update keyboard list if using internal keyboard (desktops). diff --git a/web/src/engine/namespaced-main/dom/domManager.ts b/web/src/engine/namespaced-main/dom/domManager.ts index 623afca6d8..4dd88d12c8 100644 --- a/web/src/engine/namespaced-main/dom/domManager.ts +++ b/web/src/engine/namespaced-main/dom/domManager.ts @@ -480,53 +480,6 @@ namespace com.keyman.dom { return Promise.resolve(); }.bind(this); - /** - * Add or replace the style sheet used to set the font for input elements - * - * @param {Object} kfd KFont font descriptor - * @return {string} - * - **/ - setAttachmentFontStyle(keyboardFontDescriptor /* : KeyboardFont */): string { - let kfd = keyboardFontDescriptor; - - // Get name of font to be applied - var fontName = this.keyman.baseFont; - if (typeof (kfd) != 'undefined' && typeof (kfd['family']) != 'undefined') { - fontName = kfd['family']; // If we have a font set by the keyboard, prioritize that over the base font. - } - - // Unquote font name in base font (if quoted) - fontName = fontName.replace(/\u0022/g, ''); - - // Set font family chain for mapped elements and remove any double quotes - // font-family: maintains the base font as a fallback. - var rx = new RegExp('\\s?' + fontName + ',?'), fontFamily = this.keyman.appliedFont.replace(/\u0022/g, ''); - - // Remove base font name from chain if present - fontFamily = fontFamily.replace(rx, ''); - fontFamily = fontFamily.replace(/,$/, ''); - - // Then replace it at the head of the chain - if (fontFamily == '') { - fontFamily = fontName; - } else { - fontFamily = fontName + ',' + fontFamily; - } - - // Re-insert quotes around individual font names - fontFamily = '"' + fontFamily.replace(/\,\s?/g, '","') + '"'; - - // Add to the stylesheet, quoted, and with !important to override any explicit style - var s = '.keymanweb-font{\nfont-family:' + fontFamily + ' !important;\n}\n'; - - // Store the current font chain (with quote-delimited font names) - this.keyman.appliedFont = fontFamily; - - // Return the style string - return s; - } - /** * Initialize the desktop user interface as soon as it is ready */ -- GitLab From 393fc1abab3e6325439f5c84323c82f8bd3bca08 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 12 May 2023 14:56:47 +0700 Subject: [PATCH 186/386] fix(web): multi-init option overriding, ordering --- web/src/app/browser/src/configuration.ts | 12 ++++++++++-- web/src/app/browser/src/keymanEngine.ts | 5 +++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 6872012acd..740891e21c 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -12,15 +12,23 @@ export class BrowserConfiguration extends EngineConfiguration { private alertHost?: AlertHost; initialize(options: Required) { + if(this._options) { + // Preserve old options, but replace with any newly-set ones if specified. + // If specified, even as 'undefined' or 'null', it will still override. + this._options = {...this._options, ...options}; + } super.initialize(options); this._ui = options.ui; this._attachType = options.attachType; + whenDocumentReady().then(() => { if(options.useAlerts && !this.alertHost) { - this.alertHost = new AlertHost(); + if(!this.alertHost) { + this.alertHost = new AlertHost(); + } } else if(!options.useAlerts && this.alertHost) { - this.alertHost.shutdown(); + this.alertHost?.shutdown(); this.alertHost = null; } }); diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 572c630fec..1c504cb014 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -82,7 +82,8 @@ export default class KeymanEngine extends KeymanEngineBase Date: Fri, 12 May 2023 16:15:09 +0700 Subject: [PATCH 187/386] change(web): a more robust fix - does not replace .paths on re-init --- web/src/app/browser/src/keymanEngine.ts | 12 +++++- .../engine/main/src/engineConfiguration.ts | 7 ++- .../package-cache/src/cloud/queryEngine.ts | 2 +- web/src/engine/paths/src/pathConfiguration.ts | 43 +++++++++++++------ 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 1c504cb014..3f8ddbec97 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -79,12 +79,20 @@ export default class KeymanEngine extends KeymanEngineBase { } initialize(options: Required) { - this._paths = new PathConfiguration(options, this.sourcePath); + if(!this._paths) { + this._paths = new PathConfiguration(options, this.sourcePath); + } else { + this._paths.updateFromOptions(options); + } + if(typeof options.setActiveOnRegister == 'boolean') { this._activateFirstKeyboard = options.setActiveOnRegister; } else { diff --git a/web/src/engine/package-cache/src/cloud/queryEngine.ts b/web/src/engine/package-cache/src/cloud/queryEngine.ts index fd315e9cd0..e4651e0975 100644 --- a/web/src/engine/package-cache/src/cloud/queryEngine.ts +++ b/web/src/engine/package-cache/src/cloud/queryEngine.ts @@ -172,7 +172,7 @@ export default class CloudQueryEngine { } else { // If there's no preconfigured option for font paths, uses the cloud's returned `fontPath` in its place. - this.pathConfig.fonts = fontPath; + this.pathConfig.updateFontPath(fontPath); } // Indicate if unable to register keyboard diff --git a/web/src/engine/paths/src/pathConfiguration.ts b/web/src/engine/paths/src/pathConfiguration.ts index 12d65bd7a3..47d8010702 100644 --- a/web/src/engine/paths/src/pathConfiguration.ts +++ b/web/src/engine/paths/src/pathConfiguration.ts @@ -11,10 +11,10 @@ const addDelimiter = (p: string) => { } export default class PathConfiguration implements OSKResourcePathConfiguration { - readonly root: string; - readonly resources: string; - readonly keyboards: string; - readonly sourcePath: string; + private readonly sourcePath: string; + private _root: string; + private _resources: string; + private _keyboards: string; // May get its initial value from the Keyman Cloud API after a query if not // otherwise specified. @@ -38,26 +38,31 @@ export default class PathConfiguration implements OSKResourcePathConfiguration { constructor(pathSpec: Required, sourcePath: string) { sourcePath = addDelimiter(sourcePath); this.sourcePath = sourcePath; - const _rootPath = sourcePath.replace(/(https?:\/\/)([^\/]*)(.*)/,'$1$2/'); this.protocol = sourcePath.replace(/(.{3,5}:)(.*)/,'$1'); + this.updateFromOptions(pathSpec); + } + + updateFromOptions(pathSpec: Required) { + const _rootPath = this.sourcePath.replace(/(https?:\/\/)([^\/]*)(.*)/,'$1$2/'); + // Get default paths and device options - this.root = _rootPath; + this._root = _rootPath; if(pathSpec.root != '') { - this.root = this.fixPath(pathSpec.root); + this._root = this.fixPath(pathSpec.root); } else { - this.root = this.fixPath(_rootPath); + this._root = this.fixPath(_rootPath); } // Resources are located with respect to the engine by default let resources = pathSpec.resources; // avoid mutating the parameter! if(resources == '') { - resources = sourcePath; + resources = this.sourcePath; } // Convert resource, keyboard and font paths to absolute URLs - this.resources = this.fixPath(resources); - this.keyboards = this.fixPath(pathSpec.keyboards); + this._resources = this.fixPath(resources); + this._keyboards = this.fixPath(pathSpec.keyboards); this._fonts = this.fixPath(pathSpec.fonts); } @@ -93,7 +98,19 @@ export default class PathConfiguration implements OSKResourcePathConfiguration { return this._fonts; } - set fonts(str: string) { - this._fonts = this.fixPath(str); + updateFontPath(path: string) { + this._fonts = this.fixPath(path); + } + + get root(): string { + return this._root; + } + + get resources(): string { + return this._resources; + } + + get keyboards(): string { + return this._keyboards; } } \ No newline at end of file -- GitLab From a48235043d7a9e5250065c5ac0a3fe186c06bb82 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 15 May 2023 10:59:07 +0700 Subject: [PATCH 188/386] fix(web): post-rebase patchup - needed field m --- web/src/app/browser/src/configuration.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 740891e21c..72e52e78af 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -10,12 +10,15 @@ export class BrowserConfiguration extends EngineConfiguration { private _attachType: string; private alertHost?: AlertHost; + private _options: Required; initialize(options: Required) { if(this._options) { // Preserve old options, but replace with any newly-set ones if specified. // If specified, even as 'undefined' or 'null', it will still override. this._options = {...this._options, ...options}; + } else { + this._options = {...options}; } super.initialize(options); -- GitLab From 6116a495205b78f6a892b16ddd1eb5d26445c83d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 15 May 2023 11:41:00 +0700 Subject: [PATCH 189/386] fix(web): missing break-statements in switch --- web/src/engine/attachment/src/pageContextAttachment.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/engine/attachment/src/pageContextAttachment.ts b/web/src/engine/attachment/src/pageContextAttachment.ts index 1c26793a19..4cd8c19781 100644 --- a/web/src/engine/attachment/src/pageContextAttachment.ts +++ b/web/src/engine/attachment/src/pageContextAttachment.ts @@ -1087,10 +1087,13 @@ export class PageContextAttachment extends EventEmitter { switch(n) { case 0: fs=fsDefault; + break; case 1: fs = getComputedStyle(ipInput[0]).fontFamily || ''; + break; case 2: fs = getComputedStyle(ipTextArea[0]).fontFamily || ''; + break; } if(typeof(fs) == 'undefined' || fs == 'monospace') { fs=fsDefault; -- GitLab From dbb15141a43fe846d5be2686b957d89fee3193a4 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 17 May 2023 10:26:13 +0700 Subject: [PATCH 190/386] chore(web): updates Promise API test page b/c it validates font-path suspicions --- web/src/test/manual/web/promise-api/index.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/test/manual/web/promise-api/index.html b/web/src/test/manual/web/promise-api/index.html index 42827325b2..7b2090a7e0 100644 --- a/web/src/test/manual/web/promise-api/index.html +++ b/web/src/test/manual/web/promise-api/index.html @@ -25,7 +25,7 @@ - + + - + - + - + + + + + + + + + + KeymanWeb Sample Page - Fully Compiled Source + + + + + + + + + + + + + + + + + + + + +

KeymanWeb Sample Page - Complex File Organization + Toggle UI

+ +
+ +
+ +

Type in your language in this text area:

+ + +

or in this input field:

+ + + +

Add a keyboard by keyboard name:

+ + + +

Add a keyboard by BCP-47 language code:

+ + + +

Add a keyboard by language name:

+ + + + +

Return to samples home page

+
+ + + + + + diff --git a/web/src/samples/index.html b/web/src/samples/index.html index 8d0ac059c0..2a9259063f 100644 --- a/web/src/samples/index.html +++ b/web/src/samples/index.html @@ -20,9 +20,9 @@

KeymanWeb Samples

-

Test unminified Keymanweb

-

Test fully compiled Keymanweb

-

Full multilingual (compiled) Keymanweb

-

Return to main index. +

Example 1 - Toggle UI, all resources in same folder as page

+

Example 2 - Toggle UI, all resources in a common subfolder

+

Example 3 - Toolbar UI, all resources in a common subfolder

+

Example 4 - a more complex file-organization setup

diff --git a/web/src/samples/samplehdr.js b/web/src/samples/samplehdr.js index 2ce215c666..129d54fc01 100644 --- a/web/src/samples/samplehdr.js +++ b/web/src/samples/samplehdr.js @@ -1,111 +1,146 @@ // JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages -/* +/* The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available. If the same keyboard is used for several languages, it must be listed for each - language, but the keyboard itself will only be loaded once. + language, but the keyboard itself will only be loaded once. If two (or more) keyboards are to be available for a given language, both must be listed. - Any number of keyboards may be specified in one or more calls. - Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. + Any number of keyboards may be specified in one or more calls. + Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. The actual keyboard object will be downloaded asynchronously when first selected for use. - + Each argument to addKeyboards() is a string, for example: european2 loads the current version of the Eurolatin 2 keyboard (for its default language) european2@fr loads the current version of the Eurolatin 2 keyboard for French european2@fr@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French - + Argument syntax also supports the following extensions: @fr load the current version of the default keyboard for French @fr$ load all available keyboards (current version) for French - - Each call to addKeyboards() requires a single call to the remote server, + + Each call to addKeyboards() requires a single call to the remote server, (unless all keyboards listed are local and fully specified) so it is better - to use multiple arguments rather than separate function calls. - - Calling addKeyboards() with no arguments returns a list of *all* available keyboards. - The Toolbar (desktop browser) UI is best suited for allowing users to select + to use multiple arguments rather than separate function calls. + + Calling addKeyboards() with no arguments returns a list of *all* available keyboards. + The Toolbar (desktop browser) UI is best suited for allowing users to select the appropriate language and keyboard in this case. Keyboards may also be specified by language name using addKeyboardsForLanguage() for example: keymanweb.addKeyboardsForLanguage('Burmese'); - + Appending $ to the language name will again cause all available keyboards for that language to be loaded rather than the default keyboard. - - The first call to addKeyboardsForLanguage() makes an additional call to the + + The first call to addKeyboardsForLanguage() makes an additional call to the keyman API to load the current list of keyboard/language associations. In this example, the following function loads the indicated keyboards, - and is called when the page loads. + and is called when the page loads. */ - function loadKeyboards() - { + function errToString(err) { + // Painful? Kinda. But needed on un-updated Android API 21! + if(Array.isArray(err)) { + var result = ''; + for(var i = 0; i < err.length; i++) { + var e = err[i]; + if(e.error instanceof Error) { + result += e.error.message + '\n'; + } else { + result += JSON.stringify(e) + '\n'; + } + } + return result; + } + if(err instanceof Error) { + return err.message; + } + return JSON.stringify(err); + } + + function doAddKeyboards(data) { + return keyman.addKeyboards(data).catch(function(err) { + console.error('keyman.addKeyboards failed with '+errToString(err)+' for '+JSON.stringify(data)); + }); + } + + function doAddKeyboardsForLanguage(data) { + return keyman.addKeyboardsForLanguage(data).catch(function(err) { + console.error('keyman.addKeyboardsForLanguage failed with '+errToString(err)+' for '+JSON.stringify(data)); + }); + } + + function loadKeyboards(nestLevel) { var kmw=keyman; - + + var base_prefix = '../'; + var prefix = './'; // The default - when prefix == 0. + + if(nestLevel !== undefined && nestLevel > 0) { + prefix = ''; + for(var i=0; i < nestLevel; i++) { + prefix = prefix + base_prefix; + } + } + // The first keyboard added will be the default keyboard for touch devices. // For faster loading, it may be best for the default keyboard to be // locally sourced. - kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, - filename:'./us-1.0.js'}); - - // Add more keyboards to the language menu, by keyboard name, - // keyboard name and language code, or just the BCP-47 language code. - kmw.addKeyboards('french', 'sil_euro_latin@no,sv', '@he'); // Loads all from uniquely-identifying strings. - + doAddKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, + filename:(prefix + 'us-1.0.js')}); + + // Add more keyboards to the language menu by: + // 1. keyboard name ('french'), + // 2. keyboard name and language code ('sil_euro_latin@no,sv'), + // 3. or just the BCP-47 language code ('@he'). + kmw.addKeyboards('french', 'sil_euro_latin@no,sv', '@he'); + // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - kmw.addKeyboardsForLanguage('Dzongkha'); - - // Add a fully-specified, locally-sourced, keyboard with custom font - kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', - languages:{ - id:'lo',name:'Lao',region:'Asia', - // A font can be specified here if its files are available. - // Example: - //font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']} - }, - filename:'./lao_2008_basic-1.2.js' - }); - - // The following two optional calls should be delayed until language menus are fully loaded: - // (a) a specific mapped input element input is focused, to ensure that the OSK appears - // (b) a specific keyboard is loaded, rather than the keyboard last used. - //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); - //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000); - - // Note that locally specified keyboards will be listed before keyboards + doAddKeyboardsForLanguage('Dzongkha'); + + // Add a fully-specified, locally-sourced, keyboard with custom font + doAddKeyboards({ + id:'lao_2008_basic', + name:'Lao Basic', + languages: { + id:'lo',name:'Lao',region:'Asia', + }, + filename:(prefix + 'lao_2008_basic-1.2.js') + }); + + // Note that locally specified keyboards will be listed before keyboards // requested from the remote server by user interfaces that do not order // keyboards alphabetically by language. } - - // Script to allow a user to add any keyboard to the keyboard menu - function addKeyboard(n) - { - var sKbd,kmw=keyman; - switch(n) - { + + // Script to allow a user to add any keyboard to the keyboard menu + function addKeyboard(n) { + var sKbd; + switch(n) { case 1: sKbd=document.getElementById('kbd_id1').value; - kmw.addKeyboards(sKbd); + doAddKeyboards(sKbd); break; case 2: sKbd=document.getElementById('kbd_id2').value.toLowerCase(); - kmw.addKeyboards('@'+sKbd); + doAddKeyboards('@'+sKbd); break; case 3: + // Add keyboard for comma-separated language name(s) sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd); + doAddKeyboardsForLanguage(sKbd); break; } } - + // Add keyboard on Enter (as well as pressing button) function clickOnEnter(e,id) { e = e || window.event; - if(e.keyCode == 13) addKeyboard(id); + if(e.keyCode == 13) addKeyboard(id); } diff --git a/web/src/samples/simplest/.gitignore b/web/src/samples/simplest/.gitignore new file mode 100644 index 0000000000..61b48a93bc --- /dev/null +++ b/web/src/samples/simplest/.gitignore @@ -0,0 +1,3 @@ +**/* +!.gitignore +!index.html \ No newline at end of file diff --git a/web/src/samples/minified.html b/web/src/samples/simplest/index.html similarity index 89% rename from web/src/samples/minified.html rename to web/src/samples/simplest/index.html index 9b5d1674e8..c2ed9fd70b 100644 --- a/web/src/samples/minified.html +++ b/web/src/samples/simplest/index.html @@ -23,7 +23,7 @@ - + - + - + -

KeymanWeb Sample Page - Fully Compiled/Minified Source

+

KeymanWeb Sample Page - Simple Subfolder + Toggle UI

- KeymanWeb Sample Page - Unminified Source + KeymanWeb Sample Page - Fully Compiled Source - - + + - + - + - + -

KeymanWeb Sample Page - Unminified Source

+

KeymanWeb Sample Page - Simple Subfolder + Toggle UI

+ diff --git a/web/src/samples/subfolder_toolbar/.gitignore b/web/src/samples/subfolder_toolbar/.gitignore new file mode 100644 index 0000000000..659fe973e2 --- /dev/null +++ b/web/src/samples/subfolder_toolbar/.gitignore @@ -0,0 +1 @@ +keyman/**/* \ No newline at end of file diff --git a/web/src/samples/multilingual.html b/web/src/samples/subfolder_toolbar/index.html similarity index 82% rename from web/src/samples/multilingual.html rename to web/src/samples/subfolder_toolbar/index.html index de2174a49a..7e9aed3f3f 100644 --- a/web/src/samples/multilingual.html +++ b/web/src/samples/subfolder_toolbar/index.html @@ -21,17 +21,16 @@ - + - + @@ -76,7 +72,7 @@ -

Return to samples home page

+

Return to samples home page

-- GitLab From 864cb59810a36ed95352ac7f65997fbcbc12be8e Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 18 May 2023 15:27:51 +0700 Subject: [PATCH 232/386] chore(web): does base updates to compiled kmw path, resource path --- .../test/manual/web/attachment-api/index.html | 5 +++-- .../test/manual/web/basic-iframe/index.html | 5 +++-- .../web/build-visual-keyboard/index.html | 21 ++++++++++--------- .../web/caps-lock-layer-3620/index.html | 5 +++-- web/src/test/manual/web/chirality/index.html | 5 +++-- web/src/test/manual/web/ckeditor/index.html | 5 +++-- web/src/test/manual/web/ckeditor/inline.html | 5 +++-- web/src/test/manual/web/empty-row/index.html | 5 +++-- web/src/test/manual/web/inline-osk/index.html | 5 +++-- web/src/test/manual/web/issue103/index.html | 4 ++-- web/src/test/manual/web/issue115/index.html | 7 ++++--- web/src/test/manual/web/issue116/index.html | 7 ++++--- web/src/test/manual/web/issue1332/index.html | 5 +++-- web/src/test/manual/web/issue160/index.html | 13 ++++++------ web/src/test/manual/web/issue266/index.html | 13 ++++++------ web/src/test/manual/web/issue271/index.html | 7 ++++--- web/src/test/manual/web/issue29/index.html | 5 +++-- web/src/test/manual/web/issue2924/header.js | 3 ++- web/src/test/manual/web/issue2924/index.html | 2 +- web/src/test/manual/web/issue3701/index.html | 5 +++-- web/src/test/manual/web/issue382/index.html | 7 ++++--- web/src/test/manual/web/issue53/index.html | 7 ++++--- .../index.html | 5 +++-- web/src/test/manual/web/issue5455/index.html | 5 +++-- web/src/test/manual/web/issue6005/index.html | 5 +++-- web/src/test/manual/web/issue62/index.html | 7 ++++--- web/src/test/manual/web/issue63/index.html | 7 ++++--- .../issue917-context-and-notany/index.html | 5 +++-- web/src/test/manual/web/issue920/index.html | 5 +++-- .../manual/web/keyboard-errors/index.html | 7 ++++--- web/src/test/manual/web/mnemonic/index.html | 5 +++-- .../manual/web/options-with-save/index.html | 5 +++-- .../manual/web/osk-event-buttons/index.html | 5 +++-- .../test/manual/web/osk-movement/index.html | 5 +++-- web/src/test/manual/web/platform/index.html | 5 +++-- .../manual/web/prediction-mtnt/index.html | 5 +++-- .../test/manual/web/prediction-ui/index.html | 5 +++-- .../manual/web/rotation-events/index.html | 7 ++++--- web/src/test/manual/web/scrolling/index.html | 5 +++-- .../manual/web/sentry-integration/index.html | 9 ++++---- .../test/manual/web/spacebar-text/index.html | 5 +++-- .../web/start-of-sentence-3621/index.html | 5 +++-- .../manual/web/test-updateLayer/index.html | 5 +++-- .../test/manual/web/unminified - manual.html | 5 +++-- .../tools/testing/bulk_rendering/index.html | 11 +++++----- 45 files changed, 161 insertions(+), 118 deletions(-) diff --git a/web/src/test/manual/web/attachment-api/index.html b/web/src/test/manual/web/attachment-api/index.html index 0ee6896af4..45bcfb8db9 100644 --- a/web/src/test/manual/web/attachment-api/index.html +++ b/web/src/test/manual/web/attachment-api/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/build-visual-keyboard/index.html b/web/src/test/manual/web/build-visual-keyboard/index.html index a572a638a7..f1cc07fc98 100644 --- a/web/src/test/manual/web/build-visual-keyboard/index.html +++ b/web/src/test/manual/web/build-visual-keyboard/index.html @@ -24,7 +24,7 @@ - + diff --git a/web/src/test/manual/web/caps-lock-layer-3620/index.html b/web/src/test/manual/web/caps-lock-layer-3620/index.html index 0c8981a725..1f03caba0f 100644 --- a/web/src/test/manual/web/caps-lock-layer-3620/index.html +++ b/web/src/test/manual/web/caps-lock-layer-3620/index.html @@ -17,7 +17,7 @@ - + + - + - + - + - + - + - + - + - + - + - + - + diff --git a/web/src/test/manual/web/issue29/index.html b/web/src/test/manual/web/issue29/index.html index 92fed6a8c1..b8476887c5 100644 --- a/web/src/test/manual/web/issue29/index.html +++ b/web/src/test/manual/web/issue29/index.html @@ -23,7 +23,7 @@ - + diff --git a/web/src/test/manual/web/issue2924/header.js b/web/src/test/manual/web/issue2924/header.js index 8e3ecb745a..21ebe47619 100644 --- a/web/src/test/manual/web/issue2924/header.js +++ b/web/src/test/manual/web/issue2924/header.js @@ -1,5 +1,6 @@ keyman.init({ - attachType: 'auto' + attachType: 'auto', + resources: '../../resources' }); window.addEventListener('load', function() { diff --git a/web/src/test/manual/web/issue2924/index.html b/web/src/test/manual/web/issue2924/index.html index 61a1be97cd..c8653b9bfe 100644 --- a/web/src/test/manual/web/issue2924/index.html +++ b/web/src/test/manual/web/issue2924/index.html @@ -6,7 +6,7 @@ KeymanWeb Issue 2924 - Variable Stores and Predictive Text - + diff --git a/web/src/test/manual/web/issue3701/index.html b/web/src/test/manual/web/issue3701/index.html index ca2729133a..aef8b2a614 100644 --- a/web/src/test/manual/web/issue3701/index.html +++ b/web/src/test/manual/web/issue3701/index.html @@ -23,7 +23,7 @@ - + - + - + diff --git a/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html b/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html index 682e279ab6..b5be6f54ec 100644 --- a/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html +++ b/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html @@ -23,7 +23,7 @@ - + - + - + - + diff --git a/web/src/test/manual/web/issue63/index.html b/web/src/test/manual/web/issue63/index.html index 43842dc3e9..0b5630bd12 100644 --- a/web/src/test/manual/web/issue63/index.html +++ b/web/src/test/manual/web/issue63/index.html @@ -23,7 +23,7 @@ - + diff --git a/web/src/test/manual/web/issue917-context-and-notany/index.html b/web/src/test/manual/web/issue917-context-and-notany/index.html index 1db3b10c4d..02ff4307ac 100644 --- a/web/src/test/manual/web/issue917-context-and-notany/index.html +++ b/web/src/test/manual/web/issue917-context-and-notany/index.html @@ -23,7 +23,7 @@ - + - + - + diff --git a/web/src/test/manual/web/mnemonic/index.html b/web/src/test/manual/web/mnemonic/index.html index 65331615aa..f1f42aebbc 100644 --- a/web/src/test/manual/web/mnemonic/index.html +++ b/web/src/test/manual/web/mnemonic/index.html @@ -20,7 +20,7 @@ - + diff --git a/web/src/test/manual/web/options-with-save/index.html b/web/src/test/manual/web/options-with-save/index.html index 0f91ea354a..f88944c3da 100644 --- a/web/src/test/manual/web/options-with-save/index.html +++ b/web/src/test/manual/web/options-with-save/index.html @@ -20,7 +20,7 @@ - + diff --git a/web/src/test/manual/web/osk-event-buttons/index.html b/web/src/test/manual/web/osk-event-buttons/index.html index 3c88e22201..0c2da2bb28 100644 --- a/web/src/test/manual/web/osk-event-buttons/index.html +++ b/web/src/test/manual/web/osk-event-buttons/index.html @@ -23,7 +23,7 @@ - + - + - + - + - + - + diff --git a/web/src/test/manual/web/scrolling/index.html b/web/src/test/manual/web/scrolling/index.html index 138e5a43c1..5f45692e04 100644 --- a/web/src/test/manual/web/scrolling/index.html +++ b/web/src/test/manual/web/scrolling/index.html @@ -24,7 +24,7 @@ - + - + - + @@ -40,8 +40,9 @@ + + + - + - + + - + - + -- GitLab From a5a4a4018fb8118fd509e031d5176f48acc57bc5 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 19 May 2023 14:58:15 +0700 Subject: [PATCH 237/386] fix(web): content-editable issues re: CKEditor --- .../src/text/outputTarget.ts | 18 +++++++++++++----- .../element-wrappers/src/contentEditable.ts | 2 +- .../element-wrappers/src/designIFrame.ts | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/common/web/keyboard-processor/src/text/outputTarget.ts b/common/web/keyboard-processor/src/text/outputTarget.ts index 30c9468295..97a4792847 100644 --- a/common/web/keyboard-processor/src/text/outputTarget.ts +++ b/common/web/keyboard-processor/src/text/outputTarget.ts @@ -390,11 +390,19 @@ export class Mock extends OutputTarget { let priorMock = outputTarget as Mock; clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); } else { - let text = outputTarget.getText(); - let beforeText = outputTarget.getTextBeforeCaret(); - let afterText = outputTarget.getTextAfterCaret(); - let selectionStart = beforeText._kmwLength(); - let selectionEnd = text._kmwLength() - afterText._kmwLength(); + const text = outputTarget.getText(); + const textLen = text._kmwLength(); + + // If !hasSelection() + let selectionStart: number = textLen; + let selectionEnd: number = 0; + + if(outputTarget.hasSelection()) { + let beforeText = outputTarget.getTextBeforeCaret(); + let afterText = outputTarget.getTextAfterCaret(); + selectionStart = beforeText._kmwLength(); + selectionEnd = textLen - afterText._kmwLength(); + } // readonly group or not, the returned Mock remains the same. // New-context events should act as if the caret were at the earlier-in-context diff --git a/web/src/engine/element-wrappers/src/contentEditable.ts b/web/src/engine/element-wrappers/src/contentEditable.ts index c5f17c9be5..e80686ac50 100644 --- a/web/src/engine/element-wrappers/src/contentEditable.ts +++ b/web/src/engine/element-wrappers/src/contentEditable.ts @@ -103,7 +103,7 @@ export default class ContentEditable extends OutputTarget<{}> { } getDeadkeyCaret(): number { - return this.getTextBeforeCaret().kmwLength(); + return (this.getTextBeforeCaret() ?? this.getText()).kmwLength(); } getTextBeforeCaret(): string { diff --git a/web/src/engine/element-wrappers/src/designIFrame.ts b/web/src/engine/element-wrappers/src/designIFrame.ts index a16369c25f..3c88630f3e 100644 --- a/web/src/engine/element-wrappers/src/designIFrame.ts +++ b/web/src/engine/element-wrappers/src/designIFrame.ts @@ -125,7 +125,7 @@ export default class DesignIFrame extends OutputTarget<{}> { } getDeadkeyCaret(): number { - return this.getTextBeforeCaret().kmwLength(); + return (this.getTextBeforeCaret() ?? this.getText()).kmwLength(); } getTextBeforeCaret(): string { -- GitLab From 02dab9cdbdc75e20073db2fa7d1705c81aff9a63 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 19 May 2023 15:21:56 +0700 Subject: [PATCH 238/386] fix(web): config & help buttons on osk --- web/src/engine/osk/src/views/floatingOskView.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/src/engine/osk/src/views/floatingOskView.ts b/web/src/engine/osk/src/views/floatingOskView.ts index 26de204fdb..9e277390b0 100644 --- a/web/src/engine/osk/src/views/floatingOskView.ts +++ b/web/src/engine/osk/src/views/floatingOskView.ts @@ -1,8 +1,8 @@ import { Codes, DeviceSpec, ManagedPromise, Version } from '@keymanapp/keyboard-processor'; import { getAbsoluteX, getAbsoluteY, landscapeView } from 'keyman/engine/dom-utils'; -import { EmitterListenerSpy } from 'keyman/engine/events'; +import { EmitterListenerSpy, LegacyEventMap } from 'keyman/engine/events'; -import OSKView, { EventMap, OSKPos, OSKRect } from './oskView.js'; +import OSKView, { EventMap, type LegacyOSKEventMap, OSKPos, OSKRect } from './oskView.js'; import TitleBar from '../components/titleBar.js'; import ResizeBar from '../components/resizeBar.js'; @@ -67,16 +67,18 @@ export default class FloatingOSKView extends OSKView { this.headerView = this.titleBar; - const onListenedEvent = (eventName: keyof EventMap) => { + const onListenedEvent = (eventName: keyof EventMap | keyof LegacyOSKEventMap) => { // As the following title bar buttons (for desktop / FloatingOSKView) do nothing unless a site // designer uses these events, we disable / hide them unless an event-handler is attached. let titleBar = this.headerView; if(titleBar && titleBar instanceof TitleBar) { switch(eventName) { + case 'configclick': case 'showConfig': titleBar.configEnabled = this.listenerCount('showConfig') + this.legacyEvents.listenerCount('configclick') > 0; break; case 'showHelp': + case 'helpclick': titleBar.helpEnabled = this.listenerCount('showHelp') + this.legacyEvents.listenerCount('helpclick') > 0; break; default: -- GitLab From f0c231ca142027735f290ffa9b20ceef2412cee1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 19 May 2023 15:22:30 +0700 Subject: [PATCH 239/386] fix(web): active element did not blur on base-page touch --- web/src/app/browser/src/contextManager.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 89a92f2fb2..903c12c262 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -183,8 +183,15 @@ export default class ContextManager extends ContextManagerBase Date: Fri, 19 May 2023 15:58:54 +0700 Subject: [PATCH 240/386] chore(web): updates extremely old manual test pages --- web/src/test/manual/web/issue005/header.js | 79 +++++++++-------- web/src/test/manual/web/issue005/index.html | 96 ++++++++++----------- web/src/test/manual/web/issue271/index.html | 8 +- 3 files changed, 91 insertions(+), 92 deletions(-) diff --git a/web/src/test/manual/web/issue005/header.js b/web/src/test/manual/web/issue005/header.js index 6ad971333e..48cd338c70 100644 --- a/web/src/test/manual/web/issue005/header.js +++ b/web/src/test/manual/web/issue005/header.js @@ -1,87 +1,87 @@ // JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages -/* +/* The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available. If the same keyboard is used for several languages, it must be listed for each - language, but the keyboard itself will only be loaded once. + language, but the keyboard itself will only be loaded once. If two (or more) keyboards are to be available for a given language, both must be listed. - Any number of keyboards may be specified in one or more calls. - Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. + Any number of keyboards may be specified in one or more calls. + Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. The actual keyboard object will be downloaded asynchronously when first selected for use. - + Each argument to addKeyboards() is a string, for example: european2 loads the current version of the Eurolatin 2 keyboard (for its default language) european2@fr loads the current version of the Eurolatin 2 keyboard for French european2@fr@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French - + Argument syntax also supports the following extensions: @fr load the current version of the default keyboard for French @fr$ load all available keyboards (current version) for French - - Each call to addKeyboards() requires a single call to the remote server, + + Each call to addKeyboards() requires a single call to the remote server, (unless all keyboards listed are local and fully specified) so it is better - to use multiple arguments rather than separate function calls. - - Calling addKeyboards() with no arguments returns a list of *all* available keyboards. - The Toolbar (desktop browser) UI is best suited for allowing users to select + to use multiple arguments rather than separate function calls. + + Calling addKeyboards() with no arguments returns a list of *all* available keyboards. + The Toolbar (desktop browser) UI is best suited for allowing users to select the appropriate language and keyboard in this case. Keyboards may also be specified by language name using addKeyboardsForLanguage() for example: keymanweb.addKeyboardsForLanguage('Burmese'); - + Appending $ to the language name will again cause all available keyboards for that language to be loaded rather than the default keyboard. - - The first call to addKeyboardsForLanguage() makes an additional call to the + + The first call to addKeyboardsForLanguage() makes an additional call to the keyman API to load the current list of keyboard/language associations. In this example, the following function loads the indicated keyboards, - and is called when the page loads. + and is called when the page loads. */ - function loadKeyboards() - { + function loadKeyboards() + { var kmw=window['keyman'] ? keyman : tavultesoft.keymanweb; - + // The first keyboard added will be the default keyboard for touch devices. // For faster loading, it may be best for the default keyboard to be // locally sourced. kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, filename:'../us-1.0.js'}); - + // Add more keyboards to the language menu, by keyboard name, // keyboard name and language code, or just the BCP-47 language code. kmw.addKeyboards('french','european2@sv','european2@no','@he'); - + // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) kmw.addKeyboardsForLanguage('Dzongkha'); - - // Add a fully-specified, locally-sourced, keyboard with custom font + + // Add a fully-specified, locally-sourced, keyboard with custom font kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', languages:{ id:'lo',name:'Lao',region:'Asia', font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']} }, - filename:'../lao_2008_basic.js' - }); + filename:'../lao_2008_basic-1.2.js' + }); // The following two optional calls should be delayed until language menus are fully loaded: // (a) a specific mapped input element input is focused, to ensure that the OSK appears - // (b) a specific keyboard is loaded, rather than the keyboard last used. + // (b) a specific keyboard is loaded, rather than the keyboard last used. //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000); - - // Note that locally specified keyboards will be listed before keyboards + + // Note that locally specified keyboards will be listed before keyboards // requested from the remote server by user interfaces that do not order // keyboards alphabetically by language. } - - // Script to allow a user to add any keyboard to the keyboard menu + + // Script to allow a user to add any keyboard to the keyboard menu function addKeyboard(n) - { + { var sKbd, kmw=window['keyman'] ? keyman : tavultesoft.keymanweb; switch(n) { @@ -99,27 +99,26 @@ break; } } - + // Add keyboard on Enter (as well as pressing button) function clickOnEnter(e,id) { e = e || window.event; - if(e.keyCode == 13) addKeyboard(id); + if(e.keyCode == 13) addKeyboard(id); } - + function removeKeyboard() { var kmw=window['keyman'] ? keyman : tavultesoft.keymanweb; - + var sKbd = document.getElementById('kbd_id4').value; var result = kmw.removeKeyboards(sKbd); - + console.log("Keyboard '" + sKbd + "' removal success: " + result); } - + // Removes keyboard on Enter (as well as pressing button) function removeOnEnter(e) - { + { e = e || window.event; - if(e.keyCode == 13) removeKeyboard(); + if(e.keyCode == 13) removeKeyboard(); } - \ No newline at end of file diff --git a/web/src/test/manual/web/issue005/index.html b/web/src/test/manual/web/issue005/index.html index 1b2e7dfae3..e7bb82fd56 100644 --- a/web/src/test/manual/web/issue005/index.html +++ b/web/src/test/manual/web/issue005/index.html @@ -2,62 +2,62 @@ - - - + + + - + - - + + KeymanWeb Sample Page - Uncompiled Source - - - - - + #KeymanWebControl {width:50%;min-width:600px;} + + + + - - - + + - + keyman.init({ + attachType:'auto', + resources: '../../resources' + }); + + - - + + - - + +

KeymanWeb: 'Test case' for proper implementation of the removeKeyboards API function

- -
+

This version is used to test the removeKeyboards API function for proper argument handling.

Automatically-loaded keyboard IDs:

    @@ -71,34 +71,34 @@


    -
+

Add a keyboard by keyboard ID:

- - + +

Remove a keyboard by keyboard ID:

- +

Return to testing home page

- - + + - - + * + *** + --> diff --git a/web/src/test/manual/web/issue271/index.html b/web/src/test/manual/web/issue271/index.html index 75c7ebdb22..dfde8f9198 100644 --- a/web/src/test/manual/web/issue271/index.html +++ b/web/src/test/manual/web/issue271/index.html @@ -32,7 +32,7 @@ The toolbar UI is best for any page designed to support keyboards for a large number of languages. --> - + - - - - - - - - - - -

KeymanWeb Sample Page - Scrolling Touch Elements

-
- -
- -

Type in your language in this text area:

- - -

Return to testing home page

-
- - - - - - - -- GitLab From 49ee4738f0db9c2e3556d5d257c515a7f58b49cd Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 09:33:27 +0700 Subject: [PATCH 244/386] docs(web): fixes manual test page link --- web/src/test/manual/web/caps-lock-layer-3620/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/manual/web/caps-lock-layer-3620/index.html b/web/src/test/manual/web/caps-lock-layer-3620/index.html index 1f03caba0f..b505403928 100644 --- a/web/src/test/manual/web/caps-lock-layer-3620/index.html +++ b/web/src/test/manual/web/caps-lock-layer-3620/index.html @@ -57,7 +57,7 @@

KeymanWeb Sample Page - caps_lock_layer_3620 Testing

-

See issue #3620 for details.

+

See issue #3620 for details.


-- GitLab From 48c8fa3324c5bc65a4c6aa93612fea755e9edd9d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 09:51:04 +0700 Subject: [PATCH 245/386] fix(web): sticky desktop-OSK CAPS --- .../web/keyboard-processor/src/keyboards/activeLayout.ts | 6 ++---- common/web/keyboard-processor/src/keyboards/keyboard.ts | 9 +++++++++ web/src/engine/osk/src/visualKeyboard.ts | 3 --- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index 065f8c5193..e6d2ab9f15 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -5,9 +5,7 @@ import type { KeyDistribution } from "../text/keyEvent.js"; import type { LayoutKey, LayoutRow, LayoutLayer, LayoutFormFactor, ButtonClass } from "./defaultLayouts.js"; import type Keyboard from "./keyboard.js"; -import KeyboardProcessor from "../text/keyboardProcessor.js"; - -import { deepCopy, type DeviceSpec } from "@keymanapp/web-utils"; +import { type DeviceSpec } from "@keymanapp/web-utils"; // TS 3.9 changed behavior of getters to make them // non-enumerable by default. This broke our 'polyfill' @@ -151,7 +149,7 @@ export class ActiveKey implements LayoutKey { @Enumerable public get baseKeyEvent(): KeyEvent { - return deepCopy(this._baseKeyEvent); + return new KeyEvent(this._baseKeyEvent); } /** diff --git a/common/web/keyboard-processor/src/keyboards/keyboard.ts b/common/web/keyboard-processor/src/keyboards/keyboard.ts index a657ff8d03..b99286030e 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboard.ts +++ b/common/web/keyboard-processor/src/keyboards/keyboard.ts @@ -499,6 +499,15 @@ export default class Keyboard { // devices, the only state key in use currently is Caps Lock, which is set // when the 'caps' layer is active in ActiveKey::constructBaseKeyEvent. if(!Lkc.device.touchable) { + /* + * For desktop-style keyboards, start from a blank slate. They have a 'default' + * (implicit 'NO_CAPS') layer but not a 'caps' layer. With caps set, it just + * highlights the key on the 'default' layer instead. + * + * 'Caps' could thus be logical-ORed with 'no-caps' below by mistake. We should + * never have both set at the same time under any condition. + */ + Lkc.Lstates = 0; Lkc.Lstates |= stateKeys['K_CAPS'] ? Codes.modifierCodes['CAPS'] : Codes.modifierCodes['NO_CAPS']; Lkc.Lstates |= stateKeys['K_NUMLOCK'] ? Codes.modifierCodes['NUM_LOCK'] : Codes.modifierCodes['NO_NUM_LOCK']; Lkc.Lstates |= stateKeys['K_SCROLL'] ? Codes.modifierCodes['SCROLL_LOCK'] : Codes.modifierCodes['NO_SCROLL_LOCK']; diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 78904557ac..3e797b44d5 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -1146,9 +1146,6 @@ export default class VisualKeyboard extends EventEmitter implements Ke modelKeyClick(e: KeyElement, input?: InputEventCoordinate) { let keyEvent = this.initKeyEvent(e, input); - - // TODO: convert into an actual event, raised by the VisualKeyboard. - // Its code is intended to lie outside of the OSK-Core library/module. this.raiseKeyEvent(keyEvent, e); } -- GitLab From 86d92eae2ea1cf8ae2f0fbbf77f3997169c533ce Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 09:55:33 +0700 Subject: [PATCH 246/386] chore(web): removes another touch-alias focused test page --- web/src/test/manual/web/index.html | 1 - .../index.html | 113 ------------------ 2 files changed, 114 deletions(-) delete mode 100644 web/src/test/manual/web/issue5312-touch-alias-optimization/index.html diff --git a/web/src/test/manual/web/index.html b/web/src/test/manual/web/index.html index 1a9f43d766..151ada8f93 100644 --- a/web/src/test/manual/web/index.html +++ b/web/src/test/manual/web/index.html @@ -65,7 +65,6 @@

Test Start of Sentence (#3621)

Test start of sentence keyboard rules (#5963)

Tests predictive text & other handling of rule matching when the final rule group does not match (#6005)

-

Tests performance of touch-alias elements with large amounts of text (#5312)

Other

Keystroke processing regression test engine.

Return to main index. diff --git a/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html b/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html deleted file mode 100644 index b5be6f54ec..0000000000 --- a/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - KeymanWeb Sample Page - Unminified Source - - - - - - - - - - - - - - - - - - - - -

KeymanWeb Sample Page - Touch-Alias Element Optimization

- -
- -
- -

Text performance with this element:

- -

Text was generated by the Lorem Ipsum Generator.

- -

Return to testing home page

-
- - -
-

--End of Document--

- - - - - -- GitLab From 3c4306800ff869b15ad14cbd6635067c14705163 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 10:03:01 +0700 Subject: [PATCH 247/386] fix(web): more page patchup --- web/src/test/manual/web/index.html | 2 +- web/src/test/manual/web/issue6005/index.html | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/web/src/test/manual/web/index.html b/web/src/test/manual/web/index.html index 151ada8f93..96926ea805 100644 --- a/web/src/test/manual/web/index.html +++ b/web/src/test/manual/web/index.html @@ -19,7 +19,7 @@ -

KeymanWeb 15 Testing

+

KeymanWeb 17 Testing

Test unminified Keymanweb

Test unminified Keymanweb in manual-attachment mode.

Tests keyboard error-handling functionality

diff --git a/web/src/test/manual/web/issue6005/index.html b/web/src/test/manual/web/issue6005/index.html index 864d98edef..8c372a6bc7 100644 --- a/web/src/test/manual/web/issue6005/index.html +++ b/web/src/test/manual/web/issue6005/index.html @@ -47,29 +47,26 @@ resources:'../../resources' }).then(function() { // if mobile device, activate pred text. - if(kmw.util.device.touchable) { - //keyman.osk.banner.setOptions({'mayPredict': true}); - + if(kmw.util.isTouchDevice()) { var pageRef = (window.location.protocol == 'file:') ? window.location.href.substr(0, window.location.href.lastIndexOf('/')+1) : window.location.href; - // Slice off any 'path' after the testing/ folder. - // This may vary depending on how the file is hosted / served; - // going relative may be tricky when served via our CI test-host setup. - pageRef = pageRef.substr(0, pageRef.lastIndexOf('testing/') + 'testing/'.length); + // Determine the path to the prediction-mtnt manual-test folder so we + // can utilize its model, rather than duplicating it here. + pageRef = pageRef.substr(0, pageRef.lastIndexOf('test/manual/web/') + 'test/manual/web/'.length); // also register appropriate models. var modelStub1 = {'id': 'nrc.en.mtnt', languages: ['en'], path: (pageRef + "prediction-mtnt/nrc.en.mtnt.model.js") }; - kmw.modelManager.register(modelStub1); + kmw.addModel(modelStub1); var modelStub2 = {'id': 'nrc.en.mtnt', languages: ['pny-latn'], // yep. Total cheat for the sake of testing. path: (pageRef + "prediction-mtnt/nrc.en.mtnt.model.js") }; - kmw.modelManager.register(modelStub2); + kmw.addModel(modelStub2); } }); kmw.addKeyboards('sil_euro_latin@en', @@ -95,6 +92,10 @@ See PR #6473 for additional details.

+

+ If not already obvious, this is designed for use on mobile devices or + with touch-device emulation via your browser's Developer mode. +

About the "Unmatched Vowel Rule" test keyboard: this keyboard will only allow up to two vowels in a row. It will not emit any further vowels until the cluster is -- GitLab From 6becc6d4891ef3197683392ae8b16f5e055af5e0 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 10:26:20 +0700 Subject: [PATCH 248/386] chore(web): minor script cleanups --- web/build.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/web/build.sh b/web/build.sh index 693063d52f..34a4fdf0ce 100755 --- a/web/build.sh +++ b/web/build.sh @@ -44,18 +44,12 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ ":samples Builds all needed resources for the KMW sample-page set" \ "--ci+ Set to utilize CI-based test configurations & reporting." -# ":app/browser The website-integrating, browser-based version of KMW" \ - # Possible TODO? # "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \ builder_describe_outputs \ configure /node_modules -# build:app/webview build/app/webview/lib/index.js \ - - # TODO: app/ui linkage. - builder_parse "$@" #### Build action definitions #### -- GitLab From 335031ae53c062e001ffcf8cf0d45c67bf0b99b0 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 13:36:45 +0700 Subject: [PATCH 249/386] fix(web): osk rotation, prevention of page-bottom occlusion --- .../src/context/pageIntegrationHandlers.ts | 29 ++++ .../browser/src/utils/rotationProcessor.ts | 142 +++++++++++++++++ web/src/app/web/README.md | 3 - web/src/app/web/kmwnative.ts | 83 ---------- web/src/app/web/kmwrotation.ts | 145 ------------------ web/src/app/web/tsconfig.json | 27 ---- .../engine/package-cache/src/keyboardStub.ts | 16 +- 7 files changed, 182 insertions(+), 263 deletions(-) create mode 100644 web/src/app/browser/src/utils/rotationProcessor.ts delete mode 100644 web/src/app/web/README.md delete mode 100644 web/src/app/web/kmwnative.ts delete mode 100644 web/src/app/web/kmwrotation.ts delete mode 100644 web/src/app/web/tsconfig.json diff --git a/web/src/app/browser/src/context/pageIntegrationHandlers.ts b/web/src/app/browser/src/context/pageIntegrationHandlers.ts index bbff97915e..a8027d2cc6 100644 --- a/web/src/app/browser/src/context/pageIntegrationHandlers.ts +++ b/web/src/app/browser/src/context/pageIntegrationHandlers.ts @@ -2,6 +2,7 @@ import { DomEventTracker } from 'keyman/engine/events'; import KeymanEngine from "../keymanEngine.js"; import { FocusAssistant } from './focusAssistant.js'; +import { RotationProcessor } from '../utils/rotationProcessor.js'; // Note: in the future, it'd probably be best to have an instance per iframe window as // well as the top-level window. This was not done in or before KMW 16.0 though, so @@ -30,11 +31,35 @@ export class PageIntegrationHandlers { */ private deactivateOnScroll: boolean; + /** + * This component should only ever be applied to a base page: we need the ability to add 'scroll + * space' on touch devices so that the OSK doesn't block the bottom. + */ + private mobilePageTrailer: HTMLDivElement; + + private rotationProcessor: RotationProcessor + constructor(window: Window, engine: KeymanEngine) { this.window = window; this.engine = engine; this.attachHandlers(); + + if(engine.config.hostDevice.touchable) { + this.buildPageTrailer(); + + this.rotationProcessor = new RotationProcessor(this.engine); + this.rotationProcessor.init(); + } + } + + private buildPageTrailer() { + // Add a blank DIV to the bottom of the page to allow the bottom of the page to be shown + const dTrailer = this.mobilePageTrailer = document.createElement('div'); + const ds=dTrailer.style; + ds.width='100%'; + ds.height=(screen.width/2)+'px'; // ... interesting choice, but okay. + document.body.appendChild(dTrailer); } private get focusAssistant(): FocusAssistant { @@ -197,6 +222,10 @@ export class PageIntegrationHandlers { eventTracker.detachDOMEvent(docBody, 'touchstart', this.touchStartActivationHandler,false); eventTracker.detachDOMEvent(docBody, 'touchmove', this.touchMoveActivationHandler, false); eventTracker.detachDOMEvent(docBody, 'touchend', this.touchEndActivationHandler, false); + + if(this.mobilePageTrailer) { + this.mobilePageTrailer.parentElement.removeChild(this.mobilePageTrailer); + } } eventTracker.detachDOMEvent(window, 'load', this._WindowLoad, false); diff --git a/web/src/app/browser/src/utils/rotationProcessor.ts b/web/src/app/browser/src/utils/rotationProcessor.ts new file mode 100644 index 0000000000..1228153497 --- /dev/null +++ b/web/src/app/browser/src/utils/rotationProcessor.ts @@ -0,0 +1,142 @@ +import KeymanEngine from "../keymanEngine.js"; + +class RotationState { + innerWidth: number; + innerHeight: number; + + constructor() { + this.innerWidth = window.innerWidth; + this.innerHeight = window.innerHeight; + } + + equals(other: RotationState) { + return this.innerWidth == other.innerWidth && this.innerHeight == other.innerHeight; + } +} + +// Please reference /testing/rotation-events/index.html and update it as necessary when maintaining this class. +export class RotationProcessor { + private keyman: KeymanEngine; + + // State variables used by rotations. + private oskVisible: boolean; + private isActive: boolean; + + // iOS-oriented members + // -------------------- + // We'll assume permutations are complete after this many 'update' iterations. + private static readonly IDLE_PERMUTATION_CAP = 15; + // Tracks the number of idle 'update' iterations since the last permutation. + private idlePermutationCounter: number = RotationProcessor.IDLE_PERMUTATION_CAP; + // Tracks the most recent rotation state snapshot. + private rotState: RotationState; + // Tracks the window.setTimeout id for rotation update checks. + private updateTimer: number; + private static readonly UPDATE_INTERVAL = 20; // 20 ms, that is. + // -------------------- + + constructor(keyman: KeymanEngine) { + this.keyman = keyman; + } + + resolve() { + var osk = this.keyman.osk; + + // `keyman: KeymanEngine` (modularized app/browser) + this.keyman.touchLanguageMenu?.hide(); + this.keyman.touchLanguageMenu = null; + + osk.setNeedsLayout(); + if(this.oskVisible) { + osk.present(); + } + + this.isActive = false; + + // If we've been using an update interval loop, we should clear the state information. + if(this.updateTimer) { + window.clearInterval(this.updateTimer); + this.rotState = null; + } + } + + // Used by both Android and iOS. + initNewRotation() { + this.oskVisible = this.keyman.osk.isVisible(); + this.keyman.osk.hideNow(); + this.isActive = true; + } + + /** + * Establishes rotation-oriented event handling for native-mode KeymanWeb. At this time, tablet PCs are not directly supported. + */ + init() { + // Note: we use wrapper functions instead of `.bind(this)` in this method to facilitate stubbing for our rotation test page. + var os = this.keyman.config.hostDevice.OS; + var util = this.keyman.util; + + if(os == 'ios') { + /* iOS is rather inconsistent about these events, with changes to important window state information - + * especially to `window.innerWidth` - possible after the events trigger! They don't always trigger + * the same amount or in a consistently predictable manner. + * + * The overall idea is to wait out all those changes so that we don't produce a bad keyboard layout. + */ + util.attachDOMEvent(window, 'orientationchange', () => { + this.iOSEventHandler(); + return false; + }); + util.attachDOMEvent(window, 'resize', () => { + this.iOSEventHandler(); + return false; + }); + } else if(os == 'android') { + // Android's far more consistent with its event generation than iOS. + if('onmozorientationchange' in screen) { + // 'mozorientationchange' doesn't seem documented at this point, let alone by TypeScript. + // Plain 'orientationchange' requires a (comparatively) late version of Firefox for Android, + // though - v44, as opposed to Chrome for Android 18. + //@ts-ignore + util.attachDOMEvent(screen, 'mozorientationchange', () => { + this.initNewRotation(); + return false; + }); + } else { + util.attachDOMEvent(window, 'orientationchange', () => { + this.initNewRotation(); + return false; + }); + } + + util.attachDOMEvent(window, 'resize', () => { + this.resolve(); + return false; + }); + } + } + + iOSEventHandler() { + if(!this.isActive) { + this.initNewRotation(); + this.rotState = new RotationState(); + + this.updateTimer = window.setInterval(this.iOSEventUpdate.bind(this), RotationProcessor.UPDATE_INTERVAL); + } + + // If one of the rotation-oriented events just triggered, we should ALWAYS reset the counter. + this.idlePermutationCounter = 0; + } + + iOSEventUpdate() { + var newState = new RotationState(); + + if(this.rotState.equals(newState)) { + if(++this.idlePermutationCounter == RotationProcessor.IDLE_PERMUTATION_CAP) { + this.resolve(); + } + } else { + this.rotState = newState; + this.idlePermutationCounter = 0; + } + } +} \ No newline at end of file diff --git a/web/src/app/web/README.md b/web/src/app/web/README.md deleted file mode 100644 index ca2213a129..0000000000 --- a/web/src/app/web/README.md +++ /dev/null @@ -1,3 +0,0 @@ -**NOTE**: _deprecated_ - -This subproject holds old namespaced-code corresponding to the new, modularized `app/browser` subproject. \ No newline at end of file diff --git a/web/src/app/web/kmwnative.ts b/web/src/app/web/kmwnative.ts deleted file mode 100644 index b7b9cc5482..0000000000 --- a/web/src/app/web/kmwnative.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Contains event management for mobile device rotation events. -/// - -/*** - KeymanWeb 11.0 - Copyright 2019 SIL International -***/ - -// If KMW is already initialized, the KMW script has been loaded more than once. We wish to prevent resetting the -// KMW system, so we use the fact that 'initialized' is only 1 / true after all scripts are loaded for the initial -// load of KMW. -if(!window['keyman']['initialized']) { - /*****************************************/ - /* */ - /* On-Screen (Visual) Keyboard Code */ - /* */ - /*****************************************/ - (function() { - // Declare KeymanWeb object - var keymanweb=window['keyman'],osk=keymanweb['osk'],util=keymanweb['util'],device=util.device; - var dbg=keymanweb.debug; - var dom = com.keyman.dom; - - // Force full initialization - keymanweb.isEmbedded = false; - - /** - * Set default device options - * @param {OptionType} opt device options object - */ - keymanweb.setDefaultDeviceOptions = function(opt : com.keyman.OptionType) { - // Element attachment type - if (!opt['attachType']) { - opt['attachType'] = (device.touchable ? 'manual' : 'auto'); - } - } - - // Get default style sheet path - keymanweb.getStyleSheetPath=function(ssName) { - var ssPath = util['getOption']('resources')+'osk/'+ssName; - return ssPath; - } - - /** - * Get keyboard path (relative or absolute) - * KeymanWeb 2 revised keyboard location specification: - * (a) absolute URL (includes ':') - load from specified URL - * (b) relative URL (starts with /, ./, ../) - load with respect to current page - * (c) filename only (anything else) - prepend keyboards option to URL - * (e.g. default keyboards option will be set by Cloud) - * - * @param {string} Lfilename keyboard file name with optional prefix - */ - keymanweb.getKeyboardPath=function(Lfilename) { - var rx=RegExp('^(([\\.]/)|([\\.][\\.]/)|(/))|(:)'); - return (rx.test(Lfilename) ? '' : keymanweb.options['keyboards']) + Lfilename; - } - - /** - * Use rotation events to adjust OSK and input element positions and scaling as necessary - */ - keymanweb.handleRotationEvents=function() { - var rotationManager = new com.keyman.RotationManager(keymanweb); - - rotationManager.init(); - } - - /** - * Possible way to detect the start of a rotation and hide the OSK before it is adjusted in size - * - * @param {Object} e accelerometer rotation event - * - keymanweb.testRotation = function(e) - { - var r=e.rotationRate; - if(typeof(r) != 'undefined') - { - dbg(r.alpha+' '+r.beta+' '+r.gamma); - } - } - */ - })(); -} \ No newline at end of file diff --git a/web/src/app/web/kmwrotation.ts b/web/src/app/web/kmwrotation.ts deleted file mode 100644 index 1c47b65dfc..0000000000 --- a/web/src/app/web/kmwrotation.ts +++ /dev/null @@ -1,145 +0,0 @@ -namespace com.keyman { - class RotationState { - innerWidth: number; - innerHeight: number; - - constructor() { - this.innerWidth = window.innerWidth; - this.innerHeight = window.innerHeight; - } - - equals(other: RotationState) { - return this.innerWidth == other.innerWidth && this.innerHeight == other.innerHeight; - } - } - - // Please reference /testing/rotation-events/index.html and update it as necessary when maintaining this class. - export class RotationManager { - private keyman: KeymanBase; - - // State variables used by rotations. - private oskVisible: boolean; - private isActive: boolean; - - // iOS-oriented members - // -------------------- - // We'll assume permutations are complete after this many 'update' iterations. - private static readonly IDLE_PERMUTATION_CAP = 15; - // Tracks the number of idle 'update' iterations since the last permutation. - private idlePermutationCounter: number = RotationManager.IDLE_PERMUTATION_CAP; - // Tracks the most recent rotation state snapshot. - private rotState: RotationState; - // Tracks the window.setTimeout id for rotation update checks. - private updateTimer: number; - private static readonly UPDATE_INTERVAL = 20; // 20 ms, that is. - // -------------------- - - constructor(keyman: KeymanBase) { - this.keyman = keyman; - } - - resolve() { - var osk = this.keyman.osk; - - // `keyman: KeymanEngine` (modularized app/browser) - this.keyman.lgMenu?.hide(); - this.keyman.lgMenu = null; - - osk.setNeedsLayout(); - if(this.oskVisible) { - osk.present(); - } - - this.isActive = false; - - // If we've been using an update interval loop, we should clear the state information. - if(this.updateTimer) { - window.clearInterval(this.updateTimer); - this.rotState = null; - } - } - - // Used by both Android and iOS. - initNewRotation() { - this.oskVisible = this.keyman.osk.isVisible(); - this.keyman.osk.hideNow(); - this.isActive = true; - } - - /** - * Establishes rotation-oriented event handling for native-mode KeymanWeb. At this time, tablet PCs are not directly supported. - */ - init() { - // If we're in embedded mode, we really should NOT run this method. - if(this.keyman.isEmbedded) { - return; - } - - // Note: we use wrapper functions instead of `.bind(this)` in this method to facilitate stubbing for our rotation test page. - var os = this.keyman.util.device.OS; - var util = this.keyman.util; - - var rotationManager = this; - - if(os == 'iOS') { - /* iOS is rather inconsistent about these events, with changes to important window state information - - * especially to `window.innerWidth` - possible after the events trigger! They don't always trigger - * the same amount or in a consistently predictable manner. - * - * The overall idea is to wait out all those changes so that we don't produce a bad keyboard layout. - */ - util.attachDOMEvent(window, 'orientationchange', function() { - rotationManager.iOSEventHandler(); - return false; - }); - util.attachDOMEvent(window, 'resize', function() { - rotationManager.iOSEventHandler(); - return false; - }); - } else if(os == 'Android') { - // Android's far more consistent with its event generation than iOS. - if('onmozorientationchange' in screen) { - util.attachDOMEvent(screen, 'mozorientationchange', function() { - rotationManager.initNewRotation(); - return false; - }); - } else { - util.attachDOMEvent(window, 'orientationchange', function() { - rotationManager.initNewRotation(); - return false; - }); - } - - util.attachDOMEvent(window, 'resize', function() { - rotationManager.resolve(); - return false; - }); - } - } - - iOSEventHandler() { - if(!this.isActive) { - this.initNewRotation(); - this.rotState = new RotationState(); - - this.updateTimer = window.setInterval(this.iOSEventUpdate.bind(this), RotationManager.UPDATE_INTERVAL); - } - - // If one of the rotation-oriented events just triggered, we should ALWAYS reset the counter. - this.idlePermutationCounter = 0; - } - - iOSEventUpdate() { - var newState = new RotationState(); - - if(this.rotState.equals(newState)) { - if(++this.idlePermutationCounter == RotationManager.IDLE_PERMUTATION_CAP) { - this.resolve(); - } - } else { - this.rotState = newState; - this.idlePermutationCounter = 0; - } - } - } -} \ No newline at end of file diff --git a/web/src/app/web/tsconfig.json b/web/src/app/web/tsconfig.json deleted file mode 100644 index 97c6a1acb0..0000000000 --- a/web/src/app/web/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - - "compilerOptions": { - "outFile": "../../../build/app/web/obj/keymanweb.js", - "sourceRoot": "/keyman/" - }, - - "include": [ - "./*.ts", - "./**/*.ts" - ], - - "files": [ - "kmwnative.ts" - ], - - "references": [ - { "path": "../../../../common/web/keyman-version", "prepend": true }, - { "path": "../../../../common/web/utils", "prepend": true }, - { "path": "../../../../common/predictive-text/browser.tsconfig.json", "prepend": true }, - { "path": "../../../../common/web/input-processor/src", "prepend": true }, - { "path": "../../../../common/web/keyboard-processor/src", "prepend": true }, - { "path": "../../engine/device-detect", "prepend": true}, - { "path": "../../engine/main", "prepend": true} - ] -} diff --git a/web/src/engine/package-cache/src/keyboardStub.ts b/web/src/engine/package-cache/src/keyboardStub.ts index f5b4724ff6..2a65cc277f 100644 --- a/web/src/engine/package-cache/src/keyboardStub.ts +++ b/web/src/engine/package-cache/src/keyboardStub.ts @@ -38,16 +38,22 @@ export default class KeyboardStub extends KeyboardProperties { this.mapRegion(apiSpec.languages); /* - * Detects the following patterns (at minimum): + * Get keyboard path (relative or absolute) + * KeymanWeb 2 revised keyboard location specification: + * (a) absolute URL (includes ':') - load from specified URL + * (b) relative URL (starts with /, ./, ../) - load with respect to current page + * (c) filename only (anything else) - prepend keyboards option to URL + * (e.g. default keyboards option will be set by Cloud) + * + * So, to fully interpret the following regex, it detects the following patterns (at minimum): * ../file (but not .../file) * ./file * /file * http:// (on the colon) - * hello:world (on the colon) - that one miiiight be less intentional, though. - * - * Essentially, detects absolute paths and paths explicitly relative to the host page's URI. + * hello:world (on the colon) - that one miiiight be less intentional, though. Would 'fall + * over' on attempted use anyway, since it's not a valid path. * - * Alternative clearer version - '^(\.{0,2}/)|(:)' + * Alternative clearer version - '^(\.{0,2}/)|(:)'? * Unless backslashes should be able to replace dots? */ let rx=RegExp('^(([\\.]/)|([\\.][\\.]/)|(/))|(:)'); -- GitLab From 56dfb11ed9c4d63e7c0c17cd2be5884d69cc2be2 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 13:39:02 +0700 Subject: [PATCH 250/386] chore(web): deletes prior location of moved code in last commit --- web/src/engine/namespaced-main/dom/domManager.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/web/src/engine/namespaced-main/dom/domManager.ts b/web/src/engine/namespaced-main/dom/domManager.ts index 4dd88d12c8..a25dc1be54 100644 --- a/web/src/engine/namespaced-main/dom/domManager.ts +++ b/web/src/engine/namespaced-main/dom/domManager.ts @@ -457,13 +457,6 @@ namespace com.keyman.dom { osk._Box.addEventListener('touchend',function(e){ e.stopPropagation(); }, false); - - // Add a blank DIV to the bottom of the page to allow the bottom of the page to be shown - dTrailer=document.createElement('DIV'); - ds=dTrailer.style; - ds.width='100%'; - ds.height=(screen.width/2)+'px'; - document.body.appendChild(dTrailer); } //document.body.appendChild(keymanweb._StyleBlock); -- GitLab From 683708b1cc1c6ee97340abc95057b8752dfdb438 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 14:22:19 +0700 Subject: [PATCH 251/386] chore(web): adjustments per review --- web/package.json | 32 ---------------- .../app/browser/src/defaultBrowserRules.ts | 18 ++++----- web/src/engine/attachment/build.sh | 38 ++++--------------- 3 files changed, 16 insertions(+), 72 deletions(-) diff --git a/web/package.json b/web/package.json index 2ffea125b5..120f5c350c 100644 --- a/web/package.json +++ b/web/package.json @@ -6,10 +6,6 @@ "types": "./build/engine/attachment/obj/index.d.ts", "import": "./build/engine/attachment/obj/index.js" }, - "./engine/attachment/lib": { - "types": "./build/engine/attachment/obj/index.d.ts", - "import": "./build/engine/attachment/lib/index.mjs" - }, "./engine/paths": { "types": "./build/engine/paths/obj/index.d.ts", "import": "./build/engine/paths/obj/index.js" @@ -18,10 +14,6 @@ "types": "./build/engine/device-detect/obj/index.d.ts", "import": "./build/engine/device-detect/obj/index.js" }, - "./engine/device-detect/lib": { - "types": "./build/engine/device-detect/lib/index.d.ts", - "import": "./build/engine/device-detect/lib/index.mjs" - }, "./engine/dom-utils": { "types": "./build/engine/dom-utils/obj/index.d.ts", "import": "./build/engine/dom-utils/obj/index.js" @@ -30,10 +22,6 @@ "types": "./build/engine/element-wrappers/obj/index.d.ts", "import": "./build/engine/element-wrappers/obj/index.js" }, - "./engine/element-wrappers/lib": { - "types": "./build/engine/element-wrappers/lib/index.d.ts", - "import": "./build/engine/element-wrappers/lib/index.mjs" - }, "./engine/events": { "types": "./build/engine/events/obj/index.d.ts", "import": "./build/engine/events/obj/index.js" @@ -43,41 +31,21 @@ "import": "./build/engine/package-cache/obj/index.js", "require": "./build/engine/package-cache/obj/index.js" }, - "./engine/package-cache/lib": { - "types": "./build/engine/package-cache/lib/index.d.ts", - "import": "./build/engine/package-cache/lib/index.mjs" - }, "./engine/package-cache/dom-requester": { "types": "./build/engine/package-cache/obj/domCloudRequester.d.ts", "import": "./build/engine/package-cache/obj/domCloudRequester.js" }, - "./engine/package-cache/dom-requester/lib": { - "types": "./build/engine/package-cache/lib/index.d.ts", - "import": "./build/engine/package-cache/lib/dom-cloud-requester.mjs" - }, "./engine/package-cache/node-requester": { "types": "./build/engine/package-cache/obj/nodeCloudRequester.d.ts", "import": "./build/engine/package-cache/obj/nodeCloudRequester.js" }, - "./engine/package-cache/node-requester/lib": { - "types": "./build/engine/package-cache/lib/index.d.ts", - "import": "./build/engine/package-cache/lib/node-cloud-requester.mjs" - }, "./engine/main": { "types": "./build/engine/main/obj/index.d.ts", "import": "./build/engine/main/obj/index.js" }, - "./engine/main/lib": { - "types": "./build/engine/main/lib/index.d.ts", - "import": "./build/engine/main/lib/index.mjs" - }, "./engine/osk": { "types": "./build/engine/osk/obj/index.d.ts", "import": "./build/engine/osk/obj/index.js" - }, - "./engine/osk/lib": { - "types": "./build/engine/osk/lib/index.d.ts", - "import": "./build/engine/osk/lib/index.mjs" } }, "repository": { diff --git a/web/src/app/browser/src/defaultBrowserRules.ts b/web/src/app/browser/src/defaultBrowserRules.ts index c38fff84db..723792d83e 100644 --- a/web/src/app/browser/src/defaultBrowserRules.ts +++ b/web/src/app/browser/src/defaultBrowserRules.ts @@ -34,24 +34,24 @@ export default class DefaultBrowserRules extends DefaultRules { applyCommand(Lkc: KeyEvent, outputTarget: OutputTarget): void { let code = this.codeForEvent(Lkc); - const contextManager = this.contextManager; + const moveToNext = (back: boolean) => { + const contextManager = this.contextManager; + const activeElement = contextManager.activeTarget?.getElement(); + const nextElement = contextManager.page.findNeighboringInput(activeElement, back); + nextElement.focus(); + } - let elem: HTMLElement; switch(code) { // This method will be handled between `ContextManager` and PageContextAttachment: // pageContextAttachment.findNeighboringInput(contextManager.activeTarget.getElement(), ) case Codes.keyCodes['K_TAB']: - const bBack = (Lkc.Lmodifiers & Codes.modifierCodes['SHIFT']) != 0; - elem = contextManager.page.findNeighboringInput(contextManager.activeTarget.getElement(), bBack); - elem.focus(); + moveToNext((Lkc.Lmodifiers & Codes.modifierCodes['SHIFT']) != 0); break; case Codes.keyCodes['K_TABBACK']: - elem = contextManager.page.findNeighboringInput(contextManager.activeTarget.getElement(), true); - elem.focus(); + moveToNext(true); break; case Codes.keyCodes['K_TABFWD']: - elem = contextManager.page.findNeighboringInput(contextManager.activeTarget.getElement(), false); - elem.focus(); + moveToNext(false); break; } diff --git a/web/src/engine/attachment/build.sh b/web/src/engine/attachment/build.sh index 4f0d8bbc48..b20eac49d3 100755 --- a/web/src/engine/attachment/build.sh +++ b/web/src/engine/attachment/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,14 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +# Imports common Web build-script definitions & functions +SUBPROJECT_NAME=engine/attachment . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/web/common.inc.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/attachment -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web (KMW) attachment engine." \ @@ -40,26 +35,7 @@ builder_parse "$@" #### Build action definitions #### -if builder_start_action configure; then - verify_npm_setup - - builder_finish_action success configure -fi - -if builder_start_action clean; then - rm -rf "$KEYMAN_ROOT/web/build/$SUBPROJECT_NAME" - builder_finish_action success clean -fi - -if builder_start_action build; then - compile $SUBPROJECT_NAME - - builder_finish_action success build -fi - -if builder_start_action test; then - # No HEADLESS tests yet. - - # TODO: DOM tests - builder_finish_action success test -fi \ No newline at end of file +builder_run_action configure verify_npm_setup +builder_run_action clean rm -rf "$KEYMAN_ROOT/web/build/$SUBPROJECT_NAME" +builder_run_action build compile $SUBPROJECT_NAME +builder_run_action test # No headless tests; TODO (next PR): DOM tests \ No newline at end of file -- GitLab From 17d0e2fca0e00b0c0fe4d8baf9a3406209e81ba1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 May 2023 15:26:13 +0700 Subject: [PATCH 252/386] fix(web): restores error-feedback handling --- web/src/app/browser/src/beepHandler.ts | 87 +++++++++++++++++++ web/src/app/browser/src/keymanEngine.ts | 4 + web/src/app/webview/src/keymanEngine.ts | 5 ++ web/src/engine/main/src/keymanEngine.ts | 8 -- .../engine/namespaced-main/dom/domManager.ts | 82 ----------------- 5 files changed, 96 insertions(+), 90 deletions(-) create mode 100644 web/src/app/browser/src/beepHandler.ts diff --git a/web/src/app/browser/src/beepHandler.ts b/web/src/app/browser/src/beepHandler.ts new file mode 100644 index 0000000000..5fae874257 --- /dev/null +++ b/web/src/app/browser/src/beepHandler.ts @@ -0,0 +1,87 @@ +import { type KeyboardInterface } from '@keymanapp/keyboard-processor'; +import { DesignIFrame, OutputTarget } from 'keyman/engine/element-wrappers'; + +// Utility object used to handle beep (keyboard error response) operations. +class BeepData { + e: HTMLElement; + c: string; + + constructor(e: HTMLElement) { + this.e = e; + this.c = e.style.backgroundColor; + } + + reset(): void { + this.e.style.backgroundColor = this.c; + } +} + +export class BeepHandler { + readonly keyboardInterface: KeyboardInterface; + + constructor(keyboardInterface: KeyboardInterface) { + this.keyboardInterface = keyboardInterface; + } + + _BeepObjects: BeepData[] = []; // BeepObjects - maintains a list of active 'beep' visual feedback elements + _BeepTimeout: number = 0; // BeepTimeout - a flag indicating if there is an active 'beep'. + // Set to 1 if there is an active 'beep', otherwise leave as '0'. + /** + * Function beep KB (DOM-side implementation) + * Scope Public + * @param {Object} Pelem element to flash + * Description Flash body as substitute for audible beep; notify embedded device to vibrate + */ + beep(outputTarget: OutputTarget) { + if(!(outputTarget instanceof OutputTarget)) { + return; + } + + // All code after this point is DOM-based, triggered by the beep. + var Pelem: HTMLElement = outputTarget.getElement(); + if(outputTarget instanceof DesignIFrame) { + Pelem = outputTarget.docRoot; // I1446 - beep sometimes fails to flash when using OSK and rich control + } + + if(!Pelem) { + return; // There's no way to signal a 'beep' to null, so just cut everything short. + } + + if(!Pelem.style || typeof(Pelem.style.backgroundColor)=='undefined') { + return; + } + + for(var Lbo=0; Lbo { + this.keyboardInterface.resetContextCache(); + + var Lbo; + this._BeepTimeout = 0; + for(Lbo=0;Lbo { touchLanguageMenu?: LanguageMenu; @@ -37,6 +38,7 @@ export default class KeymanEngine extends KeymanEngineBase { this.contextManager.restoreLastActiveTarget(); @@ -47,6 +49,8 @@ export default class KeymanEngine extends KeymanEngineBase this.legacyAPIEvents)); this._util = new UtilApiEndpoint(config); + this.beepHandler = new BeepHandler(this.core.keyboardInterface); + this.core.keyboardProcessor.beepHandler = () => this.beepHandler.beep(this.contextManager.activeTarget); this.hardKeyboard = new HardwareEventKeyboard(config.hardDevice, this.core.keyboardProcessor, this.contextManager); diff --git a/web/src/app/webview/src/keymanEngine.ts b/web/src/app/webview/src/keymanEngine.ts index a68f3f8999..93120af86c 100644 --- a/web/src/app/webview/src/keymanEngine.ts +++ b/web/src/app/webview/src/keymanEngine.ts @@ -51,6 +51,10 @@ export default class KeymanEngine extends KeymanEngineBase void = null; hideKeyboard?: () => void = null; menuKeyUp?: () => void = null; showKeyboardList?: () => void = null; diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index 46ad1e6933..b6d05fed03 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -104,12 +104,6 @@ export default class KeymanEngine< this.osk?.refreshLayout(); }); - this.core.keyboardProcessor.beepHandler = (target) => { - if(this.doBeep) { - this.doBeep(target); - } - } - // The OSK does not possess a direct connection to the KeyboardProcessor's state-key // management object; this event + handler allow us to keep the OSK's related states // in sync. @@ -454,8 +448,6 @@ export default class KeymanEngine< setNumericLayer() { this.core.keyboardProcessor.setNumericLayer(this.config.softDevice); }; - - doBeep?: (target: OutputTarget) => void; } // Intent: define common behaviors for both primary app types; each then subclasses & extends where needed. \ No newline at end of file diff --git a/web/src/engine/namespaced-main/dom/domManager.ts b/web/src/engine/namespaced-main/dom/domManager.ts index a25dc1be54..148c7618e9 100644 --- a/web/src/engine/namespaced-main/dom/domManager.ts +++ b/web/src/engine/namespaced-main/dom/domManager.ts @@ -14,20 +14,7 @@ /// namespace com.keyman.dom { - // Utility object used to handle beep (keyboard error response) operations. - class BeepData { - e: HTMLElement; - c: string; - - constructor(e: HTMLElement) { - this.e = e; - this.c = e.style.backgroundColor; - } - reset(): void { - this.e.style.backgroundColor = this.c; - } - } /** * This class serves as the intermediary between KeymanWeb and any given web page's elements. @@ -45,10 +32,6 @@ namespace com.keyman.dom { */ nonTouchHandlers: DOMEventHandlers; - _BeepObjects: BeepData[] = []; // BeepObjects - maintains a list of active 'beep' visual feedback elements - _BeepTimeout: number = 0; // BeepTimeout - a flag indicating if there is an active 'beep'. - // Set to 1 if there is an active 'beep', otherwise leave as '0'. - // Used for special touch-based page interactions re: element activation on touch devices. deactivateOnScroll: boolean = false; deactivateOnRelease: boolean = false; @@ -94,71 +77,6 @@ namespace com.keyman.dom { } } - /** - * Function beep KB (DOM-side implementation) - * Scope Public - * @param {Object} Pelem element to flash - * Description Flash body as substitute for audible beep; notify embedded device to vibrate - */ - doBeep(outputTarget: targets.OutputTarget) { - // Handles embedded-mode beeps. - let keyman = com.keyman.singleton; - if ('beepKeyboard' in keyman) { - keyman['beepKeyboard'](); - return; - } - - if(!(outputTarget instanceof targets.OutputTarget)) { - return; - } - - // All code after this point is DOM-based, triggered by the beep. - var Pelem: HTMLElement = outputTarget.getElement(); - if(outputTarget instanceof dom.targets.DesignIFrame) { - Pelem = outputTarget.docRoot; // I1446 - beep sometimes fails to flash when using OSK and rich control - } - - if(!Pelem) { - return; // There's no way to signal a 'beep' to null, so just cut everything short. - } - - if(!Pelem.style || typeof(Pelem.style.backgroundColor)=='undefined') { - return; - } - - for(var Lbo=0; Lbo Date: Mon, 22 May 2023 15:57:43 +0700 Subject: [PATCH 253/386] chore(web): a bit of cleaning --- web/src/engine/namespaced-main/kmwbase.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/engine/namespaced-main/kmwbase.ts b/web/src/engine/namespaced-main/kmwbase.ts index 71ce63c93a..ac447b6bec 100644 --- a/web/src/engine/namespaced-main/kmwbase.ts +++ b/web/src/engine/namespaced-main/kmwbase.ts @@ -288,8 +288,6 @@ namespace com.keyman { this.domManager.moveToElement(e); } - // Functions that might be added later - ['beepKeyboard']: () => void; /** * @param {number} dn Number of pre-caret characters to delete * @param {string} s Text to insert -- GitLab From 7e2f94d2c4ca5a5c55ff802451a9ddb7cc514f50 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 08:57:40 +0700 Subject: [PATCH 254/386] chore(web): finishes modularization of base object API --- web/src/app/browser/src/contextManager.ts | 19 +- web/src/app/browser/src/keymanEngine.ts | 46 ++- web/src/engine/main/src/keymanEngine.ts | 18 + .../engine/namespaced-main/dom/domManager.ts | 14 - web/src/engine/namespaced-main/kmwbase.ts | 377 ------------------ 5 files changed, 79 insertions(+), 395 deletions(-) delete mode 100644 web/src/engine/namespaced-main/kmwbase.ts diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 29dfe93342..4017dc9b6d 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -316,15 +316,21 @@ export default class ContextManager extends ContextManagerBase { let target = this.currentTarget || this.mostRecentTarget; - let attachmentInfo = target?.getElement()._kmwAttachment; - if(attachmentInfo?.keyboard || attachmentInfo?.keyboard === '') { + if(this.isTargetKeyboardIndependent(target)) { return target; } else { return null; } } + private isTargetKeyboardIndependent(target: OutputTarget): boolean { + let attachmentInfo = target?.getElement()._kmwAttachment; + + // If null or undefined, we're in 'global' mode. + return !!(attachmentInfo?.keyboard || attachmentInfo?.keyboard === ''); + } + // Note: is part of the keyboard activation process. Not to be called directly by published API. activateKeyboardForTarget(kbd: {keyboard: Keyboard, metadata: KeyboardStub}, target: OutputTarget) { let attachment = target?.getElement()._kmwAttachment; @@ -393,6 +399,15 @@ export default class ContextManager extends ContextManagerBase) { + if(!this.isTargetKeyboardIndependent(target)) { + return this.globalKeyboard.metadata; + } else { + const attachment = target.getElement()._kmwAttachment; + return this.keyboardCache.getStub(attachment.keyboard, attachment.languageCode); + } + } + protected getFallbackCodes() { const emptyCodes = { id: '', diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index e8d48521dc..f6581fba7f 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -40,6 +40,8 @@ export default class KeymanEngine extends KeymanEngineBase { this.contextManager.restoreLastActiveTarget(); } @@ -111,7 +113,8 @@ export default class KeymanEngine extends KeymanEngineBase} Promise of added keyboard/error stubs **/ - ['addKeyboardsForLanguage'](arg: string[]|string) : Promise<(KeyboardStub|ErrorStub)[]> { + addKeyboardsForLanguage(arg: string[]|string) : Promise<(KeyboardStub|ErrorStub)[]> { if (typeof arg === 'string') { return this.keyboardRequisitioner.addLanguageKeyboards(arg.split(',').map(item => item.trim())); } else { @@ -450,6 +479,19 @@ export default class KeymanEngine extends KeymanEngineBase -// Defines the web-page interface object. -/// -// Extends KeyboardInterface with DOM-oriented offerings. -/// -// Defines the web-page interface object. -/// -// Includes KMW-added property declaration extensions for HTML elements. -/// -// Defines keyboard management classes. -/// -// Defines KMW's hotkey management object. -/// -// Defines the ui management code that tracks UI activation and such. -/// -// Defines OSK management code. -/// -/// -/// -// Defines the model manager. -/// - -/*** - KeymanWeb 14.0 - Copyright 2017-2021 SIL International -***/ -namespace com.keyman { - - export enum SpacebarText { - KEYBOARD = 'keyboard', - LANGUAGE = 'language', - LANGUAGE_KEYBOARD = 'languageKeyboard', - BLANK = 'blank' - }; - - export interface OptionType { - root?: string; - resources?: string; - keyboards?: string; - fonts?: string; - // attachType and ui are 100% ignored for embedded (app-WebView hosted) contexts. - // They should only be expected for website-based KMW use. - attachType?: 'auto' | 'manual' | ''; // If blank or undefined, attachType will be assigned to "auto" or "manual" - ui?: string; - setActiveOnRegister?: string; // TODO: Convert to boolean. Option loader needs to be able to receive this as a string or boolean - - // Determines the default text shown on the spacebar, if undefined, LANGUAGE_KEYBOARD - spacebarText?: SpacebarText; - - // Determines whether or not KeymanWeb should display its own alert messages - // Only relevant for website-based KMW use. - useAlerts?: boolean; - } - - export class KeymanBase { - _MasterDocument = null; // Document with controller (to allow iframes to distinguish local/master control) - _HotKeys = []; // Array of document-level hotkey objects - warned = false; // Warning flag (to prevent multiple warnings) - baseFont = 'sans-serif'; // Default page font (utilized by the OSK) - appliedFont = ''; // Chain of fonts to be applied to OSK elements - srcPath = ''; // Path to folder containing executing keymanweb script - rootPath = ''; // Path to server root - protocol = ''; // Protocol used for the KMW script. - mustReloadKeyboard = false;// Force keyboard refreshing even if already loaded - globalKeyboard = null; // Indicates the currently-active keyboard for controls without independent keyboard settings. - globalLanguageCode = null; // Indicates the language code corresponding to `globalKeyboard`. - isEmbedded = false; // Indicates if the KeymanWeb instance is embedded within a mobile app. - // Blocks full page initialization when set to `true`. - - initialized: number; // Signals the initialization state of the KeymanWeb system. - 'build' = 300; // TS needs this to be defined within the class. - _BrowserIsSafari: boolean; // A legacy browser-check variable. - - // Used as placeholders during initialization. - // The corresponding class properties should be dropped after a refactor; - // this is an intermediate solution while doing the big conversion. - static _srcPath: string; - static _rootPath: string; - static _protocol: string; - - // Internal objects - ['util']: Util; - ['osk']: com.keyman.osk.OSKView; - ['ui']: any; - keyboardManager: keyboards.KeyboardManager; - domManager: dom.DOMManager; - hotkeyManager: HotkeyManager; - uiManager: UIManager; // half has been modularized as `focusAssistant`. - core: text.InputProcessor; - modelManager: text.prediction.ModelManager; - - touchAliasing: dom.DOMEventHandlers; - - // Defines default option values - options: OptionType = { - root: '', - resources: '', - keyboards: '', - fonts: '', - attachType: '', - ui: null, - setActiveOnRegister: 'true', // TODO: convert to boolean - spacebarText: SpacebarText.LANGUAGE_KEYBOARD, - - // Determines whether or not KeymanWeb should display its own alert messages - useAlerts: true - }; - - // Stub functions (defined later in code only if required) - setDefaultDeviceOptions(opt: OptionType){} - getStyleSheetPath(s){return s;} - linkStylesheetResources(){} - getKeyboardPath(f, p?){return f;} - KC_(n, ln, Pelem){return '';} - handleRotationEvents(){} - /** - * Legacy API function for touch-alias issue workarounds - * Touch-aliases have been eliminated, though. - * - * This function is deprecated in 16.0, with plans for removal in 17.0. - */ - ['alignInputs'](eleList?: HTMLElement[]){} - namespaceID(Pstub) {}; - preserveID(Pk) {}; - - setInitialized(val: number) { - this.initialized = this['initialized'] = val; - } - - refreshElementContent = null; - - // ------------- - - constructor() { - // Allow internal minification of the public modules. - this.util = this['util'] = new Util(this); - this.ui = this['ui'] = {}; - - this.keyboardManager = new keyboards.KeyboardManager(this); - this.domManager = new dom.DOMManager(this); - this.hotkeyManager = new HotkeyManager(this); - this.uiManager = new UIManager(this); - - // I732 START - Support for European underlying keyboards #1 - var baseLayout: string; - if(typeof(window['KeymanWeb_BaseLayout']) !== 'undefined') { - baseLayout = window['KeymanWeb_BaseLayout']; - } else { - baseLayout = 'us'; - } - this._BrowserIsSafari = (navigator.userAgent.indexOf('AppleWebKit') >= 0); // I732 END - Support for European underlying keyboards #1 - - this.core = new text.InputProcessor(this.util.device.coreSpec, { - baseLayout: baseLayout, - variableStoreSerializer: new dom.VariableStoreCookieSerializer() - }); - - // Used by the embedded apps. - this['interface'] = this.core.keyboardInterface; - - this.modelManager = new text.prediction.ModelManager(); - this.osk = this['osk'] = null; - - // Load properties from their static variants. - this['build'] = Number.parseInt(com.keyman.KEYMAN_VERSION.VERSION_PATCH, 10); - this.srcPath = KeymanBase._srcPath; - this.rootPath = KeymanBase._rootPath; - this.protocol = KeymanBase._protocol; - - this['version'] = com.keyman.KEYMAN_VERSION.VERSION_RELEASE; - this['helpURL'] = 'http://help.keyman.com/go'; - this.setInitialized(0); - - // Signals that a KMW load has occurred in order to prevent double-loading. - this['loaded'] = true; - } - - /** - * Triggers a KeymanWeb engine shutdown to facilitate a full system reset. - * This function is designed for use with KMW unit-testing, which reloads KMW - * multiple times to test the different initialization paths. - */ - ['shutdown']() { - // Disable page focus/blur events, which can sometimes trigger and cause parallel KMW instances in testing. - - if(this.ui && this.ui.shutdown) { - this.ui.shutdown(); - } - - dom.DOMEventHandlers.states = new dom.CommonDOMStates(); - } - - /** - * Function _push - * Scope Private - * @param {Array} Parray Array - * @param {*} Pval Value to be pushed or appended to array - * @return {Array} Returns extended array - * Description Push (if possible) or append a value to an array - */ - _push(Parray: T[], Pval: T) { - if(Parray.push) { - Parray.push(Pval); - } else { - Parray=Parray.concat(Pval); - } - return Parray; - } - - // Base object API definitions - - /** - * Exposed function to load keyboards by name. One or more arguments may be used - * - * @param {any[]} args keyboard name string or keyboard metadata JSON object - * @returns {Promise<(KeyboardStub|ErrorStub)[]>} Promise of added keyboard/error stubs - * - */ - ['addKeyboards'](...args: any[]) : - Promise<(com.keyman.keyboards.KeyboardStub|com.keyman.keyboards.ErrorStub)[]> { - if (!args || !args[0] || args[0].length == 0) { - // Get the cloud keyboard catalog - return this.keyboardManager.keymanCloudRequest('',false).catch(error => { - console.error(error); - return Promise.reject([{error: error}]); - }); - } else { - let x: (string|com.keyman.keyboards.KeyboardStub)[] = []; - if (Array.isArray(args[0])) { - args[0].forEach(a => - x.push(a)); - } else if (Array.isArray(args)) { - args.forEach(a => - x.push(a)); - } else { - x.push(args); - } - return this.keyboardManager.addKeyboardArray(x); - } - } - - /** - * Add default keyboards for given language(s) - * - * @param {string|string[]} arg Language name (multiple arguments allowed) - * @returns {Promise<(KeyboardStub|ErrorStub)[]>} Promise of added keyboard/error stubs - **/ - ['addKeyboardsForLanguage'](arg: string[]|string) : Promise<(com.keyman.keyboards.KeyboardStub|com.keyman.keyboards.ErrorStub)[]> { - if (typeof arg === 'string') { - return this.keyboardManager.addLanguageKeyboards(arg.split(',').map(item => item.trim())); - } else { - return this.keyboardManager.addLanguageKeyboards(arg); - } - } - - /** - * Function getKeyboardForControl - * Scope Public - * @param {Element} Pelem Control element - * @return {string|null} The independently-managed keyboard for the control. - * Description Returns the keyboard ID of the current independently-managed keyboard for this control. - * If it is currently following the global keyboard setting, returns null instead. - */ - ['getKeyboardForControl'](Pelem) { - this.domManager.getKeyboardForControl(Pelem); - } - - /** - * Function getLanguageForControl - * Scope Public - * @param {Element} Pelem Control element - * @return {string|null} The independently-managed keyboard for the control. - * Description Returns the language code used with the current independently-managed keyboard for this control. - * If it is currently following the global keyboard setting, returns null instead. - */ - ['getLanguageForControl'](Pelem) { - this.domManager.getLanguageForControl(Pelem); - } - - /** - * Move focus to user-specified element - * - * @param {string|Object} e element or element id - * - **/ - ['moveToElement'](e: string|HTMLElement) { - this.domManager.moveToElement(e); - } - - /** - * @param {number} dn Number of pre-caret characters to delete - * @param {string} s Text to insert - * @param {number=} dr Number of post-caret characters to delete - */ - ['oninserttext']: (dn: number, s: string, dr?: number) => void; - - /** - * Create copy of the OSK that can be used for embedding in documentation or help - * The currently active keyboard will be returned if PInternalName is null - * - * @param {string} PInternalName internal name of keyboard, with or without Keyboard_ prefix - * @param {number} Pstatic static keyboard flag (unselectable elements) - * @param {string=} argFormFactor layout form factor, defaulting to 'desktop' - * @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default' - * @return {Object} DIV object with filled keyboard layer content - */ - ['BuildVisualKeyboard'](PInternalName, Pstatic, argFormFactor, argLayerId): HTMLElement { - let PKbd: com.keyman.keyboards.Keyboard = null; - - if(PInternalName != null) { - var p=PInternalName.toLowerCase().replace('keyboard_',''); - var keyboardsList = this.keyboardManager.keyboards; - - for(let Ln=0; Ln Date: Tue, 23 May 2023 10:53:22 +0700 Subject: [PATCH 255/386] chore(web): finishes converting keyman.util API --- web/src/app/browser/src/configuration.ts | 8 + web/src/app/browser/src/utilApiEndpoint.ts | 112 ++++++ .../engine/main/src/engineConfiguration.ts | 10 +- web/src/engine/namespaced-main/kmwapi.ts | 30 -- web/src/engine/namespaced-main/kmwutils.ts | 348 ------------------ 5 files changed, 123 insertions(+), 385 deletions(-) delete mode 100644 web/src/engine/namespaced-main/kmwapi.ts delete mode 100644 web/src/engine/namespaced-main/kmwutils.ts diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index e7d69a5e47..2de59e0df8 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -54,6 +54,14 @@ export class BrowserConfiguration extends EngineConfiguration { return this.alertHost; } + set signalUser(host: AlertHost) { + if(!host || host != this.alertHost) { + this.alertHost.shutdown(); + } + + this.alertHost = host; + } + debugReport(): Record { const baseReport = super.debugReport(); baseReport.attachType = this.attachType; diff --git a/web/src/app/browser/src/utilApiEndpoint.ts b/web/src/app/browser/src/utilApiEndpoint.ts index 4cf7955bfb..48fbc76730 100644 --- a/web/src/app/browser/src/utilApiEndpoint.ts +++ b/web/src/app/browser/src/utilApiEndpoint.ts @@ -38,6 +38,12 @@ export class UtilApiEndpoint { readonly getAbsoluteX = getAbsoluteX; readonly getAbsoluteY = getAbsoluteY; + // These four were renamed, but we need to maintain their legacy names. + readonly _GetAbsoluteX = getAbsoluteX; + readonly _GetAbsoluteY = getAbsoluteY; + readonly _GetAbsolute = this.getAbsolute; + readonly toNzString = this.nzString; + /** * Expose the touchable state for UIs - will disable external UIs entirely **/ @@ -80,6 +86,31 @@ export class UtilApiEndpoint { } } + setOption(optionName: keyof BrowserInitOptionSpec, value: any): void { + switch(optionName) { + case 'attachType': + // 16.0 & before: did nothing. + // Fixable for 17.0 with some extra work, but the changes would likely be enough to + // merit a focused PR. It's not 100% straightforward. + break; + case 'ui': + // 16.0 & before: relies on the Float UI to passively pick up on any changes. + // Only appears to be effective before the Float UI initializes. + break; + case 'useAlerts': + this.config.signalUser = (value ? new AlertHost() : null); + break; + case 'setActiveOnRegister': + this.config.activateFirstKeyboard = !!value; + break; + case 'spacebarText': + this.config.spacebarText = value; + break; + default: + throw new Error("Path-related options may not be changed after the engine has initialized."); + } + } + /** * Document cookie parsing for use by kernel, OSK, UI etc. * @@ -226,6 +257,87 @@ export class UtilApiEndpoint { this.alertHost.alert(s, fn); } + /** + * Function toNzString + * Scope Public + * @param {*} item variable to test + * @param {?*=} dflt default value + * @return {*} + * Description Test if a variable is null, false, empty string, or undefined, and return as string + */ + nzString(item: any, dflt: string): string { + // // ... is this whole thing essentially just: + // return '' + (item || dflt || ''); + // // ? + + let dfltValue = ''; + if(arguments.length > 1) { + dfltValue = dflt; + } + + if(typeof(item) == 'undefined') { + return dfltValue; + } + + if(item == null) { + return dfltValue; + } + + if(item == 0 || item == '') { + return dfltValue; + } + + return ''+item; + } + + /** + * Function toNumber + * Scope Public + * @param {string} s numeric string + * @param {number} dflt default value + * @return {number} + * Description Return string converted to integer or default value + */ + toNumber(s: string, dflt: number): number { + const x = parseInt(s,10); + return isNaN(x) ? dflt : x; + } + + /** + * Function toNumber + * Scope Public + * @param {string} s numeric string + * @param {number} dflt default value + * @return {number} + * Description Return string converted to real value or default value + */ + toFloat(s: string, dflt: number): number { + const x = parseFloat(s); + return isNaN(x) ? dflt : x; + } + + /** + * Function rgba + * Scope Public + * @param {Object} s element style object + * @param {number} r red value, 0-255 + * @param {number} g green value, 0-255 + * @param {number} b blue value, 0-255 + * @param {number} a opacity value, 0-1.0 + * @return {string} background colour style string + * Description Browser-independent alpha-channel management + */ + rgba(s: HTMLStyleElement, r:number, g:number, b:number, a:number): string { + let bgColor='transparent'; + try { + bgColor='rgba('+r+','+g+','+b+','+a+')'; + } catch(ex) { + bgColor='rgb('+r+','+g+','+b+')'; + } + + return bgColor; + } + shutdown() { this.stylesheetManager?.unlinkAll(); this.domEventTracker?.shutdown(); diff --git a/web/src/engine/main/src/engineConfiguration.ts b/web/src/engine/main/src/engineConfiguration.ts index 3641e82071..d74804eaad 100644 --- a/web/src/engine/main/src/engineConfiguration.ts +++ b/web/src/engine/main/src/engineConfiguration.ts @@ -19,7 +19,7 @@ export class EngineConfiguration extends EventEmitter { readonly deferForInitialization: ManagedPromise; private _paths: PathConfiguration; - private _activateFirstKeyboard: boolean; + public activateFirstKeyboard: boolean; private _spacebarText: SpacebarText; private _stubNamespacer?: (KeyboardStub) => void; @@ -49,9 +49,9 @@ export class EngineConfiguration extends EventEmitter { } if(typeof options.setActiveOnRegister == 'boolean') { - this._activateFirstKeyboard = options.setActiveOnRegister; + this.activateFirstKeyboard = options.setActiveOnRegister; } else { - this._activateFirstKeyboard = true; + this.activateFirstKeyboard = true; } this._spacebarText = options.spacebarText; @@ -68,10 +68,6 @@ export class EngineConfiguration extends EventEmitter { return this._paths; } - get activateFirstKeyboard() { - return this._activateFirstKeyboard; - } - get spacebarText() { return this._spacebarText; } diff --git a/web/src/engine/namespaced-main/kmwapi.ts b/web/src/engine/namespaced-main/kmwapi.ts deleted file mode 100644 index 8efcbcbad4..0000000000 --- a/web/src/engine/namespaced-main/kmwapi.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// -/// - -/** - * This file generates aliases linking renamed functions to some of our published developer API for KMW. - * This won't enable Closure to do "advanced minification", but it's useful for ensuring we don't break - * things people depended on in legacy versions. - */ - -// Util.ts -(function() { - let prototype = com.keyman.Util.prototype; - - var publishAPI = function(legacyName: string, name: string) { - prototype[legacyName] = prototype[name]; - } - - // These four were renamed, but we need to maintain their legacy names. - publishAPI("_GetAbsoluteX", 'getAbsoluteX'); - publishAPI("_GetAbsoluteY", "getAbsoluteY"); - publishAPI("_GetAbsolute", "getAbsolute"); - publishAPI("toNzString", "nzString"); -}()); - -(function() { - // DOM-aware KeymanWeb overwrites some of the API functions, so we - // re-publish the API so that the overwritten functions are accessible - // via their short-form equivalents found in actual keyboard code. - com.keyman.text.KeyboardInterface.__publishShorthandAPI(); -}()); \ No newline at end of file diff --git a/web/src/engine/namespaced-main/kmwutils.ts b/web/src/engine/namespaced-main/kmwutils.ts deleted file mode 100644 index 74bdf0c0dd..0000000000 --- a/web/src/engine/namespaced-main/kmwutils.ts +++ /dev/null @@ -1,348 +0,0 @@ -// Includes KMW-added property declaration extensions for HTML elements. -/// -// Includes the DOM utils, since our UI modules need access to certain methods here. -/// - -namespace com.keyman { - export class Util { - // Generalized component event registration - device: Device; - activeDevice: Device; - physicalDevice: Device; - - waiting: HTMLDivElement; // The element displayed for util.wait and util.alert. - - private embeddedFonts: any[] = []; // Array of currently embedded font descriptor entries. (Is it just a string?) - - // Consider refactoring keymanweb.options to within Util. - - private keyman: KeymanBase; // Closure doesn't like relying on the global object from within a class def. - - constructor(keyman: any) { - this.initDevices(); - - this.keyman = keyman; - } - - initDevices(): void { - this.device = new Device(); - this.physicalDevice = new Device(); - this.activeDevice = this.device; - - // Initialize the true device values. - this.device.detect(); - - /* DEBUG: Force touch device (Build 360) - - device.touchable = true; - device.browser = 'safari'; - device.formFactor = 'tablet'; - device.OS = 'iOS'; - - END DEBUG */ - - /* If we've made it to this point of initialization and aren't anything else, KeymanWeb assumes - * we're a desktop. Since we don't yet support desktops with touch-based input, we disable it here. - */ - if(this.device.formFactor == 'desktop') { - this.device.touchable = false; - } - - /** - * Represents hardware-based keystrokes regardless of the 'true' device, facilitating hardware keyboard input - * whenever touch-based input is available. - */ - this.physicalDevice = new Device(); - this.physicalDevice.touchable = false; - this.physicalDevice.browser = this.device.browser; - this.physicalDevice.formFactor = 'desktop'; - this.physicalDevice.OS = this.device.OS; - } - - // ----------------------------------------------- - - /** - * More reliable way of identifying element class - * @param {Object} e HTML element - * @param {string} name class name - * @return {boolean} - */ - hasClass(e: HTMLElement, name: string): boolean { - var className = " " + name + " "; - return (" " + e.className + " ").replace(/[\n\t\r\f]/g, " ").indexOf(className) >= 0; - } - - /** - * Function setOption - * Scope Public - * @param {string} optionName Name of option - * @param {*=} value Value of option - * Description Sets value of named option - */ - setOption(optionName,value) { - this.keyman.options[optionName] = value; - } - /** - * Select start handler (to replace multiple inline handlers) (Build 360) - */ - selectStartHandler = function() { - return false; - } - - /** - * Function _CancelMouse - * Scope Private - * @param {Object} e event - * @return {boolean} always false - * Description Closes mouse click event - */ - _CancelMouse=function(e: MouseEvent) { - if(e && e.preventDefault) { - e.preventDefault(); - } - if(e) { - e.cancelBubble=true; - } // I2409 - Avoid focus loss for visual keyboard events - - return false; - } - - /** - * Get browser-independent computed style integer value for element (Build 349) - * - * @param {Element} e HTML element - * @param {string} s CSS style name - * @param {number=} d default value if NaN - * @return {number} integer value of style - */ - getStyleInt(e: HTMLElement, s: string, d?: number): number { - var x=parseInt(this.getStyleValue(e,s),10); - if(!isNaN(x)) { - return x; - } - - // Return the default value if numeric, else 0 - if(typeof(d) == 'number') { - return d; - } else { - return 0; - } - } - - /** - * Return height of URL bar on mobile devices, if visible - * TODO: This does not seem to be right, so is not currently used - * - * @return {number} - */ - barHeight(): number { - var dy=0; - if(this.device.formFactor == 'phone') { - dy=screen.height/2-window.innerHeight-(this.landscapeView() ? this.device.dyLandscape: this.device.dyPortrait); - } - return dy; - } - - /** - * Function _EncodeEntities - * Scope Private - * @param {string} P_txt string to be encoded - * @return {string} encoded (html-safe) string - * Description Encode angle brackets and ampersand in text string - */ - _EncodeEntities(P_txt: string): string { - return P_txt.replace('&','&').replace('<','<').replace('>','>'); // I1452 part 2 - } - - /** - * Function createShim - * Scope Public - * Description [Deprecated] Create an IFRAME element to go between KMW and drop down (to fix IE6 bug) - * @deprecated - */ - createShim(): void { // I1476 - Handle SELECT overlapping BEGIN - console.warn("The util.createShim function is deprecated, as its old functionality is no longer needed. " + - "It and references to its previously-produced shims may be safely removed."); - return; - } - - // I1476 - Handle SELECT overlapping BEGIN - - /** - * Function showShim - * Scope Public - * @param {Object} Pvkbd Visual keyboard DIV element - * @param {Object} Pframe IFRAME shim element - * @param {Object} Phelp OSK Help DIV element - * Description [Deprecated] Display iFrame under OSK at its currently defined position, to allow OSK to overlap SELECT elements (IE6 fix) - * @deprecated - */ - showShim(Pvkbd: HTMLElement, Pframe: HTMLElement, Phelp: HTMLElement) { - console.warn("The util.showShim function is deprecated, as its old functionality is no longer needed. It may be safely removed."); - } - - /** - * Function hideShim - * Scope Public - * @param {Object} Pframe IFRAME shim element - * Description [Deprecated] Hide iFrame shim containing OSK - * @deprecated - */ - hideShim(Pframe: HTMLElement) { - console.warn("The util.hideShim function is deprecated, as its old functionality is no longer needed. It may be safely removed."); - } - - /** - * Function rgba - * Scope Public - * @param {Object} s element style object - * @param {number} r red value, 0-255 - * @param {number} g green value, 0-255 - * @param {number} b blue value, 0-255 - * @param {number} a opacity value, 0-1.0 - * @return {string} background colour style string - * Description Browser-independent alpha-channel management - */ - rgba(s: HTMLStyleElement, r:number, g:number, b:number, a:number): string { - var bgColor='transparent'; - try { - bgColor='rgba('+r+','+g+','+b+','+a+')'; - } catch(ex) { - bgColor='rgb('+r+','+g+','+b+')'; - } - - return bgColor; - } - - /** - * Function toNumber - * Scope Public - * @param {string} s numeric string - * @param {number} dflt default value - * @return {number} - * Description Return string converted to integer or default value - */ - toNumber(s: string, dflt: number): number { - var x = parseInt(s,10); - return isNaN(x) ? dflt : x; - } - - /** - * Function toNumber - * Scope Public - * @param {string} s numeric string - * @param {number} dflt default value - * @return {number} - * Description Return string converted to real value or default value - */ - toFloat(s: string, dflt: number): number { - var x = parseFloat(s); - return isNaN(x) ? dflt : x; - } - - /** - * Function toNzString - * Scope Public - * @param {*} item variable to test - * @param {?*=} dflt default value - * @return {*} - * Description Test if a variable is null, false, empty string, or undefined, and return as string - */ - nzString(item: any, dflt: any): string { - var dfltValue = ''; - if(arguments.length > 1) { - dfltValue = dflt; - } - - if(typeof(item) == 'undefined') { - return dfltValue; - } - - if(item == null) { - return dfltValue; - } - - if(item == 0 || item == '') { - return dfltValue; - } - - return ''+item; - } - - /** - * Return the event target for any browser - * - * @param {Event} e event - * @return {Object} HTML element - */ - eventTarget(e: Event): EventTarget { - if(!e) { - return null; - } else if (e.target) { // most browsers - return e.target; - } else if (e.srcElement) { - return e.srcElement; - } else { - return null; // shouldn't happen! - } - } - - /** - * Return the event type for any browser - * - * @param {Event} e event - * @return {string} type of event - */ - eventType(e: Event): string { - if(e && e.type) { // most browsers - return e.type; - } else { - return ''; // shouldn't happen! - } - } - - shutdown() { - // Remove all event-handler references rooted in KMW events. - this.events = {}; - } - - /** - * Get path of keymanweb script, for relative references - * - * *** This is not currently used, but may possibly be needed if *** - * *** script identification during loading proves unreliable. *** - * - * @param {string} sName filename prefix - * @return {string} path to source, with trailing slash - **/ - myPath(sName: string): string { - var i, scripts=document.getElementsByTagName('script'), ss; - - for(i=0; i= 0) { - return ss.src.substr(0,ss.src.lastIndexOf('/')+1); - } - } - - return ''; - } - - // Prepend the appropriate protocol if not included in path - prependProtocol(path: string): string { - var pattern = new RegExp('^https?:'); - - if(pattern.test(path)) { - return path; - } else if(path.substr(0,2) == '//') { - return this.keyman.protocol+path; - } else if(path.substr(0,1) == '/') { - return this.keyman.protocol+'/'+path; - } else { - return this.keyman.protocol+'//'+path; - } - } - } -} - -import Util = com.keyman.Util; -- GitLab From 4c24618c56dd57e812c81f4285188c4711c75ba6 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 11:42:46 +0700 Subject: [PATCH 256/386] chore(web): load/unload ui events --- web/src/app/browser/src/keymanEngine.ts | 3 ++ web/src/engine/main/src/legacyAPIEvents.ts | 5 ++- .../engine/namespaced-main/kmwuimanager.ts | 32 ------------------- 3 files changed, 7 insertions(+), 33 deletions(-) delete mode 100644 web/src/engine/namespaced-main/kmwuimanager.ts diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index f6581fba7f..47824dedc6 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -177,6 +177,7 @@ export default class KeymanEngine extends KeymanEngineBase boolean; + 'loaduserinterface': (p: {}) => boolean; + 'unloaduserinterface': (p: {}) => boolean; + // TODO: more of the documented API events. Note that any remaining events not seen here // yet go unused within the mobile apps. -} \ No newline at end of file +} diff --git a/web/src/engine/namespaced-main/kmwuimanager.ts b/web/src/engine/namespaced-main/kmwuimanager.ts deleted file mode 100644 index b78ad4d899..0000000000 --- a/web/src/engine/namespaced-main/kmwuimanager.ts +++ /dev/null @@ -1,32 +0,0 @@ -namespace com.keyman { - export class UIManager { - keyman: KeymanBase; - - constructor(keyman: KeymanBase) { - this.keyman = keyman; - } - - /** - * Function doLoad - * Scope Private - * @return {boolean} - * Description Execute UI initialization code after loading the UI - * // Appears to be unused; could be eliminated? Though, doUnload IS used. - */ - doLoad() { - var p={}; - return this.keyman.util.callEvent('kmw.loaduserinterface',p); - } - - /** - * Function doUnload - * Scope Private - * @return {boolean} - * Description Execute UI cleanup code before unloading the UI (may not be required?) - */ - doUnload = function() { - var p={}; - return this.keyman.util.callEvent('kmw.unloaduserinterface',p); - } - } -} \ No newline at end of file -- GitLab From 070dc66483c9a9f9ea717bfe315d3a431f4ff47a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 11:44:11 +0700 Subject: [PATCH 257/386] chore(web): a bit of cleanup --- .../engine/namespaced-main/dom/domManager.ts | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/web/src/engine/namespaced-main/dom/domManager.ts b/web/src/engine/namespaced-main/dom/domManager.ts index 9992eb257b..2bcb6da710 100644 --- a/web/src/engine/namespaced-main/dom/domManager.ts +++ b/web/src/engine/namespaced-main/dom/domManager.ts @@ -77,42 +77,6 @@ namespace com.keyman.dom { } } - /* ------ Defines independent, per-control keyboard setting behavior for the API. ------ */ - - /** - * Function getKeyboardForControl - * Scope Public - * @param {Element} Pelem Control element - * @return {string|null} The independently-managed keyboard for the control. - * Description Returns the keyboard ID of the current independently-managed keyboard for this control. - * If it is currently following the global keyboard setting, returns null instead. - */ - getKeyboardForControl(Pelem: HTMLElement): string { - if(!this.isAttached(Pelem)) { - console.error("KeymanWeb is not attached to element " + Pelem); - return null; - } else { - return Pelem._kmwAttachment.keyboard; - } - } - - /** - * Function getLanguageForControl - * Scope Public - * @param {Element} Pelem Control element - * @return {string|null} The independently-managed keyboard for the control. - * Description Returns the language code used with the current independently-managed keyboard for this control. - * If it is currently following the global keyboard setting, returns null instead. - */ - getLanguageForControl(Pelem: HTMLElement): string { - if(!this.isAttached(Pelem)) { - console.error("KeymanWeb is not attached to element " + Pelem); - return null; - } else { - return Pelem._kmwAttachment.languageCode; // Should we have a version for the language code, too? - } - } - /* ------ End independent, per-control keyboard setting behavior definitions. ------ */ get activeElement(): HTMLElement { -- GitLab From f0fe7da216ee820f62609e9e875d7f00de4fd13a Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Tue, 23 May 2023 11:47:27 +0700 Subject: [PATCH 258/386] chore(web): Apply suggestions from code review Co-authored-by: Marc Durdin --- web/src/app/browser/src/keymanEngine.ts | 8 +++----- web/src/app/browser/src/oskConfiguration.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 5da9365dd4..10dc149dc7 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -34,11 +34,9 @@ export class KeymanEngine extends KeymanEngineBase= t) { + dy -= (window.innerHeight - this.osk._Box.offsetHeight - e.offsetHeight - 2); if(dy < 0) { dy=0; } diff --git a/web/src/app/browser/src/oskConfiguration.ts b/web/src/app/browser/src/oskConfiguration.ts index 089c727916..b21abd07de 100644 --- a/web/src/app/browser/src/oskConfiguration.ts +++ b/web/src/app/browser/src/oskConfiguration.ts @@ -35,7 +35,7 @@ export function setupOskListeners(engine: KeymanEngine, osk: OSKView, contextMan osk.on('showBuild', () => { internalAlert('KeymanWeb Version ' + KEYMAN_VERSION.VERSION + '

' - +'Copyright © 2021 SIL International'); + +'Copyright © 2007-2023 SIL International'); }); osk.on('dragMove', async (promise) => { -- GitLab From ef6d159175308fb26f4bf1c60c95133ed56d86f4 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 11:52:28 +0700 Subject: [PATCH 259/386] fix(web): addresses FIXME comment --- .../app/browser/src/hardwareEventKeyboard.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/web/src/app/browser/src/hardwareEventKeyboard.ts b/web/src/app/browser/src/hardwareEventKeyboard.ts index 06f2640643..c58c2c5a32 100644 --- a/web/src/app/browser/src/hardwareEventKeyboard.ts +++ b/web/src/app/browser/src/hardwareEventKeyboard.ts @@ -445,28 +445,6 @@ export default class HardwareEventKeyboard extends HardKeyboard { // Only reached if it's a mnemonic keyboard. - /* FIXME: delete this comment once it's a proper PR comment or in the description! - * - * After running a _deep_ `git blame` trace on the following section, I arrived at #1525: - * https://github.com/keymanapp/keyman/pull/1525/files#diff-4958568b7fd00cf53893ab07e55d4e23777c2f05aa77631771ef08717ea7321bL861 - * - * It turns out that there was a _slight_ difference in how _KeyPress and _KeyDown referred to - * the KeymanWeb keyboard-interface object... and that difference appears to have caused me to - * not update the corresponding line, as seen later in #2892: - * https://github.com/keymanapp/keyman/pull/2892/files#diff-30a24e9475a0b72843ac5c621aac9fe9f62866e1ce4a94c0022d8c204e687652R265 - * - note that `keyDown`'s version had already been updated by this time, seemingly by #1802 - * during the implementation of fat-fingering for use in predictive-text. - * - I simply... missed that default keystroke output might matter here at the (#1802) time. - * - default keystroke emulation was added during development of the same b/c it must be handled - * by the engine, rather than browser, to be accessible to predictive text. - * - * My conclusion: the pattern can be uniform for both cases, even if the default-keystroke bit - * could still be handled by the browser here (because no non-app/webview predictive text). As - * it makes the design simpler, "can be uniform" becomes "should be uniform" here, hence the - * significant line change. - * - * END FIXME / comment - */ let resultCapture: { preventDefaultKeystroke?: boolean } = {}; // Should only be run if `preventDefaultKeystroke` is required by the following conditional -- GitLab From ba0373864df2678c41880d807a3e9c57f230d090 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 12:00:03 +0700 Subject: [PATCH 260/386] chore(web): applies suggestions from code review + necessary corollaries --- web/src/app/browser/src/configuration.ts | 8 +- web/src/app/browser/src/contextManager.ts | 12 +- web/src/app/browser/src/oskConfiguration.ts | 2 +- web/src/app/browser/src/utilApiEndpoint.ts | 346 ++++++++++++++++++++ 4 files changed, 357 insertions(+), 11 deletions(-) create mode 100644 web/src/app/browser/src/utilApiEndpoint.ts diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index c45396a2d9..14cd9aac9d 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -8,7 +8,7 @@ export class BrowserConfiguration extends EngineConfiguration { private _ui: string; private _attachType: string; - private alertHost?: AlertHost; + private _alertHost?: AlertHost; initialize(options: Required) { this.initialize(options); @@ -16,7 +16,7 @@ export class BrowserConfiguration extends EngineConfiguration { this._ui = options.ui; this._attachType = options.attachType; if(options.useAlerts) { - this.alertHost = new AlertHost(); + this._alertHost = new AlertHost(); } } @@ -24,8 +24,8 @@ export class BrowserConfiguration extends EngineConfiguration { return this._attachType; } - get signalUser(): AlertHost | undefined { - return this.alertHost; + get alertHost(): AlertHost | undefined { + return this._alertHost; } debugReport(): Record { diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 9fb18962a9..49561f57c3 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -69,10 +69,10 @@ export default class ContextManager extends ContextManagerBase { - this.engineConfig.signalUser?.wait('Installing keyboard
' + stub.name); + this.engineConfig.alertHost?.wait('Installing keyboard
' + stub.name); completion.then(() => { - this.engineConfig.signalUser?.wait(); // cancels the wait. + this.engineConfig.alertHost?.wait(); // cancels the wait. }); }); @@ -326,7 +326,7 @@ export default class ContextManager extends ContextManagerBase { - engine.config.signalUser?.alert('KeymanWeb Version ' + KEYMAN_VERSION.VERSION + '

' + engine.config.alertHost?.alert('KeymanWeb Version ' + KEYMAN_VERSION.VERSION + '

' +'Copyright © 2007-2023 SIL International'); }); diff --git a/web/src/app/browser/src/utilApiEndpoint.ts b/web/src/app/browser/src/utilApiEndpoint.ts new file mode 100644 index 0000000000..b1feb07c9f --- /dev/null +++ b/web/src/app/browser/src/utilApiEndpoint.ts @@ -0,0 +1,346 @@ + +import { + CookieSerializer, + createStyleSheet, + getAbsoluteX, + getAbsoluteY, + StylesheetManager + } from "keyman/engine/dom-utils"; +import { DomEventTracker } from "keyman/engine/events"; +import { BrowserConfiguration, BrowserInitOptionSpec } from "./configuration.js"; +import { getStyleValue } from "./utils/getStyleValue.js"; +import { AlertHost } from "./utils/alertHost.js"; + +/** + * Calls document.createElement for the specified node type and also applies + * 'user-select: none' styling to the new element. + * @param nodeName + * @returns + */ +export function createUnselectableElement(nodeName:E) { + const e = document.createElement(nodeName); + e.style.userSelect="none"; + return e; +} + +export class UtilApiEndpoint { + readonly config: BrowserConfiguration; + private readonly stylesheetManager: StylesheetManager; + private readonly domEventTracker: DomEventTracker; + private _alertHost: AlertHost; + + constructor(config: BrowserConfiguration) { + this.config = config; + this.stylesheetManager = new StylesheetManager(document.body, config.applyCacheBusting); + this.domEventTracker = new DomEventTracker(); + } + + readonly getAbsoluteX = getAbsoluteX; + readonly getAbsoluteY = getAbsoluteY; + + // These four were renamed, but we need to maintain their legacy names. + readonly _GetAbsoluteX = getAbsoluteX; + readonly _GetAbsoluteY = getAbsoluteY; + readonly _GetAbsolute = this.getAbsolute; + readonly toNzString = this.nzString; + + /** + * Expose the touchable state for UIs - will disable external UIs entirely + **/ + isTouchDevice(): boolean { + return this.config.hostDevice.touchable; + } + + getAbsolute(elem: HTMLElement): { x: number, y: number } { + return { + x: getAbsoluteX(elem), + y: getAbsoluteY(elem) + }; + } + + /** + * Calls document.createElement for the specified node type and also applies + * 'user-select: none' styling to the new element. + * @param nodeName + * @returns + */ + readonly createElement = createUnselectableElement; + + /** + * Function getOption + * Scope Public + * @param {string} optionName Name of option + * @param {*=} dflt Default value of option + * @return {*} + * Description Returns value of named option + */ + getOption(optionName: keyof BrowserInitOptionSpec, dflt?:any): any { + if(optionName in this.config.paths) { + return this.config.paths[optionName]; + } else if(optionName in this.config.options) { + return this.config.options[optionName]; + } else if(arguments.length > 1) { + return dflt; + } else { + return ''; + } + } + + setOption(optionName: keyof BrowserInitOptionSpec, value: any): void { + switch(optionName) { + case 'attachType': + // 16.0 & before: did nothing. + // Fixable for 17.0 with some extra work, but the changes would likely be enough to + // merit a focused PR. It's not 100% straightforward. + break; + case 'ui': + // 16.0 & before: relies on the Float UI to passively pick up on any changes. + // Only appears to be effective before the Float UI initializes. + break; + case 'useAlerts': + this.config.alertHost = (value ? new AlertHost() : null); + break; + case 'setActiveOnRegister': + this.config.activateFirstKeyboard = !!value; + break; + case 'spacebarText': + this.config.spacebarText = value; + break; + default: + throw new Error("Path-related options may not be changed after the engine has initialized."); + } + } + + /** + * Document cookie parsing for use by kernel, OSK, UI etc. + * + * @param {string=} cn cookie name (optional) + * @return {Object} array of names and strings, or array of variables and values + */ + loadCookie>(cn?: string) { + const cookie = new CookieSerializer(cn); + return cookie.load(decodeURIComponent); + } + + /** + * Standard cookie saving for use by kernel, OSK, UI etc. + * + * @param {string} cn name of cookie + * @param {Object} cv object with array of named arguments and values + */ + saveCookie>(cn: string, cv: CookieType) { + const cookie = new CookieSerializer(cn); + cookie.save(cv, encodeURIComponent); + } + + /** + * Add a stylesheet to a page programmatically, for use by the OSK, the UI or the page creator + * + * @param {string} s style string + * @return {Object} returns the object reference + **/ + addStyleSheet(s: string): HTMLStyleElement { + const styleSheet = createStyleSheet(s); + this.stylesheetManager.linkStylesheet(styleSheet); + + return styleSheet; + } + + /** + * Remove a stylesheet element + * + * @param {Object} s style sheet reference + * @return {boolean} false if element is not a style sheet + **/ + removeStyleSheet(s: HTMLStyleElement) { + return this.stylesheetManager.unlink(s); + } + + /** + * Add a reference to an external stylesheet file + * + * @param {string} s path to stylesheet file + */ + linkStyleSheet(s: string): void { + this.stylesheetManager.linkExternalSheet(s); + } + + // Possible alternative: https://www.npmjs.com/package/language-tags + // This would necessitate linking in a npm module into compiled KeymanWeb, though. + getLanguageCodes(lgCode: string): string[] { + if(lgCode.indexOf('-')==-1) { + return [lgCode]; + } else { + return lgCode.split('-'); + } + } + + /** + * Function attachDOMEvent: Note for most browsers, adds an event to a chain, doesn't stop existing events + * Scope Public + * @param {Object} Pelem Element (or IFrame-internal Document) to which event is being attached + * @param {string} Peventname Name of event without 'on' prefix + * @param {function(Object)} Phandler Event handler for event + * @param {boolean=} PuseCapture True only if event to be handled on way to target element + * Description Attaches event handler to element DOM event + */ + attachDOMEvent( + Pelem: Window, + Peventname: K, + Phandler: (ev: WindowEventMap[K]) => any, + PuseCapture?: boolean + ): void; + attachDOMEvent( + Pelem: Document, + Peventname: K, + Phandler: (ev: DocumentEventMap[K]) => any, + PuseCapture?: boolean + ): void; + attachDOMEvent( + Pelem: HTMLElement, + Peventname: K, + Phandler: (ev: HTMLElementEventMap[K]) => any, + PuseCapture?: boolean + ): void; + attachDOMEvent(Pelem: EventTarget, Peventname: string, Phandler: (Object) => boolean, PuseCapture?: boolean): void { + // TS can't quite track the type inference forwarding here. + this.domEventTracker.attachDOMEvent(Pelem as any, Peventname as any, Phandler, PuseCapture); + } + + /** + * Function detachDOMEvent + * Scope Public + * @param {Object} Pelem Element from which event is being detached + * @param {string} Peventname Name of event without 'on' prefix + * @param {function(Object)} Phandler Event handler for event + * @param {boolean=} PuseCapture True if event was being handled on way to target element + * Description Detaches event handler from element [to prevent memory leaks] + */ + detachDOMEvent( + Pelem: Window, + Peventname: K, + Phandler: (ev: WindowEventMap[K]) => any, + PuseCapture?: boolean + ): void; + detachDOMEvent( + Pelem: Document, + Peventname: K, + Phandler: (ev: DocumentEventMap[K]) => any, + PuseCapture?: boolean + ): void; + detachDOMEvent( + Pelem: HTMLElement, + Peventname: K, + Phandler: (ev: HTMLElementEventMap[K]) => any, + PuseCapture?: boolean + ): void; + detachDOMEvent(Pelem: EventTarget, Peventname: string, Phandler: (Object) => boolean, PuseCapture?: boolean): void { + // TS can't quite track the type inference forwarding here. + this.domEventTracker.detachDOMEvent(Pelem as any, Peventname as any, Phandler, PuseCapture); + } + + getStyleValue = getStyleValue; + + private get alertHost(): AlertHost { + if(this.config.alertHost) { + return this.config.alertHost; + } else if(!this._alertHost) { + // Lazy init: if KMW is set to not show alerts, we try not to initialize the alert host. + // If the .alert API is called, though, we have no choice. + this._alertHost = new AlertHost(); + } + + return this._alertHost; + } + + alert(s: string, fn: () => void) { + this.alertHost.alert(s, fn); + } + + /** + * Function toNzString + * Scope Public + * @param {*} item variable to test + * @param {?*=} dflt default value + * @return {*} + * Description Test if a variable is null, false, empty string, or undefined, and return as string + */ + nzString(item: any, dflt: string): string { + // // ... is this whole thing essentially just: + // return '' + (item || dflt || ''); + // // ? + + let dfltValue = ''; + if(arguments.length > 1) { + dfltValue = dflt; + } + + if(typeof(item) == 'undefined') { + return dfltValue; + } + + if(item == null) { + return dfltValue; + } + + if(item == 0 || item == '') { + return dfltValue; + } + + return ''+item; + } + + /** + * Function toNumber + * Scope Public + * @param {string} s numeric string + * @param {number} dflt default value + * @return {number} + * Description Return string converted to integer or default value + */ + toNumber(s: string, dflt: number): number { + const x = parseInt(s,10); + return isNaN(x) ? dflt : x; + } + + /** + * Function toNumber + * Scope Public + * @param {string} s numeric string + * @param {number} dflt default value + * @return {number} + * Description Return string converted to real value or default value + */ + toFloat(s: string, dflt: number): number { + const x = parseFloat(s); + return isNaN(x) ? dflt : x; + } + + /** + * Function rgba + * Scope Public + * @param {Object} s element style object + * @param {number} r red value, 0-255 + * @param {number} g green value, 0-255 + * @param {number} b blue value, 0-255 + * @param {number} a opacity value, 0-1.0 + * @return {string} background colour style string + * Description Browser-independent alpha-channel management + */ + rgba(s: HTMLStyleElement, r:number, g:number, b:number, a:number): string { + let bgColor='transparent'; + try { + bgColor='rgba('+r+','+g+','+b+','+a+')'; + } catch(ex) { + bgColor='rgb('+r+','+g+','+b+')'; + } + + return bgColor; + } + + shutdown() { + this.stylesheetManager?.unlinkAll(); + this.domEventTracker?.shutdown(); + this._alertHost?.shutdown(); + } +} \ No newline at end of file -- GitLab From e01614e0f926839b687a8e885d778141c3d57974 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 12:07:48 +0700 Subject: [PATCH 261/386] chore(web): undone domManager.ts cleanup --- .../engine/namespaced-main/dom/domManager.ts | 150 ------------------ web/src/engine/namespaced-main/dom/utils.ts | 34 ---- 2 files changed, 184 deletions(-) delete mode 100644 web/src/engine/namespaced-main/dom/utils.ts diff --git a/web/src/engine/namespaced-main/dom/domManager.ts b/web/src/engine/namespaced-main/dom/domManager.ts index 2bcb6da710..1c2a59f751 100644 --- a/web/src/engine/namespaced-main/dom/domManager.ts +++ b/web/src/engine/namespaced-main/dom/domManager.ts @@ -77,88 +77,6 @@ namespace com.keyman.dom { } } - /* ------ End independent, per-control keyboard setting behavior definitions. ------ */ - - get activeElement(): HTMLElement { - return DOMEventHandlers.states._activeElement; - } - - set activeElement(Pelem: HTMLElement) { - DOMEventHandlers.states._activeElement = Pelem; - - let maintainingFocus = focusAssistant.maintainingFocus; - - // Hide the OSK when the control is blurred, unless the UI is being temporarily selected - const osk = this.keyman.osk; - // const device = this.keyman.util.device; - - if(osk) { - const target = Pelem?._kmwAttachment?.interface || null; - if(osk && osk.activationModel instanceof TwoStateActivator && (target || !maintainingFocus)) { - // Do not unset the field if the UI is activated. - osk.activationCondition = target; - } - } - } - - /** - * Set the active input element directly optionally setting focus - * - * @param {Object|string} e element id or element - * @param {boolean=} setFocus optionally set focus (KMEW-123) - */ - setActiveElement(e: string|HTMLElement, setFocus?: boolean) { - if(typeof e == "string") { // Can't instanceof string, and String is a different type. - e = document.getElementById(e); - } - - if(this.keyman.isEmbedded) { - // If we're in embedded mode, auto-attach to the element specified by the page. - if(!this.isAttached(e)) { - this.attachToControl(e); - } - // Non-attached elements cannot be set as active. - } else if(!this.isAttached(e)) { - console.warn("Cannot set an element KMW is not attached to as the active element."); - return; - } - - // If we're changing controls, don't forget to properly manage the keyboard settings! - // It's only an issue on 'native' (non-embedded) code paths. - if(!this.keyman.isEmbedded) { - this.keyman.touchAliasing._BlurKeyboardSettings(this.keyman.domManager.lastActiveElement); - } - - // No need to reset context if we stay within the same element. - if(this.activeElement != e) { - this.keyman['resetContext'](e as HTMLElement); - } - - this.activeElement = this.lastActiveElement = e; - if(!this.keyman.isEmbedded) { - this.keyman.touchAliasing._FocusKeyboardSettings(e, false); - } - - // Allow external focusing KMEW-123 - if(arguments.length > 1 && setFocus) { - this.focusLastActiveElement(); - } - - // Let the keyboard do its initial group processing - //console.log('processNewContextEvent [not] called from setActiveElement'); - com.keyman.singleton.core.processNewContextEvent(dom.Utils.getOutputTarget(e)); - } - - /** Sets the active input element only if it is presently null. - * - * @param {Element} - */ - initActiveElement(Lelem: HTMLElement) { - if(this.activeElement == null) { - this.activeElement = Lelem; - } - } - /* ----------------------- Editable IFrame methods ------------------- */ /** @@ -176,56 +94,6 @@ namespace com.keyman.dom { /* ----------------------- Initialization methods ------------------ */ - /** - * Get the user-specified (or default) font for the first mapped input or textarea element - * before applying any keymanweb styles or classes - * - * @return {string} - */ - getBaseFont() { - var util = this.keyman.util; - var ipInput = document.getElementsByTagName<'input'>('input'), - ipTextArea=document.getElementsByTagName<'textarea'>('textarea'), - n=0,fs,fsDefault='Arial,sans-serif'; - - // Find the first input element (if it exists) - if(ipInput.length == 0 && ipTextArea.length == 0) { - n=0; - } else if(ipInput.length > 0 && ipTextArea.length == 0) { - n=1; - } else if(ipInput.length == 0 && ipTextArea.length > 0) { - n=2; - } else { - var firstInput = ipInput[0]; - var firstTextArea = ipTextArea[0]; - - if(firstInput.offsetTop < firstTextArea.offsetTop) { - n=1; - } else if(firstInput.offsetTop > firstTextArea.offsetTop) { - n=2; - } else if(firstInput.offsetLeft < firstTextArea.offsetLeft) { - n=1; - } else if(firstInput.offsetLeft > firstTextArea.offsetLeft) { - n=2; - } - } - - // Grab that font! - switch(n) { - case 0: - fs=fsDefault; - case 1: - fs=util.getStyleValue(ipInput[0],'font-family'); - case 2: - fs=util.getStyleValue(ipTextArea[0],'font-family'); - } - if(typeof(fs) == 'undefined' || fs == 'monospace') { - fs=fsDefault; - } - - return fs; - } - /** * Function Initialization * Scope Public @@ -340,23 +208,5 @@ namespace com.keyman.dom { this.keyman.setInitialized(2); return Promise.resolve(); }.bind(this); - - /** - * Initialize the desktop user interface as soon as it is ready - */ - initializeUI() { - if(this.keyman.ui && this.keyman.ui['initialize'] instanceof Function) { - this.keyman.ui['initialize'](); - // Display the OSK (again) if enabled, in order to set its position correctly after - // adding the UI to the page - this.keyman.osk.present(); - } else if(this.keyman.isEmbedded) { - // UI modules aren't utilized in embedded mode. There's nothing to init, so we simply - // return instead of waiting for a UI module that will never come. - return; - } else { - window.setTimeout(this.initializeUI.bind(this),1000); - } - } } } \ No newline at end of file diff --git a/web/src/engine/namespaced-main/dom/utils.ts b/web/src/engine/namespaced-main/dom/utils.ts deleted file mode 100644 index 8219d966e4..0000000000 --- a/web/src/engine/namespaced-main/dom/utils.ts +++ /dev/null @@ -1,34 +0,0 @@ -namespace com.keyman.dom { - // NOTE: - // - instanceOf -> element-wrappers, now called nestedInstanceOf - // - forceScroll -> element-wrappers, but I believe it's only ever called from there. - - // Defines DOM-related utility functions that are not reliant on KMW's internal state. - export class Utils { - /** - * Finds the `OutputTarget` associated with the specified element, or the currently-active element if not specified. - * @param Lelem The element corresponding to the desired `OutputTarget` - */ - static getOutputTarget(Lelem?: HTMLElement): dom.targets.OutputTarget { - if(!Lelem) { - // Since this may be used to test modularly, we can't depend on the existence of the KMW global. - let keyman = com.keyman['singleton']; - if(keyman) { - Lelem = keyman.domManager.lastActiveElement; - } - - if(!Lelem) { - // If we're trying to find an active target but one doesn't exist, just return null. - return null; - } - } - - // If we were provided an element or found an active element but it's improperly attached, that should cause an error. - if(Lelem._kmwAttachment && Lelem._kmwAttachment.interface) { - return Lelem._kmwAttachment.interface; - } else { - throw new Error("OSK could not find element output target data!"); - } - } - } -} \ No newline at end of file -- GitLab From 699ddd058ad42856ea591325dbfd2f49a9e8b9da Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 12:09:35 +0700 Subject: [PATCH 262/386] chore(web): significant kmwkeyboards.ts cleanup --- .../namespaced-main/keyboards/kmwkeyboards.ts | 405 ------------------ 1 file changed, 405 deletions(-) diff --git a/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts b/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts index 2668360fe9..a54495b65a 100644 --- a/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts +++ b/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts @@ -5,12 +5,6 @@ namespace com.keyman.keyboards { stores: {[text: string]: text.ComplexKeyboardStore} = {}; } - export interface KeyboardChangeData { - ['internalName']: string; - ['languageCode']: string; - ['indirect']: boolean; - } - export class KeyboardManager { keymanweb: KeymanBase; @@ -38,52 +32,6 @@ namespace com.keyman.keyboards { }); } - getActiveKeyboardName(): string { - let core = com.keyman.singleton.core; - return core.activeKeyboard ? core.activeKeyboard.id : ''; - } - - getActiveLanguage(fullName?: boolean): string { - if(this.activeStub == null) { - return ''; - } else if(fullName) { - return this.activeStub['KL']; - } else { - return this.activeStub['KLC']; - } - } - - /** - * Create or update a keyboard meta-data 'stub' during keyboard registration - * - * Cross-reference with https://help.keyman.com/developer/engine/web/11.0/reference/core/addKeyboards. - * - * @param {Object} kp (partial) keyboard meta-data object (`spec` object) - * @param {Object} lp language object (`spec.languages` object) - * @param {Object} options KeymanCloud callback options - **/ - mergeStub(kp: any, lp: any, options) { - var sp: KeyboardStub = this.findStub(kp['id'], lp['id']); - var isNew: boolean = false; - - // - - // BUT: the rest of this, with events? Yeah, we need to be a little more on top of that. - - // Update the UI - this.doKeyboardRegistered(sp['KI'],sp['KL'],sp['KN'],sp['KLC'],sp['KP']); - - // If we have no activeStub because there were no stubs, set the new keyboard as active. - // Do not trigger on merges. - if(!this.activeStub && isNew && this.keyboardStubs.length == 1 && this.keymanweb.options['setActiveOnRegister']=='true') { - // #676: We call _SetActiveKeyboard so we can avoid overwriting - // cookies that determine our active keyboard at page load time - this.doBeforeKeyboardChange(sp['KI'], sp['KLC']); - this._SetActiveKeyboard(sp['KI'], sp['KLC'], false); - this.doKeyboardChange(sp['KI'], sp['KLC']); - } - } - // Called on the embedded path at the end of its initialization. setDefaultKeyboard() { if(this.keyboardStubs.length > 0) { @@ -95,50 +43,6 @@ namespace com.keyman.keyboards { } } - /** - * Allow to change active keyboard by (internal) keyboard name - * - * @param {string} PInternalName Internal name - * @param {string} PLgCode Language code - */ - setActiveKeyboard(PInternalName: string, PLgCode: string): Promise { - //TODO: This does not make sense: the callbacks should be in _SetActiveKeyboard, not here, - // since this is always called FROM the UI, which should not need notification. - // If UI callbacks are needed at all, they should be within _SetActiveKeyboard - - // Skip on embedded which namespaces packageID::Keyboard_keyboardID - if(!this.keymanweb.isEmbedded && PInternalName && PInternalName.indexOf("Keyboard_") != 0) { - PInternalName = "Keyboard_" + PInternalName; - } - - this.doBeforeKeyboardChange(PInternalName,PLgCode); - let p: Promise = this._SetActiveKeyboard(PInternalName,PLgCode,true); - if(this.keymanweb.domManager.lastActiveElement != null) { - this.keymanweb.domManager.focusLastActiveElement(); // TODO: Resolve without need for the cast. - } - // If we ever allow PLgCode to be set by default, we can auto-detect the language code - // after the _SetActiveKeyboard call. - // if(!PLgCode && (keymanweb).keyboardManager.activeStub) { - // PLgCode = (keymanweb).keyboardManager.activeStub['KLC']; - // } - const _this = this; - p.then(function() { - // Only mark the keyboard as having changed once the setActiveKeyboard op - // is successful. - _this.doKeyboardChange(PInternalName, PLgCode); - }); - - p.catch(error => { - // Rejection indicates a failure of the keyboard to load. - // - // In case p's rejection is never caught, throwing this error will generate logs that shows up - // in Sentry or in the console, with useful information for debugging either way. - throw new Error("Unable to load keyboard with internal name \"" + PInternalName + "\", language code \"" + PLgCode + "\": " + error); - }); - - return p; - } - /* TODO: why not use util.loadCookie and saveCookie?? */ /** @@ -178,315 +82,6 @@ namespace com.keyman.keyboards { } } - /** - * Function isCJK - * Scope Public - * @param {Object=} k0 - * @return {boolean} - * Description Tests if the keyboard stub uses a pick list (Chinese, Japanese, Korean, etc.) - * (This function accepts either keyboard structure.) - */ - isCJK(k: KeyboardStub) { // I3363 (Build 301) - var lg: string; - if(typeof(k['KLC']) != 'undefined') { - lg = k['KLC']; - } else if(typeof(k['LanguageCode']) != 'undefined') { - lg = k['LanguageCode']; - } - - return ((lg == 'cmn') || (lg == 'jpn') || (lg == 'kor')); - } - - /** - * Function _getKeyboardByID - * Scope Private - * @param {string} keyboardID - * @return {Object|null} - * Description Returns the internal, registered keyboard object - not the stub, but the keyboard itself. - */ - private getKeyboardByID(keyboardID: string):any { - var Li; - for(Li=0; Li=0; j--) { - if('Keyboard_'+arguments[i] == this.keyboardStubs[j]['KI']) { - if('Keyboard_'+arguments[i] == this.getActiveKeyboardName()) { - activeRemoved = true; - } - - anyRemoved = true; - this.keyboardStubs.splice(j,1); - break; - } - } - - if(j < 0) { - success = false; - } - } - - for(i=0; i=0; j--) { - if('Keyboard_'+arguments[i] == this.keyboards[j]['KI']) { - this.keyboards.splice(j, 1); - break; - } - } - } - - if(activeRemoved) { - if(this.keyboardStubs.length > 0) { - // Always reset to the first remaining keyboard - this._SetActiveKeyboard(this.keyboardStubs[0]['KI'],this.keyboardStubs[0]['KLC'],true); - } else { - this._SetActiveKeyboard('', '', false); - } - // This is likely to be triggered by a UI call of some sort, and we need to treat - // this call as such to properly maintain the globalKeyboard setting. - focusAssistant.restoringFocus = true; - } - - if(anyRemoved) { - // Update the UI keyboard menu - this.doKeyboardUnregistered(); - } - - return success; - } - - /** - * Function _registerKeyboard KR - * Scope Public - * @param {Object} Pk Keyboard object - * Description Register and load the keyboard - */ - async _registerKeyboard(Pk) { - // Ensure keymanweb is initialized before continuing to register keyboards - if(!this.keymanweb.initialized) { - await this.deferment; - } - - if(Pk['_kmw']) { - console.error("The keyboard _kmw property is a reserved field for engine use only; this keyboard is invalid."); - return; - } else { - Pk['_kmw'] = new KeyboardTag(); - } - - var Li,Lstub; - - // For package namespacing with KMEA/KMEI. - if(this.keymanweb.isEmbedded) { - this.keymanweb.preserveID(Pk); - } - - // Check if the active stub refers to this keyboard, else find applicable stub - - var Ps=this.activeStub; - var savedActiveStub = this.activeStub; - if(!Ps || !('KI' in Ps) || (Ps['KI'] != Pk['KI'])) { - // Find the first stub for this keyboard - for(Lstub=0; Lstub < this.keyboardStubs.length; Lstub++) { // I1511 - array prototype extended - Ps=this.keyboardStubs[Lstub]; - if(Pk['KI'] == Ps['KI']) { - break; - } - - Ps=null; - } - } - - // Build 369: ensure active stub defined when loading local keyboards - if(this.activeStub == null && Ps != null) { - this.activeStub = Ps; - } - - // Register the stub for this language (unless it is already registered) - // keymanweb.KRS(Ps?Ps:Pk); - - // Test if keyboard already loaded - for(Li=0; Li} 1 if already registered, else null - */ - async _registerStub(Pstub): Promise { - Pstub = { ... Pstub}; // shallow clone the stub object - // Ensure keymanweb is initialized before continuing to register stub - if(!this.keymanweb.initialized) { - await this.deferment; - } - - // The default stub is always the first keyboard stub loaded [and will be ignored by desktop browsers - not for beta, anyway] - if(this.dfltStub == null) { - this.dfltStub=Pstub; - //if(device.formFactor == 'desktop') return 1; //Needs further thought before release - } - - // If no language code has been defined, and no stub has been registered for this keyboard, register with empty string as the language code - if(this.keymanweb.isEmbedded) { - this.keymanweb.namespaceID(Pstub); - } // else leave undefined. It's nice to condition upon. - if(typeof(Pstub['KLC']) == 'undefined') { - Pstub['KLC'] = ''; - } - if(typeof(Pstub['KL']) == 'undefined') { - Pstub['KL'] = 'undefined'; - } - - // Register stub (add to KeyboardStubs array) - this.keyboardStubs=this.keymanweb._push(this.keyboardStubs, Pstub); // TODO: Resolve without need for the cast. - - // TODO: Need to distinguish between initial loading of a large number of stubs and any subsequent loading. - // UI initialization should not be needed for each registration, only at end. - // Reload this keyboard if it was the last active keyboard and - // make any changes needed by UI for new keyboard stub - // (Uncommented for Build 360) - this.doKeyboardRegistered(Pstub['KI'],Pstub['KL'],Pstub['KN'],Pstub['KLC'],Pstub['KP']); - - // If we have no activeStub because there were no stubs, set the new keyboard as active. - // Do not trigger on merges. - if(!this.activeStub && this.dfltStub == Pstub && this.keyboardStubs.length == 1 && this.keymanweb.options['setActiveOnRegister']=='true') { - this.setActiveKeyboard(Pstub['KI'], Pstub['KLC']); - } - - return Promise.resolve(false); - } - - /* - * Last part - the events. - */ - - /** - * Execute external (UI) code needed on registering keyboard, used - * to update each UIs language menu - * - * Note that the argument object is not at present used by any UI, - * since the menu is always fully recreated when needed, but the arguments - * remain defined to allow for possible use in future (Aug 2014) - * - * @param {string} _internalName - * @param {string} _language - * @param {string} _keyboardName - * @param {string} _languageCode - * @param {string=} _packageID Used by KMEA/KMEI to track .kmp related info. - * @return {boolean} - */ - doKeyboardRegistered(_internalName: string, _language: string, _keyboardName: string, - _languageCode: string, _packageID?: string): boolean { - var p={'internalName':_internalName,'language':_language,'keyboardName':_keyboardName,'languageCode':_languageCode}; - - // Utilized only by our embedded codepaths. - if(_packageID) { - p['package'] = _packageID; - } - return this.keymanweb.util.callEvent('kmw.keyboardregistered',p); - } - - /** - * Execute external (UI) code to rebuild menu when deregistering keyboard - * - * @return {boolean} - */ - - doKeyboardUnregistered(): boolean { - var p={}; - return this.keymanweb.util.callEvent('kmw.keyboardregistered',p); - } - - /** - * Execute external (UI) code needed on loading keyboard - * - * @param {string} _internalName - * @return {boolean} - */ - doKeyboardLoaded(_internalName: string): boolean { - var p={}; - p['keyboardName']=_internalName; - return this.keymanweb.util.callEvent('kmw.keyboardloaded', p); - } - - /** - * Function doBeforeKeyboardChange - * Scope Private - * @param {string} _internalName - * @param {string} _languageCode - * @return {boolean} - * Description Execute external (UI) code needed before changing keyboard - */ - doBeforeKeyboardChange(_internalName: string, _languageCode: string): boolean { - var p={}; - p['internalName']=_internalName; - p['languageCode']=_languageCode; - return this.keymanweb.util.callEvent('kmw.beforekeyboardchange',p); - } - - /** - * Execute external (UI) code needed *after* changing keyboard - * - * @param {string} _internalName - * @param {string} _languageCode - * @param {boolean=} _indirect - * @return {boolean} - */ - doKeyboardChange(_internalName: string, _languageCode: string, _indirect?:boolean): boolean { - var p: KeyboardChangeData = { - 'internalName': _internalName, - 'languageCode': _languageCode, - 'indirect': (arguments.length > 2 ? _indirect : false) - } - - return this.keymanweb.util.callEvent('kmw.keyboardchange', p); - } - shutdown() { for(let script of this.linkedScripts) { if(script.remove) { -- GitLab From 3822b57aa25231448a603d301f02a0f2570cdf8a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 12:59:06 +0700 Subject: [PATCH 263/386] fix(web): setActiveOnRegister option use --- web/src/engine/main/src/keymanEngine.ts | 2 +- web/src/engine/namespaced-main/singleton.ts | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 web/src/engine/namespaced-main/singleton.ts diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index 828fbbdd30..a4a982472c 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -223,7 +223,7 @@ export default class KeymanEngine< }); // If this is the first stub loaded, set it as active. - if(this.keyboardRequisitioner.cache.defaultStub == stub) { + if(this.config.activateFirstKeyboard && this.keyboardRequisitioner.cache.defaultStub == stub) { // Note: leaving this out is super-useful for debugging issues that occur when no keyboard is active. this.contextManager.activateKeyboard(stub.id, stub.langId, true); } diff --git a/web/src/engine/namespaced-main/singleton.ts b/web/src/engine/namespaced-main/singleton.ts deleted file mode 100644 index 915bcea7fc..0000000000 --- a/web/src/engine/namespaced-main/singleton.ts +++ /dev/null @@ -1,6 +0,0 @@ -// By referencing this file first, before any other Keyman class definitions, -// a globally-usable 'singleton' reference to KeymanWeb can be established. - -namespace com.keyman { - export var singleton: KeymanBase; -} \ No newline at end of file -- GitLab From e54b63aa6e6e916951c0f519b7d3f2b4f97ad1b1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 13:00:00 +0700 Subject: [PATCH 264/386] fix(web): deferred add kbd by lang, restoration of prev-session kbd --- web/src/app/browser/src/contextManager.ts | 53 +++++++++++++++++++---- web/src/app/browser/src/keymanEngine.ts | 26 ++++++++--- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 4017dc9b6d..2c5cd82c57 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -704,6 +704,24 @@ export default class ContextManager extends ContextManagerBase('KeymanWeb_Keyboard'); + var v = cookie.load(decodeURIComponent); + + if(typeof(v.current) != 'string') { + return 'Keyboard_us:en'; + } else if(v.current == 'Keyboard_us:eng') { + // 16.0 used the :eng variant! + return 'Keyboard_us:en'; + } else { + return v.current; + } + } + /** * Gets the cookie for the name and language code of the most recently active keyboard * @@ -712,12 +730,7 @@ export default class ContextManager extends ContextManagerBase('KeymanWeb_Keyboard'); - var v = cookie.load(decodeURIComponent); - - if(typeof(v.current) != 'string') { - return 'Keyboard_us:eng'; - } + let cookieValue = this.getSavedKeyboardRaw(); // Check that the requested keyboard is included in the available keyboard stubs const stubs = this.keyboardCache.getStubList() @@ -725,7 +738,7 @@ export default class ContextManager extends ContextManagerBase void { @@ -310,11 +324,13 @@ export default class KeymanEngine extends KeymanEngineBase} Promise of added keyboard/error stubs **/ addKeyboardsForLanguage(arg: string[]|string) : Promise<(KeyboardStub|ErrorStub)[]> { - if (typeof arg === 'string') { - return this.keyboardRequisitioner.addLanguageKeyboards(arg.split(',').map(item => item.trim())); - } else { - return this.keyboardRequisitioner.addLanguageKeyboards(arg); - } + return this.config.deferForInitialization.then(() => { + if (typeof arg === 'string') { + return this.keyboardRequisitioner.addLanguageKeyboards(arg.split(',').map(item => item.trim())); + } else { + return this.keyboardRequisitioner.addLanguageKeyboards(arg); + } + }); } /** -- GitLab From d436639ae45de69fc72adea930d386fc14b1de3f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 13:11:52 +0700 Subject: [PATCH 265/386] chore(web): decommisions domManager.ts, kmwkeyboards.ts --- web/src/app/browser/src/contextManager.ts | 13 -- .../engine/namespaced-main/dom/domManager.ts | 212 ------------------ .../namespaced-main/keyboards/kmwkeyboards.ts | 95 -------- 3 files changed, 320 deletions(-) delete mode 100644 web/src/engine/namespaced-main/dom/domManager.ts delete mode 100644 web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 2c5cd82c57..d8c053ec9f 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -553,30 +553,17 @@ export default class ContextManager extends ContextManagerBase): boolean { const focusAssistant = this.focusAssistant; - // if(target.ownerDocument && target instanceof target.ownerDocument.defaultView.HTMLIFrameElement) { - // if(!this.keyman.domManager._IsEditableIframe(target, 1)) { - // DOMEventHandlers.states._DisableInput = true; - // return true; - // } - // } - // DOMEventHandlers.states._DisableInput = false; - - // const outputTarget = dom.Utils.getOutputTarget(target); - let activeKeyboard = this.activeKeyboard?.keyboard; if(!focusAssistant.restoringFocus) { outputTarget?.deadkeys().clear(); activeKeyboard?.notify(0, outputTarget, 1); // I2187 } - //if(!focusAssistant.restoringFocus && DOMEventHandlers.states._SelectionControl != target) { if(!focusAssistant.restoringFocus && this.mostRecentTarget != outputTarget) { focusAssistant.maintainingFocus = false; } focusAssistant.restoringFocus = false; - //DOMEventHandlers.states._SelectionControl = target; // effectively was .mostRecentTarget, as best as I can tell. - // Now that we've fully entered the new context, invalidate the context so we can generate initial predictions from it. // (Note that the active keyboard will have been updated by a method called before this one; the newly-focused // context should now be 100% ready.) diff --git a/web/src/engine/namespaced-main/dom/domManager.ts b/web/src/engine/namespaced-main/dom/domManager.ts deleted file mode 100644 index 1c2a59f751..0000000000 --- a/web/src/engine/namespaced-main/dom/domManager.ts +++ /dev/null @@ -1,212 +0,0 @@ -// Includes KMW-added property declaration extensions for HTML elements. -/// -// References the base KMW object. -/// -// References DOM event handling interfaces and classes. -/// -// References DOM-specific output handling. -/// -// References other DOM-specific web-core overrides. -/// -// Defines per-element-type OutputTarget element wrapping. -/// -// Defines cookie-based variable store serialization -/// - -namespace com.keyman.dom { - - - /** - * This class serves as the intermediary between KeymanWeb and any given web page's elements. - */ - export class DOMManager { - private keyman: KeymanBase; - - /** - * Implements the AliasElementHandlers interface for touch interaction. - */ - touchHandlers?: DOMTouchHandlers; - - /** - * Implements stubs for the AliasElementHandlers interface for non-touch interaction. - */ - nonTouchHandlers: DOMEventHandlers; - - // Used for special touch-based page interactions re: element activation on touch devices. - deactivateOnScroll: boolean = false; - deactivateOnRelease: boolean = false; - touchY: number; // For scroll-related aspects on iOS. - - touchStartActivationHandler: (e: TouchEvent) => boolean; - touchMoveActivationHandler: (e: TouchEvent) => boolean; - touchEndActivationHandler: (e: TouchEvent) => boolean; - - constructor(keyman: KeymanBase) { - this.keyman = keyman; - - if(keyman.util.device.touchable) { - this.touchHandlers = new DOMTouchHandlers(keyman); - } - - this.nonTouchHandlers = new DOMEventHandlers(keyman); - } - - shutdown() { - // Catch and notify of any shutdown errors, but don't let errors fail unit tests. - try { - if(this.enablementObserver) { - this.enablementObserver.disconnect(); - } - if(this.attachmentObserver) { - this.attachmentObserver.disconnect(); - } - - // On shutdown, we remove our general focus-suppression handlers as well. - this.keyman.util.detachDOMEvent(document.body, 'focus', DOMManager.suppressFocusCheck, true); - this.keyman.util.detachDOMEvent(document.body, 'blur', DOMManager.suppressFocusCheck, true); - - // Also, the base-page touch handlers for activation management. - if(this.touchStartActivationHandler) { - this.keyman.util.detachDOMEvent(document.body, 'touchstart', this.touchStartActivationHandler, false); - this.keyman.util.detachDOMEvent(document.body, 'touchmove', this.touchMoveActivationHandler, false); - this.keyman.util.detachDOMEvent(document.body, 'touchend', this.touchEndActivationHandler, false); - } - } catch (e) { - console.error("Error occurred during shutdown"); - console.error(e); - } - } - - /* ----------------------- Editable IFrame methods ------------------- */ - - /** - * Function _IsEditableIframe - * Scope Private - * @param {Object} Pelem Iframe element - * @param {boolean|number} PtestOn 1 to test if 'designMode' is 'ON' - * @return {boolean} - * Description Test if element is a Mozilla editable IFrame - */ - _IsEditableIframe(Pelem: HTMLIFrameElement, PtestOn?: number) { - var Ldv, Lvalid = Pelem && (Ldv=(Pelem).defaultView) && Ldv.frameElement; // Probable bug! - return (!PtestOn && Lvalid) || (PtestOn && (!Lvalid || Ldv.document.designMode.toLowerCase()=='on')); - } - - /* ----------------------- Initialization methods ------------------ */ - - /** - * Function Initialization - * Scope Public - * @param {com.keyman.OptionType} arg object of user-defined properties - * Description KMW window initialization - */ - init: (arg: com.keyman.OptionType) => Promise = function(this: DOMManager, arg): Promise { - var p,opt,dTrailer,ds; - var util = this.keyman.util; - var device = util.device; - - // Set callbacks for proper feedback from web-core. - this.keyman.core.keyboardProcessor.beepHandler = this.doBeep.bind(this); - this.keyman.core.keyboardProcessor.warningLogger = console.warn.bind(console); - this.keyman.core.keyboardProcessor.errorLogger = console.error.bind(console); - - // Set default device options - this.keyman.setDefaultDeviceOptions(opt); - - // Only do remainder of initialization once! - if(this.keyman.initialized) { - return Promise.resolve(); - } - - this.keyman.linkStylesheetResources(); - - const keyman: KeymanBase = this.keyman; - const domManager = this; - - // Do not initialize until the document has been fully loaded - if(document.readyState !== 'complete') - { - return new Promise(function(resolve) { - window.setTimeout(function(){ - domManager.init(arg).then(function() { - resolve(); - }); - }, 50); - }); - } - - keyman.modelManager.init(); - this.keyman._MasterDocument = window.document; - - /* - * Initialization of touch devices and browser interfaces must be done - * after all resources are loaded, during final stage of initialization - */ - - // Set exposed initialization flag member for UI (and other) code to use - this.keyman.setInitialized(1); - - // Finish keymanweb and initialize the OSK once all necessary resources are available - // OSK type selection is already modularized... but the ordering related to the parts - // afterward, which are not yet modularized, may be important. - if(device.touchable) { - this.keyman.osk = new com.keyman.osk.AnchoredOSKView(device.coreSpec); - } else { - this.keyman.osk = new com.keyman.osk.FloatingOSKView(device.coreSpec); - } - const osk = this.keyman.osk; - - // Create and save the remote keyboard loading delay indicator - util.prepareWait(); - - // Trigger registration of deferred keyboard stubs and keyboards - this.keyman.keyboardManager.endDeferment(); - - // Initialize the desktop UI - this.initializeUI(); - - // Determine the default font for mapped elements - this.keyman.appliedFont=this.keyman.baseFont=this.getBaseFont(); - - // Add orientationchange event handler to manage orientation changes on mobile devices - // Initialize touch-screen device interface I3363 (Build 301) - if(device.touchable) { - this.keyman.handleRotationEvents(); - } - // Initialize browser interface - - // Modular form: pageContextAttachment.install() - - // Initialize the OSK and set default OSK styles - // Note that this should *never* be called before the OSK has been initialized. - // However, it possibly may be called before the OSK has been fully defined with the current keyboard, need to check. - //osk._Load(); - - //document.body.appendChild(osk._Box); - - //osk._Load(false); - - // I3363 (Build 301) - if(device.touchable) { - const osk = keyman.osk as osk.AnchoredOSKView; - // Handle OSK touchend events (prevent propagation) - osk._Box.addEventListener('touchend',function(e){ - e.stopPropagation(); - }, false); - } - - //document.body.appendChild(keymanweb._StyleBlock); - - // Restore and reload the currently selected keyboard, selecting a default keyboard if necessary. - this.keyman.keyboardManager.restoreCurrentKeyboard(); - - // Set exposed initialization flag to 2 to indicate deferred initialization also complete - - // Other initialization details after this point have already been modularized: - // within app/browser KeymanEngine.init, see `setupOskListeners` call and after. - - this.keyman.setInitialized(2); - return Promise.resolve(); - }.bind(this); - } -} \ No newline at end of file diff --git a/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts b/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts deleted file mode 100644 index a54495b65a..0000000000 --- a/web/src/engine/namespaced-main/keyboards/kmwkeyboards.ts +++ /dev/null @@ -1,95 +0,0 @@ -/// - -namespace com.keyman.keyboards { - export class KeyboardTag { - stores: {[text: string]: text.ComplexKeyboardStore} = {}; - } - - export class KeyboardManager { - keymanweb: KeymanBase; - - activeStub: KeyboardStub = null; - keyboardStubs: KeyboardStub[] = []; - - // For deferment of adding keyboards until keymanweb initializes - deferment: Promise = null; - endDeferment:() => void; - - // The following was not actually utilized within KeymanWeb; I think it's handled via different logic. - // See setDefaultKeyboard() below. - dfltStub = null; // First keyboard stub loaded - default for touch-screen devices, ignored on desktops - - keyboards: any[] = []; - - linkedScripts: HTMLScriptElement[] = []; - - constructor(kmw: KeymanBase) { - this.keymanweb = kmw; - - let _this = this; - this.deferment = new Promise(function(resolve) { - _this.endDeferment = resolve; - }); - } - - // Called on the embedded path at the end of its initialization. - setDefaultKeyboard() { - if(this.keyboardStubs.length > 0) { - // Select the first stub as our active keyboard. - this._SetActiveKeyboard(this.keyboardStubs[0]['KI'], this.keyboardStubs[0]['KLC']); - return true; - } else { - return false; - } - } - - /* TODO: why not use util.loadCookie and saveCookie?? */ - - /** - * Restore the most recently used keyboard, if still available - */ - restoreCurrentKeyboard() { - var stubs = this.keyboardStubs, i, n=stubs.length; - let core = com.keyman.singleton.core; - - // Do nothing if no stubs loaded - if(stubs.length < 1) return; - - // If no saved keyboard, default to US English, else first loaded stub - var d=this.getSavedKeyboard(); - var t=d.split(':'); - - // Identify the stub with the saved keyboard - t=d.split(':'); - if(t.length < 2) t[1]=''; - - // This loop is needed to select the correct stub when several apply to a given keyboard - // TODO: There should be a better way! - for(i=0; i Date: Tue, 23 May 2023 13:12:02 +0700 Subject: [PATCH 266/386] chore(web): resolves domOverrides.ts --- .../src/text/prediction/languageProcessor.ts | 9 ++++----- web/src/engine/namespaced-main/dom/domOverrides.ts | 10 ---------- 2 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 web/src/engine/namespaced-main/dom/domOverrides.ts diff --git a/common/web/input-processor/src/text/prediction/languageProcessor.ts b/common/web/input-processor/src/text/prediction/languageProcessor.ts index 8aea39eedc..58c6e42993 100644 --- a/common/web/input-processor/src/text/prediction/languageProcessor.ts +++ b/common/web/input-processor/src/text/prediction/languageProcessor.ts @@ -80,11 +80,10 @@ export default class LanguageProcessor extends EventEmitter Date: Tue, 23 May 2023 13:17:55 +0700 Subject: [PATCH 267/386] chore(web): removes engine/namespaced-main --- web/src/engine/README.md | 3 - web/src/engine/namespaced-main/keymanweb.ts | 113 ---------- web/src/engine/namespaced-main/kmwdebug.js | 202 ------------------ .../engine/namespaced-main/kmwdebugstub.js | 16 -- web/src/engine/namespaced-main/kmwexthtml.ts | 47 ---- web/src/engine/namespaced-main/kmwinit.ts | 25 --- .../engine/namespaced-main/kmwreleasestub.js | 202 ------------------ web/src/engine/namespaced-main/tsconfig.json | 32 --- 8 files changed, 640 deletions(-) delete mode 100644 web/src/engine/README.md delete mode 100644 web/src/engine/namespaced-main/keymanweb.ts delete mode 100644 web/src/engine/namespaced-main/kmwdebug.js delete mode 100644 web/src/engine/namespaced-main/kmwdebugstub.js delete mode 100644 web/src/engine/namespaced-main/kmwexthtml.ts delete mode 100644 web/src/engine/namespaced-main/kmwinit.ts delete mode 100644 web/src/engine/namespaced-main/kmwreleasestub.js delete mode 100644 web/src/engine/namespaced-main/tsconfig.json diff --git a/web/src/engine/README.md b/web/src/engine/README.md deleted file mode 100644 index 7bf84270c2..0000000000 --- a/web/src/engine/README.md +++ /dev/null @@ -1,3 +0,0 @@ -**NOTE**: _deprecated_ - -This subproject holds old namespaced-code corresponding to the new, modularized `engine/main` subproject. \ No newline at end of file diff --git a/web/src/engine/namespaced-main/keymanweb.ts b/web/src/engine/namespaced-main/keymanweb.ts deleted file mode 100644 index 57d1901d23..0000000000 --- a/web/src/engine/namespaced-main/keymanweb.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Includes KMW-added property declaration extensions for HTML elements. -/// -// Includes type definitions for basic KMW types. -/// - -/*** - KeymanWeb 11.0 - Copyright 2019 SIL International -***/ - -// If KMW is already initialized, the KMW script has been loaded more than once. We wish to prevent resetting the -// KMW system, so we use the fact that 'initialized' is only 1 / true after all scripts are loaded for the initial -// load of KMW. -if(!window['keyman']['initialized']) { - - // Continued KeymanWeb initialization. - (function() - { - - // Declare KeymanWeb, OnScreen Keyboard and Util object variables - var keymanweb=window['keyman'],util=keymanweb['util']; - - /** - * Function debug - * Scope Private - * @param {(string|Object)} s string (or object) to print - * Description Simple debug display (upper right of screen) - * Extended to support multiple arguments May 2015 - */ - keymanweb['debug']=keymanweb.debug=function(s){ - var p; - if(keymanweb.debugElement == null) - { - var d=document.createElement('DIV'),ds=d.style; - ds.position='absolute';ds.width='30%';ds.maxHeight='50%';ds.top='0';ds.right='0'; - ds.minHeight='50px'; ds.border='1px solid blue'; ds.whiteSpace='pre-line';ds.overflowY='scroll'; - p=document.createElement('P'); p.id='debug_output';p.style.margin='2px'; - d.appendChild(p); - document.body.appendChild(d); - keymanweb.debugElement=p; - } - if((p=document.getElementById('debug_output')) == null) return; - - if(arguments.length == 0) - if(typeof p.textContent != 'undefined') p.textContent=''; else p.innerHTML=''; - else - { - var ts=new Date().toTimeString().substr(3,5),t=ts+' ',t1,k,m,sx; - for(k=0; k 0) t = t + '; '; - sx = arguments[k]; - if(typeof sx == 'object') - { - if(sx == null) - { - t = t + 'null'; - } - else - { - t1 = ''; - for(m in sx) - { - if(t1.length > 0) t1 = t1 + ', '; - t1 = t1 + m + ':'; - switch(typeof sx[m]) - { - case 'string': - case 'number': - case 'boolean': - t1 = t1 + sx[m]; break; - default: - t1 = t1 + typeof sx[m]; break; - } - if(t1.length > 1024) - { - t1 = t1.substr(0,1000)+'...'; break; - } - } - if(t1.length > 0) t = t + '{' + t1 + '}'; - } - } - else - { - t = t + sx; - } - } - // Truncate if necessary to avoid memory problems - if(t.length > 1500) t = t.substr(0,1500) + ' (more)'; - - if(typeof p.textContent != 'undefined') - p.textContent=t+'\n'+p.textContent; - else - p.innerHTML=t+'
'+p.innerHTML; - - } - } - - /* - * The following code existed here as part of the original pre-conversion JavaScript source, performing some inline initialization. - * Ideally, this will be refactored once proper object-orientation of the codebase within TypeScript is complete. - */ - keymanweb.debugElement=null; - var dbg=keymanweb.debug; - - //TODO: find all references to next three routines and disambiguate!! - - // Complete page initialization only after the page is fully loaded, including any embedded fonts - // This avoids the need to use a timer to test for the fonts - - // *** I3319 Supplementary Plane modifications - end new code - })(); -} \ No newline at end of file diff --git a/web/src/engine/namespaced-main/kmwdebug.js b/web/src/engine/namespaced-main/kmwdebug.js deleted file mode 100644 index 65ca7198ac..0000000000 --- a/web/src/engine/namespaced-main/kmwdebug.js +++ /dev/null @@ -1,202 +0,0 @@ -/*** - KeymanWeb 11.0 - Copyright 2019 SIL International -***/ - -// If KMW is already initialized, the KMW script has been loaded more than once. We wish to prevent resetting the -// KMW system, so we use the fact that 'initialized' is only 1 / true after all scripts are loaded for the initial -// load of KMW. -if(!window['keyman']['initialized']) { - /*__STARTDEBUG__*/ - /*----------------------------------------------------------------------------------------------------*/ - - (function() - { - var keymanweb=window['keyman'],util=keymanweb['util'],kbdInterface=keymanweb.core.kbdInterface; - - keymanweb._LogDebug = true; //false; // typeof(debug) == 'undefined' ? true : debug; - if(util.device.formFactor == 'phone')return; // I3363 (Build 301) - if(keymanweb._LogDebug) - { - var dhost = document.createElement('DIV'); - dhost.style.display = 'block'; - dhost.style.position = 'fixed'; - dhost.style.right = 0; - dhost.style.top = 0; - if(util.device.formFactor == 'tablet') dhost.style.top='22px'; //allow for bar at top of iPad I3363 (Build 301) - dhost.style.width = '30%'; - dhost.style.height = '200px'; - dhost.style.zIndex = 8000; - dhost.style.border = 'solid 2px #ad4a28'; - - keymanweb._DivDebug = document.createElement('DIV'); - keymanweb._DivDebug.style.fontFamily = 'Lucida Console,Courier New,courier'; - keymanweb._DivDebug.style.position = 'absolute'; - keymanweb._DivDebug.style.top = '20px'; - keymanweb._DivDebug.style.width = '100%'; - keymanweb._DivDebug.style.height = '180px'; - keymanweb._DivDebug.style.overflow = 'auto'; - keymanweb._DivDebug.style.fontSize = '8pt'; - keymanweb._DivDebug.style.background = 'white'; - keymanweb._DivDebug.style.display = 'block'; - - dhost.appendChild(keymanweb._DivDebug); - - var _dd = document.createElement('DIV'); - _dd.style.position = 'absolute'; - _dd.style.left = 0; - _dd.style.right = 0; - _dd.style.width = '100%'; - _dd.style.height = '20px'; - _dd.style.background = '#ad4a28'; - - var _c = document.createElement('A'); - _c.onclick = function() { keymanweb._DivDebug.innerHTML = ''; return false; } - _c.href = '#'; - _c.innerHTML = 'Clear'; - _c.style.color = 'white'; - _c.style.cssFloat = _c.style.styleFloat = 'right'; - _c.style.paddingRight = '8px'; - _dd.appendChild(_c); - - var _c1 = document.createElement('A'); //renamed to avoid conflict with previous _c JMD 27/8 - _c1.onclick = function() - { - if(keymanweb._DivDebug.style.display == 'block') - { - keymanweb._DivDebug.style.display = 'none'; dhost.style.height='20px'; this.innerHTML = 'Enable Debugging'; - } - else - { - keymanweb._DivDebug.style.display = 'block'; dhost.style.height='200px'; this.innerHTML = 'Disable Debugging'; - } - return false; - } - _c1.href = '#'; - _c1.innerHTML = 'Disable Debugging'; - _c1.style.color = 'white'; - _c1.style.cssFloat = _c1.style.styleFloat = 'right'; - _c1.style.paddingRight = '8px'; - _dd.appendChild(_c1); - dhost.appendChild(_dd); - - if(document.body) - document.body.appendChild(dhost); - else - util['attachDOMEvent'](window, 'load', function() { document.body.appendChild(dhost); }); - } - - keymanweb._DebugDepth = ''; - - /** - * Note that _Debug(), _DebugEnter(), _DebugExit() and _DebugDeadKeys() are defined with global scope - * so that when stubbed out, the references can be completely removed from the compiled code JMD 27/8 - */ - - _Debug = function(Ps) - { - if(keymanweb._LogDebug && keymanweb._DivDebug.style.display == 'block') - { - var Lelem = document.createElement('P'),t=''; - if(typeof(Ps)=='object') - { - for(z in Ps) t=t+z+': '+Ps[z]+', '; - } - else t=Ps; - if(arguments.length > 1) - { - var n,x; - for(n=1;n 7) keymanweb._DivDebug.removeChild(keymanweb._DivDebug.childNodes[0]); - //The above line could be replaced by absolute position checking, but this way is a lot easier (and probably faster) - //and is fine if debug output is always a single line - - } - //Lelem.scrollIntoView(); //***temporarily disabled, JMD 23/12/11 - //keymanweb._DivDebug.innerHTML = keymanweb._DivDebug.innerHTML + '
'+keymanweb._DebugDepth+Ps; - } - } - - _DebugEnter = function(f) - { - _Debug(f+' ENTER'); - keymanweb._DebugDepth = keymanweb._DebugDepth + '  '; - } - - _DebugExit = function(f) - { - keymanweb._DebugDepth = keymanweb._DebugDepth.slice(0,-12); - _Debug(f+' EXIT'); - } - - _DebugDeadKeys = function(Pelem, Ps) - { - var Lt = "", Li, Lp = 0, Ls, Lj; - - // Mozilla debug (table formatting removed) - if(Pelem.tagName == 'HTML') Ls = Pelem.innerHTML; else Ls = Pelem.value; Lt = ''; //span style="font-size: 12pt">'; - if(typeof(Ls) === 'undefined') return; - for(Li = 0; Li <= Ls._kmwLength(); Li++) Lt += "<"+Ls._kmwCharAt(Li)+">"; //I3319 - for(Li = 0; Li <= Ls._kmwLength(); Li++) //I3319 - { - for(var Lj = 0; Lj < kbdInterface._DeadKeys.length; Lj++) - { - Lt = Lt + '['+kbdInterface._DeadKeys[Lj].p+':'+kbdInterface._DeadKeys[Lj].d+']'; - } - if(Li < Ls._kmwLength()) Lt = Lt + Ls._kmwCharAt(Li); - } - - //_Debug(Ps + ': ' + Lt); - //Lt = Lt + keymanweb._DebugDepth + '   dk['+Li+'] = {pos: '+kbdInterface._DeadKeys[Li].p+', deadKey: '+kbdInterface._DeadKeys[Li].d+'}
'; - - - /* - var Lt = "", Li, Lp = 0; - for(Li = 0; Li < kbdInterface._DeadKeys.length; Li++) - { - if(kbdInterface._DeadKeys[Li].p > Lp) Lp = kbdInterface._DeadKeys[Li].p; - } - - var Ls = kbdInterface.context(Lp, Lp, Pelem); - Lt = keymanweb._DebugDepth + '   Context='+Ls+'
'; - - for(Li = 0; Li < kbdInterface._DeadKeys.length; Li++) - { - Lt = Lt + keymanweb._DebugDepth + '   dk['+Li+'] = {pos: '+kbdInterface._DeadKeys[Li].p+', deadKey: '+kbdInterface._DeadKeys[Li].d+'}
'; - } - _Debug(Ps + '
'+Lt); - */ - } - })(); - - window['_Debug'] = _Debug; - window['_DebugEnter'] = _DebugEnter; - window['_DebugExit'] = _DebugExit; - window['_DebugDeadKeys'] = _DebugDeadKeys; - - /*----------------------------------------------------------------------------------------------------*/ - /*__ENDDEBUG__*/ -} \ No newline at end of file diff --git a/web/src/engine/namespaced-main/kmwdebugstub.js b/web/src/engine/namespaced-main/kmwdebugstub.js deleted file mode 100644 index bc0f382c45..0000000000 --- a/web/src/engine/namespaced-main/kmwdebugstub.js +++ /dev/null @@ -1,16 +0,0 @@ -/*** - KeymanWeb 10.0 - Copyright 2017 SIL International -***/ -/** - * Debug calls external references - */ - - function _Debug(t){}; - - function _DebugEnter(t){}; - - function _DebugExit(t){}; - - function _DebugDeadKeys(t,u){}; - diff --git a/web/src/engine/namespaced-main/kmwexthtml.ts b/web/src/engine/namespaced-main/kmwexthtml.ts deleted file mode 100644 index 6fc7f6eef5..0000000000 --- a/web/src/engine/namespaced-main/kmwexthtml.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Defines a number of KMW objects. -/// - -interface Window { - // DOM type prototypes - HTMLElement: typeof HTMLElement; - HTMLTextAreaElement: typeof HTMLTextAreaElement; - HTMLInputElement: typeof HTMLInputElement; - HTMLIFrameElement: typeof HTMLIFrameElement; - Document: typeof Document; - Event: typeof Event; - MouseEvent: typeof MouseEvent; - TouchEvent: typeof TouchEvent; -} - -interface Element { - _kmwAttachment: com.keyman.AttachmentInfo, // Used to track each input element's attachment data. - shim: HTMLElement, // Used in subkey elements for smooth fading. - - // Touch element extensions - base: HTMLElement, // Refers to the aliased element. Is a property of the alias. - disabled: boolean, - kmwInput: boolean, - _kmwResizeHandler: (e: any) => void, - - // Used by our util.wait / util.alert system - dismiss: () => void -} - -interface CSSStyleDeclaration { - MozBoxSizing: any, - // For legacy 'selection' management. - MozUserSelect: any, // Not necessary with Firefox 52+, which was released... in 2017. - KhtmlUserSelect: any, // No dating information for the rest at present. - UserSelect: any, - WebkitUserSelect: any, - WebkitOverflowScrolling?: string, - msTransition?: string, - MozTransition?: string, - WebkitTransition?: string -} - -interface TouchEvent { - pageY?: number, - clientX?: number, - clientY?: number -} \ No newline at end of file diff --git a/web/src/engine/namespaced-main/kmwinit.ts b/web/src/engine/namespaced-main/kmwinit.ts deleted file mode 100644 index d719825a16..0000000000 --- a/web/src/engine/namespaced-main/kmwinit.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*** - KeymanWeb 11.0 - Copyright 2019 SIL International -***/ - -/********************************************************/ -/* */ -/* Automatically initialize keymanweb with defaults */ -/* after the page is fully loaded */ -/* */ -/********************************************************/ - -(function() { - // Declare KeymanWeb object - var keymanweb=window['keyman']; - - // We don't want to instantly init() in case this code is used via bookmarklet. - var readyStateCheckInterval = window.setInterval(function() { - if (document.readyState === "complete") { - window.clearInterval(readyStateCheckInterval); - keymanweb.init(null); - } - }, 10); - -})(); diff --git a/web/src/engine/namespaced-main/kmwreleasestub.js b/web/src/engine/namespaced-main/kmwreleasestub.js deleted file mode 100644 index b01ee42c00..0000000000 --- a/web/src/engine/namespaced-main/kmwreleasestub.js +++ /dev/null @@ -1,202 +0,0 @@ -/*** - KeymanWeb 11.0 - Copyright 2019 SIL International -***/ - -/** - * External references to separately compiled string prototype extensions - */ - -/** - * Prototypes for SMP string function extensions - * - * @param {number} cp0 - * @return {string} - **/ -String.kmwFromCharCode = function(cp0) {}; - -/** - * @param {number} codePointIndex - * @return {number} - **/ -String.prototype.kmwCharCodeAt = function(codePointIndex) {}; - -/** - * @param {string} searchValue - * @param {number} fromIndex - * @return {number} - **/ -String.prototype.kmwIndexOf = function(searchValue, fromIndex) {}; - -/** - * @param {string} searchValue - * @param {number} fromIndex - * @return {number} - **/ -String.prototype.kmwLastIndexOf = function(searchValue, fromIndex) {}; - -/** - * @return {number} - **/ -String.prototype.kmwLength = function() {}; - -/** - * @param {number} beginSlice - * @param {number} endSlice - * @return {string} - **/ -String.prototype.kmwSlice = function(beginSlice, endSlice) {}; - -/** - * @param {number} start - * @param {number=} length - * @return {string} - **/ -String.prototype.kmwSubstr = function(start, length) {}; - -/** - * @param {number} indexA - * @param {number=} indexB - * @return {string} - **/ -String.prototype.kmwSubstring = function(indexA, indexB) {}; - -/** - * @param {number} codeUnitIndex - * @return {number} - **/ -String.prototype.kmwNextChar = function(codeUnitIndex) {}; - -/** - * @param {number} codeUnitIndex - * @return {number} - **/ -String.prototype.kmwPrevChar = function(codeUnitIndex) {}; - -/** - * @param {number} codePointIndex - * @return {number} - **/ -String.prototype.kmwCodePointToCodeUnit = function(codePointIndex) {}; - -/** - * @param {number} codeUnitIndex - * @return {number} - **/ -String.prototype.kmwCodeUnitToCodePoint = function(codeUnitIndex) {}; - -/** - * @param {number} codePointIndex - * @return {string} - **/ -String.prototype.kmwCharAt = function(codePointIndex) {}; - - - -/** - * Prototypes for string function extensions that can be either BMP or SMP - * - * @param {number} cp0 - * @return {string} - **/ -String._kmwFromCharCode = function(cp0) {}; - -/** - * @param {number} codePointIndex - * @return {number} - **/ -String.prototype._kmwCharCodeAt = function(codePointIndex) {}; - -/** - * @param {string} searchValue - * @param {number} [fromIndex] - * @return {number} - **/ -String.prototype._kmwIndexOf = function(searchValue, fromIndex) {}; - -/** - * @param {string} searchValue - * @param {number} fromIndex - * @return {number} - **/ -String.prototype._kmwLastIndexOf = function(searchValue, fromIndex) {}; - -/** - * @return {number} - **/ -String.prototype._kmwLength = function() {}; - -/** - * @param {string} beginSlice - * @param {string} endSlice - * @return {string} - **/ -String.prototype._kmwSlice = function(beginSlice, endSlice) {}; - -/** - * @param {number} start - * @param {number=} length - * @return {string} - **/ -String.prototype._kmwSubstr = function(start, length) {}; - -/** - * @param {number} indexA - * @param {number=} indexB - * @return {string} - **/ -String.prototype._kmwSubstring = function(indexA, indexB) {}; - -/** - * @param {number} codeUnitIndex - * @return {number} - **/ -String.prototype._kmwNextChar = function(codeUnitIndex) {}; - -/** - * @param {number} codeUnitIndex - * @return {number} - **/ -String.prototype._kmwPrevChar = function(codeUnitIndex) {}; - -/** - * @param {number} codePointIndex - * @return {number} - **/ -String.prototype._kmwCodePointToCodeUnit = function(codePointIndex) {}; - -/** - * @param {number} codeUnitIndex - * @return {number} - **/ -String.prototype._kmwCodeUnitToCodePoint = function(codeUnitIndex) {}; - -/** - * @param {number} codePointIndex - * @return {string} - **/ -String.prototype._kmwCharAt = function(codePointIndex) {}; - - -/** - * String extension to enable SMP handling only as required - * - * @param {(boolean|number)} bEnable - **/ -String.kmwEnableSupplementaryPlane = function(bEnable) {}; - -/** - * External debug routines are stubbed out, allowing the compiler to remove all references - */ - -/** @nosideeffects */ -function _Debug(t){}; - -/** @nosideeffects */ -function _DebugEnter(t){}; - -/** @nosideeffects */ -function _DebugExit(t){}; - -/** @nosideeffects */ -function _DebugDeadKeys(t,u){}; diff --git a/web/src/engine/namespaced-main/tsconfig.json b/web/src/engine/namespaced-main/tsconfig.json deleted file mode 100644 index f1bb16e218..0000000000 --- a/web/src/engine/namespaced-main/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - - "compilerOptions": { - "outFile": "../../../build/engine/main/obj/keymanweb.js", - "allowSyntheticDefaultImports": true, - "module": "es6", - "moduleResolution": "Node", - }, - - "include": [ - "./*.ts", - "./**/*.ts" - ], - - "files": [ - "kmwbase.ts", - "keymanweb.ts", - "kmwinit.ts", - "kmwapi.ts" - ], - - "references": [ - { "path": "../../../../common/web/keyman-version"}, - { "path": "../../../../common/web/utils"}, - { "path": "../../../../common/predictive-text"}, - { "path": "../../../../common/web/input-processor"}, - { "path": "../../../../common/web/keyboard-processor"}, - { "path": "../../../../common/web/lm-message-types" }, - { "path": "../device-detect" } - ] -} -- GitLab From c15d0914276cb141a05e0bfdfa5fde4243cf9ca6 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 13:21:18 +0700 Subject: [PATCH 268/386] chore: base-repo tsconfig cleanup attempt --- tsconfig.cjs.json | 16 ---------------- tsconfig.esm.json | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 87aadd5c9b..3dff61f8b6 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -5,30 +5,14 @@ "files": [], "include": [], "references": [ - { "path": "./common/web/input-processor/tsconfig.json" }, - { "path": "./common/web/keyboard-processor/tsconfig.json" }, - { "path": "./common/web/recorder/tsconfig.json" }, - { "path": "./common/web/sentry-manager/src/tsconfig.json" }, - { "path": "./common/web/utils/tsconfig.json" }, - - { "path": "./common/models/templates/tsconfig.json" }, - { "path": "./common/models/types/tsconfig.json" }, - { "path": "./common/models/wordbreakers/tsconfig.json" }, { "path": "./common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json" }, { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json" }, { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json" }, - { "path": "./common/predictive-text/tsconfig.json" }, { "path": "./developer/src/server/tsconfig.json" }, { "path": "./resources/build/version/tsconfig.json" }, { "path": "./resources/build/version/tsconfig.production.json" }, // { "path": "./web/bulk_rendering/tsconfig.json" }, - { "path": "./web/src/tsconfig.all.json" }, - // { "path": "./web/tools/recorder/tsconfig.json" }, - // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, - { "path": "./common/web/lm-message-types/" }, - { "path": "./common/web/lm-worker/" }, - { "path": "./common/web/keyman-version/" }, ] } \ No newline at end of file diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 40c63618c9..de47c84fde 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -23,5 +23,22 @@ { "path": "./common/web/keyman-version" }, { "path": "./common/web/types/" }, + + { "path": "./common/web/input-processor/tsconfig.json" }, + { "path": "./common/web/keyboard-processor/tsconfig.json" }, + { "path": "./common/web/recorder/tsconfig.json" }, + { "path": "./common/web/sentry-manager/src/tsconfig.json" }, + { "path": "./common/web/utils/tsconfig.json" }, + + { "path": "./common/models/templates/tsconfig.json" }, + { "path": "./common/models/types/tsconfig.json" }, + { "path": "./common/models/wordbreakers/tsconfig.json" }, + { "path": "./common/predictive-text/tsconfig.json" }, + + { "path": "./web/src/tsconfig.all.json" }, + // { "path": "./web/tools/recorder/tsconfig.json" }, + // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, + { "path": "./common/web/lm-message-types/" }, + { "path": "./common/web/lm-worker/" }, ] } \ No newline at end of file -- GitLab From 2135ffd1975213f9d601937e9c5c5268c5f211c9 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 23 May 2023 15:37:02 +0700 Subject: [PATCH 269/386] chore(web): removes 'not yet complete' build warning --- web/build.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/build.sh b/web/build.sh index 34a4fdf0ce..180ea8d849 100755 --- a/web/build.sh +++ b/web/build.sh @@ -104,10 +104,6 @@ builder_run_child_actions build:samples builder_run_child_actions test -if builder_has_action build:app/browser; then - builder_warn "Modularization work is not yet complete; consumers may find needed API or components to be missing" -fi - if builder_start_action test:project; then TEST_OPTS= if builder_has_option --ci; then -- GitLab From bc51f813e25af4bb529c603fc151031ed24385c3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 24 May 2023 15:46:54 +0700 Subject: [PATCH 270/386] fix(common/models): predictive-text sourcemap fix --- common/web/lm-worker/build-polyfill-concatenator.js | 2 +- common/web/lm-worker/build-wrap-and-minify.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/web/lm-worker/build-polyfill-concatenator.js b/common/web/lm-worker/build-polyfill-concatenator.js index 52b24b910d..7b8b17b208 100644 --- a/common/web/lm-worker/build-polyfill-concatenator.js +++ b/common/web/lm-worker/build-polyfill-concatenator.js @@ -134,7 +134,7 @@ console.log(); let sourceRoot = "/@keymanapp/keyman"; console.log(`Setting sourceRoot: ${sourceRoot}`) remappingState.sourceRoot = sourceRoot; -fullWorkerConcatenation.sourcemapJSON = remappingState.sourceMap; +fullWorkerConcatenation.sourcemapJSON = remappingState.sourcemap; // End "cleaning the sourcemaps" diff --git a/common/web/lm-worker/build-wrap-and-minify.js b/common/web/lm-worker/build-wrap-and-minify.js index b72fb82a4f..0b816f02dd 100644 --- a/common/web/lm-worker/build-wrap-and-minify.js +++ b/common/web/lm-worker/build-wrap-and-minify.js @@ -37,10 +37,10 @@ if(MINIFY) { keepNames: true, outfile: `build/lib/worker-main.polyfilled.min.js` }); - - sourcemapJSON = convertSourcemap.fromJSON(fs.readFileSync(`build/lib/worker-main.polyfilled${MINIFY ? '.min' : ''}.js.map`)).toObject(); } +sourcemapJSON = convertSourcemap.fromJSON(fs.readFileSync(`build/lib/worker-main.polyfilled${MINIFY ? '.min' : ''}.js.map`)).toObject(); + const workerConcatenation = { script: fs.readFileSync(`build/lib/worker-main.polyfilled${MINIFY ? '.min' : ''}.js`), sourcemapJSON: sourcemapJSON -- GitLab From 6d1a0b15aeab5c07370e5a6e14f7963a2de83ed2 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 09:10:02 +0700 Subject: [PATCH 271/386] fix(web): reverts prop name update --- common/web/lm-worker/build-polyfill-concatenator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/web/lm-worker/build-polyfill-concatenator.js b/common/web/lm-worker/build-polyfill-concatenator.js index 7b8b17b208..52b24b910d 100644 --- a/common/web/lm-worker/build-polyfill-concatenator.js +++ b/common/web/lm-worker/build-polyfill-concatenator.js @@ -134,7 +134,7 @@ console.log(); let sourceRoot = "/@keymanapp/keyman"; console.log(`Setting sourceRoot: ${sourceRoot}`) remappingState.sourceRoot = sourceRoot; -fullWorkerConcatenation.sourcemapJSON = remappingState.sourcemap; +fullWorkerConcatenation.sourcemapJSON = remappingState.sourceMap; // End "cleaning the sourcemaps" -- GitLab From cb8f380efd88547fc2d9cd31d6d8c5df3478f1e7 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 09:36:22 +0700 Subject: [PATCH 272/386] fix(web): drops file accidentally pulled too far forward --- web/src/app/browser/src/utilApiEndpoint.ts | 346 --------------------- 1 file changed, 346 deletions(-) delete mode 100644 web/src/app/browser/src/utilApiEndpoint.ts diff --git a/web/src/app/browser/src/utilApiEndpoint.ts b/web/src/app/browser/src/utilApiEndpoint.ts deleted file mode 100644 index b1feb07c9f..0000000000 --- a/web/src/app/browser/src/utilApiEndpoint.ts +++ /dev/null @@ -1,346 +0,0 @@ - -import { - CookieSerializer, - createStyleSheet, - getAbsoluteX, - getAbsoluteY, - StylesheetManager - } from "keyman/engine/dom-utils"; -import { DomEventTracker } from "keyman/engine/events"; -import { BrowserConfiguration, BrowserInitOptionSpec } from "./configuration.js"; -import { getStyleValue } from "./utils/getStyleValue.js"; -import { AlertHost } from "./utils/alertHost.js"; - -/** - * Calls document.createElement for the specified node type and also applies - * 'user-select: none' styling to the new element. - * @param nodeName - * @returns - */ -export function createUnselectableElement(nodeName:E) { - const e = document.createElement(nodeName); - e.style.userSelect="none"; - return e; -} - -export class UtilApiEndpoint { - readonly config: BrowserConfiguration; - private readonly stylesheetManager: StylesheetManager; - private readonly domEventTracker: DomEventTracker; - private _alertHost: AlertHost; - - constructor(config: BrowserConfiguration) { - this.config = config; - this.stylesheetManager = new StylesheetManager(document.body, config.applyCacheBusting); - this.domEventTracker = new DomEventTracker(); - } - - readonly getAbsoluteX = getAbsoluteX; - readonly getAbsoluteY = getAbsoluteY; - - // These four were renamed, but we need to maintain their legacy names. - readonly _GetAbsoluteX = getAbsoluteX; - readonly _GetAbsoluteY = getAbsoluteY; - readonly _GetAbsolute = this.getAbsolute; - readonly toNzString = this.nzString; - - /** - * Expose the touchable state for UIs - will disable external UIs entirely - **/ - isTouchDevice(): boolean { - return this.config.hostDevice.touchable; - } - - getAbsolute(elem: HTMLElement): { x: number, y: number } { - return { - x: getAbsoluteX(elem), - y: getAbsoluteY(elem) - }; - } - - /** - * Calls document.createElement for the specified node type and also applies - * 'user-select: none' styling to the new element. - * @param nodeName - * @returns - */ - readonly createElement = createUnselectableElement; - - /** - * Function getOption - * Scope Public - * @param {string} optionName Name of option - * @param {*=} dflt Default value of option - * @return {*} - * Description Returns value of named option - */ - getOption(optionName: keyof BrowserInitOptionSpec, dflt?:any): any { - if(optionName in this.config.paths) { - return this.config.paths[optionName]; - } else if(optionName in this.config.options) { - return this.config.options[optionName]; - } else if(arguments.length > 1) { - return dflt; - } else { - return ''; - } - } - - setOption(optionName: keyof BrowserInitOptionSpec, value: any): void { - switch(optionName) { - case 'attachType': - // 16.0 & before: did nothing. - // Fixable for 17.0 with some extra work, but the changes would likely be enough to - // merit a focused PR. It's not 100% straightforward. - break; - case 'ui': - // 16.0 & before: relies on the Float UI to passively pick up on any changes. - // Only appears to be effective before the Float UI initializes. - break; - case 'useAlerts': - this.config.alertHost = (value ? new AlertHost() : null); - break; - case 'setActiveOnRegister': - this.config.activateFirstKeyboard = !!value; - break; - case 'spacebarText': - this.config.spacebarText = value; - break; - default: - throw new Error("Path-related options may not be changed after the engine has initialized."); - } - } - - /** - * Document cookie parsing for use by kernel, OSK, UI etc. - * - * @param {string=} cn cookie name (optional) - * @return {Object} array of names and strings, or array of variables and values - */ - loadCookie>(cn?: string) { - const cookie = new CookieSerializer(cn); - return cookie.load(decodeURIComponent); - } - - /** - * Standard cookie saving for use by kernel, OSK, UI etc. - * - * @param {string} cn name of cookie - * @param {Object} cv object with array of named arguments and values - */ - saveCookie>(cn: string, cv: CookieType) { - const cookie = new CookieSerializer(cn); - cookie.save(cv, encodeURIComponent); - } - - /** - * Add a stylesheet to a page programmatically, for use by the OSK, the UI or the page creator - * - * @param {string} s style string - * @return {Object} returns the object reference - **/ - addStyleSheet(s: string): HTMLStyleElement { - const styleSheet = createStyleSheet(s); - this.stylesheetManager.linkStylesheet(styleSheet); - - return styleSheet; - } - - /** - * Remove a stylesheet element - * - * @param {Object} s style sheet reference - * @return {boolean} false if element is not a style sheet - **/ - removeStyleSheet(s: HTMLStyleElement) { - return this.stylesheetManager.unlink(s); - } - - /** - * Add a reference to an external stylesheet file - * - * @param {string} s path to stylesheet file - */ - linkStyleSheet(s: string): void { - this.stylesheetManager.linkExternalSheet(s); - } - - // Possible alternative: https://www.npmjs.com/package/language-tags - // This would necessitate linking in a npm module into compiled KeymanWeb, though. - getLanguageCodes(lgCode: string): string[] { - if(lgCode.indexOf('-')==-1) { - return [lgCode]; - } else { - return lgCode.split('-'); - } - } - - /** - * Function attachDOMEvent: Note for most browsers, adds an event to a chain, doesn't stop existing events - * Scope Public - * @param {Object} Pelem Element (or IFrame-internal Document) to which event is being attached - * @param {string} Peventname Name of event without 'on' prefix - * @param {function(Object)} Phandler Event handler for event - * @param {boolean=} PuseCapture True only if event to be handled on way to target element - * Description Attaches event handler to element DOM event - */ - attachDOMEvent( - Pelem: Window, - Peventname: K, - Phandler: (ev: WindowEventMap[K]) => any, - PuseCapture?: boolean - ): void; - attachDOMEvent( - Pelem: Document, - Peventname: K, - Phandler: (ev: DocumentEventMap[K]) => any, - PuseCapture?: boolean - ): void; - attachDOMEvent( - Pelem: HTMLElement, - Peventname: K, - Phandler: (ev: HTMLElementEventMap[K]) => any, - PuseCapture?: boolean - ): void; - attachDOMEvent(Pelem: EventTarget, Peventname: string, Phandler: (Object) => boolean, PuseCapture?: boolean): void { - // TS can't quite track the type inference forwarding here. - this.domEventTracker.attachDOMEvent(Pelem as any, Peventname as any, Phandler, PuseCapture); - } - - /** - * Function detachDOMEvent - * Scope Public - * @param {Object} Pelem Element from which event is being detached - * @param {string} Peventname Name of event without 'on' prefix - * @param {function(Object)} Phandler Event handler for event - * @param {boolean=} PuseCapture True if event was being handled on way to target element - * Description Detaches event handler from element [to prevent memory leaks] - */ - detachDOMEvent( - Pelem: Window, - Peventname: K, - Phandler: (ev: WindowEventMap[K]) => any, - PuseCapture?: boolean - ): void; - detachDOMEvent( - Pelem: Document, - Peventname: K, - Phandler: (ev: DocumentEventMap[K]) => any, - PuseCapture?: boolean - ): void; - detachDOMEvent( - Pelem: HTMLElement, - Peventname: K, - Phandler: (ev: HTMLElementEventMap[K]) => any, - PuseCapture?: boolean - ): void; - detachDOMEvent(Pelem: EventTarget, Peventname: string, Phandler: (Object) => boolean, PuseCapture?: boolean): void { - // TS can't quite track the type inference forwarding here. - this.domEventTracker.detachDOMEvent(Pelem as any, Peventname as any, Phandler, PuseCapture); - } - - getStyleValue = getStyleValue; - - private get alertHost(): AlertHost { - if(this.config.alertHost) { - return this.config.alertHost; - } else if(!this._alertHost) { - // Lazy init: if KMW is set to not show alerts, we try not to initialize the alert host. - // If the .alert API is called, though, we have no choice. - this._alertHost = new AlertHost(); - } - - return this._alertHost; - } - - alert(s: string, fn: () => void) { - this.alertHost.alert(s, fn); - } - - /** - * Function toNzString - * Scope Public - * @param {*} item variable to test - * @param {?*=} dflt default value - * @return {*} - * Description Test if a variable is null, false, empty string, or undefined, and return as string - */ - nzString(item: any, dflt: string): string { - // // ... is this whole thing essentially just: - // return '' + (item || dflt || ''); - // // ? - - let dfltValue = ''; - if(arguments.length > 1) { - dfltValue = dflt; - } - - if(typeof(item) == 'undefined') { - return dfltValue; - } - - if(item == null) { - return dfltValue; - } - - if(item == 0 || item == '') { - return dfltValue; - } - - return ''+item; - } - - /** - * Function toNumber - * Scope Public - * @param {string} s numeric string - * @param {number} dflt default value - * @return {number} - * Description Return string converted to integer or default value - */ - toNumber(s: string, dflt: number): number { - const x = parseInt(s,10); - return isNaN(x) ? dflt : x; - } - - /** - * Function toNumber - * Scope Public - * @param {string} s numeric string - * @param {number} dflt default value - * @return {number} - * Description Return string converted to real value or default value - */ - toFloat(s: string, dflt: number): number { - const x = parseFloat(s); - return isNaN(x) ? dflt : x; - } - - /** - * Function rgba - * Scope Public - * @param {Object} s element style object - * @param {number} r red value, 0-255 - * @param {number} g green value, 0-255 - * @param {number} b blue value, 0-255 - * @param {number} a opacity value, 0-1.0 - * @return {string} background colour style string - * Description Browser-independent alpha-channel management - */ - rgba(s: HTMLStyleElement, r:number, g:number, b:number, a:number): string { - let bgColor='transparent'; - try { - bgColor='rgba('+r+','+g+','+b+','+a+')'; - } catch(ex) { - bgColor='rgb('+r+','+g+','+b+')'; - } - - return bgColor; - } - - shutdown() { - this.stylesheetManager?.unlinkAll(); - this.domEventTracker?.shutdown(); - this._alertHost?.shutdown(); - } -} \ No newline at end of file -- GitLab From bd74e66aa0d62af30c6098b19fc431eedcd247fa Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 09:48:05 +0700 Subject: [PATCH 273/386] chore(web): applies review suggestions --- web/src/app/browser/src/contextManager.ts | 2 +- web/src/app/browser/src/keymanEngine.ts | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 99d0e2e6c7..da041f2490 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -26,7 +26,7 @@ interface KeyboardCookie { * @param {Object} Ptarg Target element */ function _SetTargDir(Ptarg: HTMLElement, activeKeyboard: Keyboard) { - var elDir=(activeKeyboard && activeKeyboard?.isRTL) ? 'rtl' : 'ltr'; + const elDir = activeKeyboard?.isRTL ? 'rtl' : 'ltr'; if(Ptarg) { if(Ptarg instanceof Ptarg.ownerDocument.defaultView.HTMLInputElement diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 25fa33897c..274ac4718f 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -38,12 +38,10 @@ export default class KeymanEngine extends KeymanEngineBase).activationTrigger = e; if(this.config.hostDevice.touchable) { - if(!target || !this.osk) { + if(!e || !target || !this.osk) { return; } - const e = target?.getElement(); - // Get the absolute position of the caret const y = getAbsoluteY(e); const t = window.pageYOffset; @@ -193,8 +191,7 @@ export default class KeymanEngine extends KeymanEngineBase} Promise of added keyboard/error stubs * */ - addKeyboards(...args: any[]) : - Promise<(KeyboardStub|ErrorStub)[]> { + addKeyboards(...args: (any)[]) : Promise<(KeyboardStub|ErrorStub)[]> { if (!args || !args[0] || args[0].length == 0) { // Get the cloud keyboard catalog return this.keyboardRequisitioner.fetchCloudCatalog().catch((errVal) => { @@ -204,11 +201,9 @@ export default class KeymanEngine extends KeymanEngineBase - x.push(a)); + x.push(...args[0]); } else if (Array.isArray(args)) { - args.forEach(a => - x.push(a)); + x.push(...args); } else { x.push(args); } -- GitLab From 372b912c91d841b050c38c803bbd9d9779d0ca9f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 10:01:34 +0700 Subject: [PATCH 274/386] change(web): run tests before throwing 'incomplete' build error --- web/build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/build.sh b/web/build.sh index b11223ca79..9b73fc3044 100755 --- a/web/build.sh +++ b/web/build.sh @@ -109,12 +109,12 @@ if builder_has_action build:app/browser; then builder_warn "Modularization work is not yet complete; consumers may find needed API or components to be missing" fi -if builder_has_action build:app/ui; then - builder_die "Modularization work is not yet complete; builds dependent on this will fail." -fi - if builder_start_action test; then ./test.sh :engine builder_finish_action success test fi + +if builder_has_action build:app/ui; then + builder_die "Modularization work is not yet complete; builds dependent on this will fail." +fi -- GitLab From 3705d847c15b7dd41cceea5ce24da02f1971c812 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 10:18:51 +0700 Subject: [PATCH 275/386] chore(web): addresses PR review concerns --- .../app/browser/src/context/focusAssistant.ts | 4 +-- web/src/app/browser/src/contextManager.ts | 36 ++++++++++--------- web/src/app/webview/src/contextManager.ts | 12 +++---- web/src/engine/main/src/contextManagerBase.ts | 28 +++++++++++---- .../auto/dom/cases/browser/contextManager.js | 22 ++++++------ 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/web/src/app/browser/src/context/focusAssistant.ts b/web/src/app/browser/src/context/focusAssistant.ts index 38673a0e71..bda60dee5d 100644 --- a/web/src/app/browser/src/context/focusAssistant.ts +++ b/web/src/app/browser/src/context/focusAssistant.ts @@ -29,7 +29,7 @@ interface EventMap { * Called immediately after the `maintainingFocus` flag is cleared. * @returns */ - 'maintainingend': () => void; + 'maintainingfocusend': () => void; } // Formerly handled under "UIManager". @@ -70,7 +70,7 @@ export class FocusAssistant extends EventEmitter { // Needed to properly update .activeTarget upon loss of maintaining-focus state. if(priorValue && !value) { - this.emit('maintainingend'); + this.emit('maintainingfocusend'); } } diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index e08594185a..c957aec0fc 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -62,7 +62,7 @@ export default class ContextManager extends ContextManagerBase { + this.focusAssistant.on('maintainingfocusend', () => { // Basically, if the maintaining state were the reason we still had an `activeTarget`... if(!this.activeTarget && this.mostRecentTarget) { this.emit('targetchange', this.activeTarget); @@ -271,11 +271,13 @@ export default class ContextManager extends ContextManagerBase { + protected currentKeyboardSrcTarget(): OutputTarget { let target = this.currentTarget || this.mostRecentTarget; let attachmentInfo = target?.getElement()._kmwAttachment; @@ -302,7 +304,7 @@ export default class ContextManager extends ContextManagerBase { saveCookie ||= false; - const originalKeyboardTarget = this.keyboardTarget; + const originalKeyboardTarget = this.currentKeyboardSrcTarget(); // Must do here b/c of fallback behavior stuff defined below. // If the default keyboard is requested, load that. May vary based on form-factor, which is - // part of what .getFallbackCodes() handles. + // part of what .getFallbackStubKey() handles. if(!keyboardId) { - keyboardId = this.getFallbackCodes().id; - languageCode = this.getFallbackCodes().langId; + keyboardId = this.getFallbackStubKey().id; + languageCode = this.getFallbackStubKey().langId; } try { @@ -387,7 +391,7 @@ export default class ContextManager extends ContextManagerBase { // Make sure we don't infinite-recursion should the deactivate somehow fail. - const fallbackCodes = this.getFallbackCodes(); + const fallbackCodes = this.getFallbackStubKey(); if((fallbackCodes.id != keyboardId)) { await this.activateKeyboard(fallbackCodes.id, fallbackCodes.langId, true).catch(() => {}); } // else "We already failed, so give up." diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 55aad5d7a6..580a1e1cfa 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -67,28 +67,28 @@ export default class ContextManager extends ContextManagerBase { // If the default keyboard is requested, load that. May vary based on form-factor, which is - // part of what .getFallbackCodes() handles. + // part of what .getFallbackStubKey() handles. if(!keyboardId) { - keyboardId = this.getFallbackCodes().id; - languageCode = this.getFallbackCodes().langId; + keyboardId = this.getFallbackStubKey().id; + languageCode = this.getFallbackStubKey().langId; } try { return await super.activateKeyboard(keyboardId, languageCode, saveCookie); } catch(err) { // Fallback behavior - we're embedded in a touch-device's webview, so we need to keep a keyboard visible. - const fallbackCodes = this.getFallbackCodes(); + const fallbackCodes = this.getFallbackStubKey(); if(fallbackCodes.id != keyboardId) { await this.activateKeyboard(fallbackCodes.id, fallbackCodes.langId, true).catch(() => {}); } // else "We already failed, so give up." diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index f5a5d1056b..a7bc944f8d 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -124,7 +124,17 @@ export abstract class ContextManagerBase } abstract get activeKeyboard(): {keyboard: Keyboard, metadata: KeyboardStub}; - protected abstract get keyboardTarget(): OutputTarget; + + /** + * Determines the 'target' currently used to determine which keyboard should be active. + * When `null`, keyboard-activation operations will affect the global default; otherwise, + * such operations affect only the specified `target`. + * + * This method exists to facilitate independent-keyboard mode operations for specific + * attached elements within the app/browser target. For `app/webview`, this should + * always return a consistent value - likely, `null`. + */ + protected abstract currentKeyboardSrcTarget(): OutputTarget; /** * Ensures that newly activated keyboards are set correctly within managed context, possibly @@ -193,7 +203,11 @@ export abstract class ContextManagerBase } } - protected abstract getFallbackCodes(): { + /** + * Specifies the keyboard id and the language code to use when a 'default' keyboard + * must be selected by the engine for fallback behaviors. + */ + protected abstract getFallbackStubKey(): { id: string, langId: string }; @@ -219,11 +233,11 @@ export abstract class ContextManagerBase // If there was a previous activation attempt set and still active for the specified keyboard target, // cancel it. For exmaple, if the user selects a preloaded keyboard after having tried to select one // still async-loading, we should go with the later setting - the preloaded one. - this.findAndPopActivation(this.keyboardTarget); + this.findAndPopActivation(this.currentKeyboardSrcTarget()); const activatingKeyboard = this.prepareKeyboardForActivation(keyboardId, languageCode); - const originalKeyboardTarget = this.keyboardTarget; + const originalKeyboardTarget = this.currentKeyboardSrcTarget(); const keyboard = await activatingKeyboard.keyboard; if(keyboard == null && activatingKeyboard.metadata) { @@ -243,7 +257,7 @@ export abstract class ContextManagerBase * If the now-current context would be unaffected by the keyboard change, we do not raise the corresponding * event. */ - if(this.keyboardTarget == originalKeyboardTarget) { + if(this.currentKeyboardSrcTarget() == originalKeyboardTarget) { this.emit('beforekeyboardchange', activatingKeyboard.metadata); } @@ -258,7 +272,7 @@ export abstract class ContextManagerBase this.activateKeyboardForTarget(kbdStubPair, originalKeyboardTarget); // Only trigger `keyboardchange` events when they will affect the active context. - if(this.keyboardTarget == originalKeyboardTarget) { + if(this.currentKeyboardSrcTarget() == originalKeyboardTarget) { // Perform standard context-reset ops, including the processing of new-context events. this.resetContext(); // Will trigger KeymanEngine handler that passes keyboard to the OSK, displays it. @@ -357,7 +371,7 @@ export abstract class ContextManagerBase }); // Now the fun part: note the original call's parameters as a pending activation. - let promise = this.deferredKeyboardActivation(defermentPromise, requestedStub, this.keyboardTarget); + let promise = this.deferredKeyboardActivation(defermentPromise, requestedStub, this.currentKeyboardSrcTarget()); return { keyboard: promise.then(async (activation) => { // Is the activation we requested still pending, or was it cancelled in favor of a diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 3165e04f31..b2085e44d2 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -845,7 +845,7 @@ describe('app/browser: ContextManager', function () { contextManager.setKeyboardForTarget(target, 'lao_2008_basic', 'lo'); // As we haven't yet focused the affected target, no keyboard-change events should have triggered yet. - assert.equal(contextManager.keyboardTarget, null); + assert.equal(contextManager.currentKeyboardSrcTarget(), null); assert.isTrue(beforekeyboardchange.notCalled); assert.isTrue(keyboardchange.notCalled); assert.isTrue(keyboardasyncload.notCalled); @@ -857,7 +857,7 @@ describe('app/browser: ContextManager', function () { await timedPromise(10); // No need to 'keyboardchange' when the same keyboard is kept active. - assert.equal(contextManager.keyboardTarget, target); + assert.equal(contextManager.currentKeyboardSrcTarget(), target); assert.isTrue(beforekeyboardchange.calledOnce); assert.isTrue(keyboardchange.calledOnce); assert.isTrue(keyboardasyncload.notCalled); @@ -1034,7 +1034,7 @@ describe('app/browser: ContextManager', function () { await contextManager.activateKeyboard('test_chirality', 'en'); // Aspect 1: the current keyboard has changed - assert.equal(contextManager.keyboardTarget, target); + assert.equal(contextManager.currentKeyboardSrcTarget(), target); assert.isTrue(beforekeyboardchange.calledOnce); assert.isTrue(keyboardchange.calledOnce); assert.isTrue(keyboardasyncload.notCalled); @@ -1048,7 +1048,7 @@ describe('app/browser: ContextManager', function () { await timedPromise(10); // Aspect 2: ... without affecting the global keyboard's setting. - assert.equal(contextManager.keyboardTarget, null); + assert.equal(contextManager.currentKeyboardSrcTarget(), null); assert.isTrue(beforekeyboardchange.calledTwice); assert.isTrue(keyboardchange.calledTwice); assert.isTrue(keyboardasyncload.notCalled); @@ -1089,7 +1089,7 @@ describe('app/browser: ContextManager', function () { await Promise.resolve(); // Aspect 1: the current keyboard has not yet changed - assert.equal(contextManager.keyboardTarget, target); + assert.equal(contextManager.currentKeyboardSrcTarget(), target); assert.isTrue(beforekeyboardchange.calledOnce); // +1 assert.isTrue(keyboardchange.notCalled); // is delayed 50 ms, so not yet. assert.isTrue(keyboardasyncload.calledOnce); // The async load has already started. @@ -1104,7 +1104,7 @@ describe('app/browser: ContextManager', function () { // Allows any _FocusKeyboardSettings stuff trigger to resolve. await timedPromise(10); - assert.equal(contextManager.keyboardTarget, null); + assert.equal(contextManager.currentKeyboardSrcTarget(), null); assert.isTrue(beforekeyboardchange.calledTwice); // +1: re-activating the global keyboard assert.isTrue(keyboardchange.calledOnce); // +1: same assert.isTrue(keyboardasyncload.calledOnce); @@ -1115,7 +1115,7 @@ describe('app/browser: ContextManager', function () { // ...and verify that the active keyboard has not changed, since the target for // activation is not itself active. - assert.equal(contextManager.keyboardTarget, null); + assert.equal(contextManager.currentKeyboardSrcTarget(), null); assert.isTrue(beforekeyboardchange.calledTwice); // +1: re-activating the global keyboard assert.isTrue(keyboardchange.calledOnce); // +1: same assert.isTrue(keyboardasyncload.calledOnce); @@ -1133,7 +1133,7 @@ describe('app/browser: ContextManager', function () { await timedPromise(10); // And, final expectations: - assert.equal(contextManager.keyboardTarget, target); + assert.equal(contextManager.currentKeyboardSrcTarget(), target); assert.isTrue(beforekeyboardchange.calledThrice); // +1: activating the independent-mode kbd assert.isTrue(keyboardchange.calledTwice); // +1: same assert.isTrue(keyboardasyncload.calledOnce); @@ -1185,7 +1185,7 @@ describe('app/browser: ContextManager', function () { await Promise.resolve(); // Aspect 1: the current keyboard has not yet changed - assert.equal(contextManager.keyboardTarget, target); + assert.equal(contextManager.currentKeyboardSrcTarget(), target); assert.isTrue(beforekeyboardchange.calledOnce); // +1 assert.isTrue(keyboardchange.notCalled); // is delayed 50 ms, so not yet. assert.isTrue(keyboardasyncload.calledOnce); // The async load has already started. @@ -1208,7 +1208,7 @@ describe('app/browser: ContextManager', function () { await Promise.resolve(); // Aspect 2: the current keyboard STILL has not yet changed - still delayed. - assert.equal(contextManager.keyboardTarget, target); + assert.equal(contextManager.currentKeyboardSrcTarget(), target); assert.isTrue(beforekeyboardchange.calledTwice); // +1 assert.isTrue(keyboardchange.notCalled); // both should still be delayed. assert.isTrue(keyboardasyncload.calledTwice); // The async load has already started. @@ -1220,7 +1220,7 @@ describe('app/browser: ContextManager', function () { // Critical bit: the `lao` activation should appear to have auto-canceled; this is because // when its keyboard loaded, we'd already requested the `test_chirality` keyboard. - assert.equal(contextManager.keyboardTarget, target); + assert.equal(contextManager.currentKeyboardSrcTarget(), target); assert.isTrue(beforekeyboardchange.calledThrice); // +1 assert.isTrue(keyboardchange.calledOnce); // There should be no attempt to swap to the lao kbd. assert.isTrue(keyboardasyncload.calledTwice); -- GitLab From 8dd323495e186a1bbd091a22114706f79fa3101c Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 10:31:02 +0700 Subject: [PATCH 276/386] chore(web): pulls forward reconnection to test.sh --- web/test.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/test.sh b/web/test.sh index f129fad30e..5f83e5dcb6 100755 --- a/web/test.sh +++ b/web/test.sh @@ -16,10 +16,8 @@ cd "$THIS_SCRIPT_PATH" ################################ Main script ################################ -# Temp-removed dependency: -# "@./src/tools/testing/recorder test:engine" \ - builder_describe "Runs the Keyman Engine for Web unit-testing suites" \ + "@./src/tools/testing/recorder test:engine" \ "test+" \ ":engine Runs the top-level Keyman Engine for Web unit tests" \ ":libraries Runs all unit tests for KMW's submodules. Currently excludes predictive-text tests" \ -- GitLab From a92917f947fabef7e798c1ed733e5d57444c8958 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 25 May 2023 12:54:27 +0700 Subject: [PATCH 277/386] chore(developer): replace cwrap wasm bindings This moves the remainder of the WASM interfaces in kmcmplib to using emscripten bind. It is a little bit of a step backwards at present for parseUnicodeSet, because I've changed the output buffer type to an int for the purposes of simplifying the binding just now. But that can be improved later, and at least we are consistent with the binding methods. Next step is to move the filesystem access out of kmcmplib. --- .../src/kmc-kmn/src/compiler/compiler.ts | 65 ++++--------------- developer/src/kmcmplib/include/kmcmplibapi.h | 15 ++--- .../src/kmcmplib/src/CompilerInterfaces.cpp | 34 +--------- developer/src/kmcmplib/src/meson.build | 2 +- developer/src/kmcmplib/src/uset-api.cpp | 13 ++-- .../src/kmcmplib/tests/uset-api-test.cpp | 30 +++++---- 6 files changed, 43 insertions(+), 116 deletions(-) diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index c46b894cb3..a88999e99c 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -42,50 +42,10 @@ const baseOptions: CompilerOptions = { */ let callbackProcIdentifier = 0; -/** - * Pointer in wasm-space - */ -type WasmPtr = number; - -/** - * The wrapped functions - */ -class WasmWrapper { - Module: any; - - compileKeyboardFile?: (pszInfile: string, pszOutfile: string, aSaveDebug: number, aCompilerWarningsAsErrors: number, aWarnDeprecatedCode: number, msgProc: string) => boolean; - parseUnicodeSet?: (pat: string, buf: WasmPtr, length: number) => number; - setCompilerOptions?: (shouldAddCompilerVersion: number) => boolean; - - constructor(wasmModule: any) { - this.Module = wasmModule; - if (!wasmModule) { - throw Error(`wasm host did not load`); - } - this.compileKeyboardFile = this.Module.cwrap('kmcmp_Wasm_CompileKeyboardFile', 'boolean', ['string', 'string', 'number', 'number', 'number', 'string']); - this.parseUnicodeSet = this.Module.cwrap('kmcmp_Wasm_ParseUnicodeSet', 'number', ['string', 'number', 'number']); - this.setCompilerOptions = this.Module.cwrap('kmcmp_Wasm_SetCompilerOptions', 'boolean', ['number']); - - if (this.parseUnicodeSet === undefined - || this.setCompilerOptions === undefined - || this.compileKeyboardFile === undefined) { - throw Error(`some wasm functions did not load properly.`); - } - } - - /** - * Entry point into Wasm functions - * @returns WasmWrapper - */ - public static async load() : Promise { - return new WasmWrapper(await loadWasmHost()); - } -}; - export class KmnCompiler { + private Module: any; callbackName: string; callbacks: CompilerCallbacks; - wasm: WasmWrapper; constructor() { this.callbackName = 'kmnCompilerCallback' + callbackProcIdentifier; @@ -94,9 +54,9 @@ export class KmnCompiler { public async init(callbacks: CompilerCallbacks): Promise { this.callbacks = callbacks; - if(!this.wasm) { + if(!this.Module) { try { - this.wasm = await WasmWrapper.load(); + this.Module = await loadWasmHost(); } catch(e: any) { this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({e})); return false; @@ -114,7 +74,7 @@ export class KmnCompiler { // Can't report a message here. throw Error('Must call Compiler.init(callbacks) before proceeding'); } - if(!this.wasm) { // fail if wasm not loaded or function not found + if(!this.Module) { // fail if wasm not loaded or function not found this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({})); return false; } @@ -153,7 +113,7 @@ export class KmnCompiler { private runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { let result: CompilerResult = {}; - let wasm_interface = new this.wasm.Module.CompilerInterface(); + let wasm_interface = new this.Module.CompilerInterface(); let wasm_result = null; try { wasm_interface.saveDebug = options.saveDebug; @@ -161,7 +121,7 @@ export class KmnCompiler { wasm_interface.warnDeprecatedCode = options.warnDeprecatedCode; wasm_interface.messageCallback = this.callbackName; wasm_interface.loadFileCallback = this.callbackName; // TODO: this is wrong, needs to be a new callback; not yet used though - wasm_result = this.wasm.Module.kmcmp_compile(infile, wasm_interface); + wasm_result = this.Module.kmcmp_compile(infile, wasm_interface); if(!wasm_result.result) { return null; } @@ -175,7 +135,7 @@ export class KmnCompiler { result.kmx = { filename: outfile, - data: new Uint8Array(this.wasm.Module.HEAP8.buffer, wasm_result.kmx, wasm_result.kmxSize) + data: new Uint8Array(this.Module.HEAP8.buffer, wasm_result.kmx, wasm_result.kmxSize) }; return result; @@ -230,16 +190,15 @@ export class KmnCompiler { if (!bufferSize) { bufferSize = 100; // TODO-LDML: Preflight mode? Reuse buffer? } - const { Module } = this.wasm; - const buf = Module.asm.malloc(bufferSize * 2 * Module.HEAPU32.BYTES_PER_ELEMENT); + const buf = this.Module.asm.malloc(bufferSize * 2 * this.Module.HEAPU32.BYTES_PER_ELEMENT); // TODO-LDML: Catch OOM - const rc = this.wasm.parseUnicodeSet(pattern, buf, bufferSize); + const rc = this.Module.kmcmp_parseUnicodeSet(pattern, buf, bufferSize); if (rc >= 0) { const ranges = []; - const startu = (buf / Module.HEAPU32.BYTES_PER_ELEMENT); + const startu = (buf / this.Module.HEAPU32.BYTES_PER_ELEMENT); for (let i = 0; i < rc; i++) { - const low = Module.HEAPU32[startu + (i * 2) + 0]; - const high = Module.HEAPU32[startu + (i * 2) + 1]; + const low = this.Module.HEAPU32[startu + (i * 2) + 0]; + const high = this.Module.HEAPU32[startu + (i * 2) + 1]; ranges.push([low, high]); } // TODO-LDML: no free?? diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index 2fb35979b2..f4591612ec 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -62,7 +62,7 @@ EXTERN bool kmcmp_ValidateJsonFile( ); /** - * kmcmp_ParseUnicodeSet is successful if it returns >= USET_OK + * kmcmp_parseUnicodeSet is successful if it returns >= USET_OK */ static const int KMCMP_USET_OK = 0; @@ -83,24 +83,19 @@ static const int KMCMP_ERROR_UNSUPPORTED_PROPERTY = -3; */ static const int KMCMP_FATAL_OUT_OF_RANGE = -4; -/** - * Function pointer to kmcmp_ParseUnicodeSet - */ -typedef int (*kmcmp_ParseUnicodeSetProc)(const char* szText, uint32_t* output, uint32_t outputLength); - /** * Parse a UnicodeSet into 32-bit ranges. * For example, "[]" will return 0 (KMCMP_USET_OK) as a zero-length set. * "[" will return KMCMP_ERROR_SYNTAX_ERR, * and "[x A-C]" will return 2 and [0x41, 0x43, 0x78, 0x78] - * @param szText input txt, null terminated, in UTF-8 format + * @param text input txt, null terminated, in UTF-8 format * @param outputBuffer output buffer, owned by caller: Pairs of ranges in order * @param outputBufferSize length of output buffer. Needs to be twice the number of expected ranges * @return If >= KMCMP_USET_OK, number of ranges, otherwise one of the negative error values. */ -EXTERN int kmcmp_ParseUnicodeSet( - const char* szText, - uint32_t* outputBuffer, +EXTERN int kmcmp_parseUnicodeSet( + const std::string text, + uintptr_t outputBuffer_, uint32_t outputBufferSize ); diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index a1609d3544..56ecf4cdcf 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -46,39 +46,6 @@ int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, char* szText, void* c return wasm_msgproc(line, dwMsgCode, szText, msgProc); } -//DEPRECATED -EXTERN bool kmcmp_Wasm_SetCompilerOptions(int ShouldAddCompilerVersion) { - KMCMP_COMPILER_OPTIONS options; - options.dwSize = sizeof(KMCMP_COMPILER_OPTIONS); - options.ShouldAddCompilerVersion = ShouldAddCompilerVersion; - return kmcmp_SetCompilerOptions(&options); -} - -//DEPRECATED -EXTERN bool kmcmp_Wasm_CompileKeyboardFile(char* pszInfile, - char* pszOutfile, int ASaveDebug, int ACompilerWarningsAsErrors, - int AWarnDeprecatedCode, char* msgProc -) { - return kmcmp_CompileKeyboardFile( - pszInfile, - pszOutfile, - ASaveDebug, - ACompilerWarningsAsErrors, - AWarnDeprecatedCode, - wasm_CompilerMessageProc, - msgProc - ); -} - -EXTERN int kmcmp_Wasm_ParseUnicodeSet(char* pat, - uint32_t* buf, int length -) { - return kmcmp_ParseUnicodeSet( - pat, buf, length - ); -} - - struct COMPILER_INTERFACE { bool saveDebug; bool compilerWarningsAsErrors; @@ -162,6 +129,7 @@ EMSCRIPTEN_BINDINGS(compiler_interface) { ; emscripten::function("kmcmp_compile", &kmcmp_compile); + emscripten::function("kmcmp_parseUnicodeSet", &kmcmp_parseUnicodeSet); } #endif diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 292b158ce2..3cd9abd6e1 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -24,7 +24,7 @@ endif name_suffix = [] if cpp_compiler.get_id() == 'emscripten' - links += ['-lnodefs.js', '-sMODULARIZE', '-sEXPORT_ES6', '--whole-archive', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'cwrap\', \'UTF8ToString\']'] + links += ['-lnodefs.js', '-sMODULARIZE', '-sEXPORT_ES6', '--whole-archive', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] # tests are building as ES6 so we need to declare the file extension # note that meson currently struggles with the sanitycheckc_cross.exe # program, because it has a hard coded extension (.exe) which is not diff --git a/developer/src/kmcmplib/src/uset-api.cpp b/developer/src/kmcmplib/src/uset-api.cpp index 1aa22477b2..c2c72cab34 100644 --- a/developer/src/kmcmplib/src/uset-api.cpp +++ b/developer/src/kmcmplib/src/uset-api.cpp @@ -4,16 +4,13 @@ #include "unicode/uniset.h" #include "unicode/unistr.h" -EXTERN int kmcmp_ParseUnicodeSet( - const char* szText, - uint32_t* outputBuffer, +EXTERN int kmcmp_parseUnicodeSet( + const std::string text, + uintptr_t outputBuffer_, uint32_t outputBufferSize ) { - if (szText == nullptr) { - // null string coming in - return KMCMP_ERROR_SYNTAX_ERR; - } - const icu::UnicodeString str = icu::UnicodeString::fromUTF8(szText); + uint32_t* outputBuffer = reinterpret_cast(outputBuffer_); + const icu::UnicodeString str = icu::UnicodeString::fromUTF8(text.c_str()); if (str.isBogus() || str.isEmpty()) { // empty string return KMCMP_ERROR_SYNTAX_ERR; diff --git a/developer/src/kmcmplib/tests/uset-api-test.cpp b/developer/src/kmcmplib/tests/uset-api-test.cpp index 4fe599ca2e..61d263ad15 100644 --- a/developer/src/kmcmplib/tests/uset-api-test.cpp +++ b/developer/src/kmcmplib/tests/uset-api-test.cpp @@ -19,30 +19,32 @@ #include "../src/compfile.h" #include -void test_kmcmp_ParseUnicodeSetProc(); +void test_kmcmp_parseUnicodeSet(); // std::vector error_vec; int main(int argc, char *argv[]) { - test_kmcmp_ParseUnicodeSetProc(); + test_kmcmp_parseUnicodeSet(); return 0; } -void test_kmcmp_ParseUnicodeSetProc() { +void test_kmcmp_parseUnicodeSet() { { // null test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[]", buf_, bufsiz); assert(rc == KMCMP_USET_OK); } { // basic test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[x A-C]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[x A-C]", buf_, bufsiz); assert(rc == 2); assert(buf[0] == 0x41); assert(buf[1] == 0x43); @@ -53,7 +55,8 @@ void test_kmcmp_ParseUnicodeSetProc() { // bigger test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[[🙀A-C]-[CB]]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[[🙀A-C]-[CB]]", buf_, bufsiz); assert(rc == 2); assert(buf[0] == 0x41); assert(buf[1] == 0x41); @@ -64,35 +67,40 @@ void test_kmcmp_ParseUnicodeSetProc() { // overflow test const auto bufsiz = 1; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[x A-C]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[x A-C]", buf_, bufsiz); assert(rc == KMCMP_FATAL_OUT_OF_RANGE); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[:Adlm:]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[:Adlm:]", buf_, bufsiz); assert(rc == KMCMP_ERROR_UNSUPPORTED_PROPERTY); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[[\\p{Mn}]&[A-Z]]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[[\\p{Mn}]&[A-Z]]", buf_, bufsiz); assert(rc == KMCMP_ERROR_UNSUPPORTED_PROPERTY); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[abc{def}]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[abc{def}]", buf_, bufsiz); assert(rc == KMCMP_ERROR_HAS_STRINGS); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[[]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[[]", buf_, bufsiz); assert(rc == KMCMP_ERROR_SYNTAX_ERR); } } -- GitLab From 60a4a88af3d6ff6c5fc312952e10ba13ce0343bd Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 26 May 2023 15:19:19 +0700 Subject: [PATCH 278/386] fix(web): input-processor unit test - param missing --- common/web/input-processor/tests/cases/inputProcessor.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/web/input-processor/tests/cases/inputProcessor.js b/common/web/input-processor/tests/cases/inputProcessor.js index 8c42cee98f..f3f3ca6b04 100644 --- a/common/web/input-processor/tests/cases/inputProcessor.js +++ b/common/web/input-processor/tests/cases/inputProcessor.js @@ -7,6 +7,8 @@ const require = createRequire(import.meta.url); import InputProcessor from '#./text/inputProcessor.js'; import { KeyboardInterface, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; + +import { Worker } from '@keymanapp/lexical-model-layer/node'; import * as utils from '@keymanapp/web-utils'; // Required initialization setup. @@ -31,7 +33,9 @@ describe('InputProcessor', function() { }); it('has expected default values after initialization', function () { - let core = new InputProcessor(device); + // Can construct without the second parameter; if so, the final assertion - .mayPredict + // will be invalidated. (No worker, no ability to predict.) + let core = new InputProcessor(device, Worker.constructInstance()); assert.isOk(core.keyboardProcessor); assert.isDefined(core.keyboardProcessor.contextDevice); -- GitLab From bab6ee195a3cb3191a06f485d209bc88f45f84fe Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 29 May 2023 10:21:16 +0700 Subject: [PATCH 279/386] docs(web): adds review-requested comment changes --- web/src/app/browser/src/keymanEngine.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 7a7a5f2fc0..1f48cfbf9c 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -239,6 +239,9 @@ export default class KeymanEngine extends KeymanEngineBase Date: Mon, 29 May 2023 10:37:46 +0700 Subject: [PATCH 280/386] chore(web): minor build-script cleanup --- web/src/app/ui/build.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/src/app/ui/build.sh b/web/src/app/ui/build.sh index 78b3df2c32..d530a31fdb 100755 --- a/web/src/app/ui/build.sh +++ b/web/src/app/ui/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary -- GitLab From ff27a90dea28b2328d4b1f361dc5d79b4b1ae672 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 29 May 2023 10:59:10 +0700 Subject: [PATCH 281/386] chore(web): post-install changes after merge --- package-lock.json | 142 +++------------------------------------------- 1 file changed, 7 insertions(+), 135 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a6663b179..13b516d9b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -316,43 +316,8 @@ "mocha-teamcity-reporter": "^4.0.0", "promise-status-async": "^1.2.10", "sinon": "^14.0.0", - "ts-node": "^9.1.1", - "typescript": "^4.5.4" - } - }, - "common/web/gesture-recognizer/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "common/web/gesture-recognizer/node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" + "ts-node": "^10.9.1", + "typescript": "^4.9.5" } }, "common/web/input-processor": { @@ -891,26 +856,6 @@ "type-detect": "4.0.8" } }, - "developer/src/kmc-kmn/node_modules/@sinonjs/samsam": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", - "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "developer/src/kmc-kmn/node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, "developer/src/kmc-kmn/node_modules/@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", @@ -964,12 +909,6 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "developer/src/kmc-kmn/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, "developer/src/kmc-kmn/node_modules/js-yaml": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", @@ -1071,37 +1010,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "developer/src/kmc-kmn/node_modules/nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "developer/src/kmc-kmn/node_modules/nise/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "developer/src/kmc-kmn/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, "developer/src/kmc-kmn/node_modules/sinon": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.2.tgz", @@ -1198,6 +1106,7 @@ } }, "developer/src/kmc-ldml": { + "name": "@keymanapp/kmc-ldml", "license": "MIT", "dependencies": { "@keymanapp/keyman-version": "*", @@ -2461,14 +2370,14 @@ "resolved": "developer/src/server", "link": true }, - "node_modules/@keymanapp/gesture-recognizer": { - "resolved": "common/web/gesture-recognizer", - "link": true - }, "node_modules/@keymanapp/developer-test-helpers": { "resolved": "developer/src/common/web/test-helpers", "link": true }, + "node_modules/@keymanapp/gesture-recognizer": { + "resolved": "common/web/gesture-recognizer", + "link": true + }, "node_modules/@keymanapp/hextobin": { "resolved": "common/tools/hextobin", "link": true @@ -8285,15 +8194,6 @@ "type-detect": "4.0.8" } }, - "node_modules/nise/node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0" - } - }, "node_modules/nise/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -9377,34 +9277,6 @@ "sinon": ">=4.0.0" } }, - "node_modules/sinon/node_modules/diff": { - "version": "3.5.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/sinon/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, "node_modules/sinon/node_modules/@sinonjs/commons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", -- GitLab From 6ab5683d2f6bc7860a131af34035d89edc1dd8bf Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 29 May 2023 11:02:06 +0700 Subject: [PATCH 282/386] chore(web): minor build script update --- common/web/gesture-recognizer/build.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/common/web/gesture-recognizer/build.sh b/common/web/gesture-recognizer/build.sh index ea84de3863..43088489f0 100755 --- a/common/web/gesture-recognizer/build.sh +++ b/common/web/gesture-recognizer/build.sh @@ -1,10 +1,4 @@ #!/usr/bin/env bash -# -# Builds the include script for the current Keyman version. -# - -# Exit on command failure and when using unset variables: -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -26,7 +20,7 @@ builder_describe "Builds the gesture-recognition model for Web-based on-screen k "test" \ ":module" \ ":tools tools for testing & developing test resources for this module" \ - "--ci sets the --ci option for child scripts (i.e, the `test` action)" + "--ci sets the --ci option for child scripts (i.e, the $(builder_term test) action)" builder_describe_outputs \ configure:module /node_modules \ @@ -71,5 +65,5 @@ if builder_start_action test:module; then fi if builder_has_action test:tools && ! builder_has_action test:module; then - echo "The ${BUILDER_TERM_START}test:tools${BUILDER_TERM_END} action is currently a no-op." + echo "The $(builder_term test:tools) action is currently a no-op." fi \ No newline at end of file -- GitLab From d763cf315ab7581fe1eb45d8ec652d4af90eaf67 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 May 2023 08:14:23 +0700 Subject: [PATCH 283/386] chore(developer): refactor kmcmplib interfaces Relates to #8493. * Removes kmcmplib calls from kmcmpdll (now that we have kmc) * Removes old kmcmp_CompileKeyboardFile and kmcmp_CompileKeyboardFileToBuffer functions in preference for a much cleaner kmcmp_CompileKeyboard function * Removes json validation helper from kmcmplib (we'll use js-native json schema validation instead) This change means that we no longer need to keep compfile.h consistent between kmcmplib and kmcmpdll. This will simplify upcoming refactoring of kmcmplib. kmcmplib no longer writes files, but it does still read them. The next refactor will move file load responsibility into the caller. --- common/windows/cpp/include/legacy_kmx_file.h | 1 - .../src/common/delphi/compiler/compile.pas | 1 - .../src/kmc-kmn/src/compiler/compiler.ts | 12 +- developer/src/kmc-kmn/test/test-compiler.ts | 14 +- developer/src/kmcmpdll/Compiler.cpp | 38 ---- developer/src/kmcmpdll/json-validation.cpp | 10 - developer/src/kmcmpdll/kmcmpdll.vcxproj | 16 +- developer/src/kmcmplib/include/kmcmplibapi.h | 73 ++++--- developer/src/kmcmplib/include/meson.build | 4 +- .../src/kmcmplib/src/CompilerInterfaces.cpp | 202 ++++++------------ .../src/kmcmplib/src/json-validation.cpp | 68 ------ developer/src/kmcmplib/src/meson.build | 4 - developer/src/kmcmplib/tests/api-test.cpp | 38 +--- developer/src/kmcmplib/tests/kmcompxtest.cpp | 35 ++- ...er.System.Project.kmnProjectFileAction.pas | 2 +- 15 files changed, 160 insertions(+), 358 deletions(-) delete mode 100644 developer/src/kmcmplib/src/json-validation.cpp diff --git a/common/windows/cpp/include/legacy_kmx_file.h b/common/windows/cpp/include/legacy_kmx_file.h index ae18543312..d961250912 100644 --- a/common/windows/cpp/include/legacy_kmx_file.h +++ b/common/windows/cpp/include/legacy_kmx_file.h @@ -409,7 +409,6 @@ typedef COMP_GROUP *PCOMP_GROUP; typedef struct _COMPILER_OPTIONS { DWORD dwSize; BOOL ShouldAddCompilerVersion; - BOOL UseKmcmpLib; } COMPILER_OPTIONS; typedef COMPILER_OPTIONS *PCOMPILER_OPTIONS; diff --git a/developer/src/common/delphi/compiler/compile.pas b/developer/src/common/delphi/compiler/compile.pas index b89ef0eb72..0124150ef6 100644 --- a/developer/src/common/delphi/compiler/compile.pas +++ b/developer/src/common/delphi/compiler/compile.pas @@ -176,7 +176,6 @@ type TCompilerOptions = record dwSize: DWORD; ShouldAddCompilerVersion: BOOL; - UseKmcmpLib: BOOL; end; COMPILER_OPTIONS = TCompilerOptions; diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index a88999e99c..606bec638c 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -114,14 +114,17 @@ export class KmnCompiler { private runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { let result: CompilerResult = {}; let wasm_interface = new this.Module.CompilerInterface(); + let wasm_options = new this.Module.CompilerOptions(); let wasm_result = null; try { - wasm_interface.saveDebug = options.saveDebug; - wasm_interface.compilerWarningsAsErrors = options.compilerWarningsAsErrors; - wasm_interface.warnDeprecatedCode = options.warnDeprecatedCode; + wasm_options.saveDebug = options.saveDebug; + wasm_options.compilerWarningsAsErrors = options.compilerWarningsAsErrors; + wasm_options.warnDeprecatedCode = options.warnDeprecatedCode; + wasm_options.shouldAddCompilerVersion = options.shouldAddCompilerVersion; + wasm_options.target = 0; //CKF_KEYMAN; TODO, support KMW wasm_interface.messageCallback = this.callbackName; wasm_interface.loadFileCallback = this.callbackName; // TODO: this is wrong, needs to be a new callback; not yet used though - wasm_result = this.Module.kmcmp_compile(infile, wasm_interface); + wasm_result = this.Module.kmcmp_compile(infile, wasm_options, wasm_interface); if(!wasm_result.result) { return null; } @@ -147,6 +150,7 @@ export class KmnCompiler { wasm_result.delete(); } wasm_interface.delete(); + wasm_options.delete(); } } diff --git a/developer/src/kmc-kmn/test/test-compiler.ts b/developer/src/kmc-kmn/test/test-compiler.ts index 925656f8f5..8c35398361 100644 --- a/developer/src/kmc-kmn/test/test-compiler.ts +++ b/developer/src/kmc-kmn/test/test-compiler.ts @@ -86,20 +86,20 @@ describe('Compiler class', function() { const kmxFixture = fixtureDir + '/binary/caps_lock_layer_3620.kmx'; const kvkFixture = fixtureDir + '/binary/caps_lock_layer_3620.kvk'; - const kmxfile = __dirname + '/caps_lock_layer_3620.kmx'; - const kvkfile = __dirname + '/caps_lock_layer_3620.kvk'; + const resultingKmxfile = __dirname + '/caps_lock_layer_3620.kmx'; + const resultingKvkfile = __dirname + '/caps_lock_layer_3620.kvk'; - assert(compiler.run(infile, kmxfile, {saveDebug: true, shouldAddCompilerVersion: false})); + assert(compiler.run(infile, resultingKmxfile, {saveDebug: true, shouldAddCompilerVersion: false})); - assert(fs.existsSync(kmxfile)); - assert(fs.existsSync(kvkfile)); + assert(fs.existsSync(resultingKmxfile)); + assert(fs.existsSync(resultingKvkfile)); - const kmxData = fs.readFileSync(kmxfile); + const kmxData = fs.readFileSync(resultingKmxfile); const kmxFixtureData = fs.readFileSync(kmxFixture); assert.equal(kmxData.byteLength, kmxFixtureData.byteLength); assert.deepEqual(kmxData, kmxFixtureData); - const kvkData = fs.readFileSync(kvkfile); + const kvkData = fs.readFileSync(resultingKvkfile); const kvkFixtureData = fs.readFileSync(kvkFixture); assert.equal(kvkData.byteLength, kvkFixtureData.byteLength); assert.deepEqual(kvkData, kvkFixtureData); diff --git a/developer/src/kmcmpdll/Compiler.cpp b/developer/src/kmcmpdll/Compiler.cpp index 8cc28d2ab7..100e43fc5b 100644 --- a/developer/src/kmcmpdll/Compiler.cpp +++ b/developer/src/kmcmpdll/Compiler.cpp @@ -90,8 +90,6 @@ #include "UnreachableRules.h" #include "CheckForDuplicates.h" -#include "../kmcmplib/include/kmcmplibapi.h" - int xatoi(PWSTR *p); int atoiW(PWSTR p); void safe_wcsncpy(PWSTR out, PWSTR in, int cbMax); @@ -291,33 +289,15 @@ BOOL AddCompileMessage(DWORD msg) return FALSE; } -bool flag_use_new_kmcomp = true; // flag to switch to new kmcompx - extern "C" BOOL __declspec(dllexport) SetCompilerOptions(PCOMPILER_OPTIONS options) { if(!options || options->dwSize < sizeof(COMPILER_OPTIONS)) { return FALSE; } - flag_use_new_kmcomp = options->UseKmcmpLib; - - //printf("\n---> started in SetCompilerOptions() of kmcmpdll\n"); - if ( flag_use_new_kmcomp ) - { - KMCMP_COMPILER_OPTIONS kmcmp_options = {0}; - kmcmp_options.dwSize = sizeof(KMCMP_COMPILER_OPTIONS); - kmcmp_options.ShouldAddCompilerVersion = options->ShouldAddCompilerVersion; - return kmcmp_SetCompilerOptions(&kmcmp_options); - } - //printf("---> stayed in SetCompilerOptions() of kmcmpdll\n"); - FShouldAddCompilerVersion = options->ShouldAddCompilerVersion; return TRUE; } -int kmcmpMsgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { - return static_cast(context)(line, dwMsgCode, szText); -} - extern "C" BOOL __declspec(dllexport) CompileKeyboardFile(PSTR pszInfile, PSTR pszOutfile, BOOL ASaveDebug, BOOL ACompilerWarningsAsErrors, BOOL AWarnDeprecatedCode, CompilerMessageProc pMsgProc) // I4865 // I4866 { HANDLE hInfile = INVALID_HANDLE_VALUE, hOutfile = INVALID_HANDLE_VALUE; @@ -325,15 +305,6 @@ extern "C" BOOL __declspec(dllexport) CompileKeyboardFile(PSTR pszInfile, PSTR p DWORD len; char str[260]; - //printf("\n---> started in CompileKeyboardFile() of kmcmpdll\n"); - - if ( flag_use_new_kmcomp ) - { - return kmcmp_CompileKeyboardFile(pszInfile, pszOutfile, ASaveDebug, ACompilerWarningsAsErrors,AWarnDeprecatedCode, kmcmpMsgproc, (void*) pMsgProc); - } - - //printf("---> stayed in CompileKeyboardFile() of kmcmpdll\n"); - FSaveDebug = ASaveDebug; FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 FWarnDeprecatedCode = AWarnDeprecatedCode; // I4866 @@ -418,15 +389,6 @@ extern "C" BOOL __declspec(dllexport) CompileKeyboardFileToBuffer(PSTR pszInfile DWORD len; char str[260]; - //printf("\n---> started in CompileKeyboardFileToBuffer() of kmcmpdll\n"); - if ( flag_use_new_kmcomp ) - { - return kmcmp_CompileKeyboardFileToBuffer( pszInfile, (void*) pfkBuffer, ACompilerWarningsAsErrors, AWarnDeprecatedCode, kmcmpMsgproc, (void*) pMsgProc, Target); - } - - //printf("---> stayed in CompileKeyboardFileToBuffer() of kmcmpdll\n"); - - FSaveDebug = TRUE; // I3681 FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 FWarnDeprecatedCode = AWarnDeprecatedCode; // I4866 diff --git a/developer/src/kmcmpdll/json-validation.cpp b/developer/src/kmcmpdll/json-validation.cpp index e35dc0485d..c3c1af0484 100644 --- a/developer/src/kmcmpdll/json-validation.cpp +++ b/developer/src/kmcmpdll/json-validation.cpp @@ -1,14 +1,11 @@ #include #include -#include "../kmcmplib/include/kmcmplibapi.h" #include typedef bool (*kmcmp_ValidateJsonMessageProc)(int64_t offset, const char* szText, void* context); -extern bool flag_use_new_kmcomp; - using nlohmann::json; using nlohmann::json_uri; using nlohmann::json_schema_draft4::json_validator; @@ -35,13 +32,6 @@ bool kmcmpMessageProc(int64_t offset, const char* szText, void* context) { extern "C" BOOL __declspec(dllexport) ValidateJsonFile(PWSTR pwszSchemaFile, PWSTR pwszJsonFile, ValidateJsonMessageProc MessageProc) { - if ( flag_use_new_kmcomp ) - { - std::fstream f(pwszSchemaFile); - std::fstream fd(pwszJsonFile); - return kmcmp_ValidateJsonFile(f, fd, kmcmpMessageProc, (void*)MessageProc); - } - std::fstream f(pwszSchemaFile); if (!f.good()) { MessageProc(-1, "Schema file could not be loaded."); diff --git a/developer/src/kmcmpdll/kmcmpdll.vcxproj b/developer/src/kmcmpdll/kmcmpdll.vcxproj index c7f7e1f48f..2bcc038bd0 100644 --- a/developer/src/kmcmpdll/kmcmpdll.vcxproj +++ b/developer/src/kmcmpdll/kmcmpdll.vcxproj @@ -89,25 +89,25 @@ $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x86\$(Configuration)\src;$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator $(ProjectDir)obj\$(Platform)\$(Configuration)\ $(ProjectName).x64 $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x64\$(Configuration)\src;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x86\$(Configuration)\src;$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator $(ProjectDir)obj\$(Platform)\$(Configuration)\ $(ProjectName).x64 $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x64\$(Configuration)\src;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) @@ -142,7 +142,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true @@ -190,7 +190,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true Windows @@ -233,7 +233,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true true @@ -277,7 +277,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true true diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index f4591612ec..4054d81e81 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -16,49 +16,48 @@ #define EXTERN EMSCRIPTEN_KEEPALIVE #endif -typedef struct _KMCMP_COMPILER_OPTIONS { - uint32_t dwSize; - bool ShouldAddCompilerVersion; -} KMCMP_COMPILER_OPTIONS; - -EXTERN bool kmcmp_SetCompilerOptions( - KMCMP_COMPILER_OPTIONS* options -); - -typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, char* szText, void* context); - -EXTERN bool kmcmp_CompileKeyboardFile( - char* pszInfile, - char* pszOutfile, - bool ASaveDebug, - bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, - kmcmp_CompilerMessageProc pMsgproc, - void* AmsgprocContext -); - /* Compile target */ #define CKF_KEYMAN 0 #define CKF_KEYMANWEB 1 -EXTERN bool kmcmp_CompileKeyboardFileToBuffer( - char* pszInfile, - void* pfkBuffer, - bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, - kmcmp_CompilerMessageProc pMsgproc, - void* AmsgprocContext, - int Target -); - -typedef bool (*kmcmp_ValidateJsonMessageProc)(int64_t offset, const char* szText, void* context); +struct KMCMP_COMPILER_OPTIONS { + bool saveDebug; + bool compilerWarningsAsErrors; + bool warnDeprecatedCode; + bool shouldAddCompilerVersion; + int target; // CKF_KEYMAN, CKF_KEYMANWEB +}; + +struct KMCMP_COMPILER_RESULT { + void* kmx; + size_t kmxSize; + std::string kvksFilename; +}; + +// TODO: parameters in UTF-8 +typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, char* szText, void* context); -EXTERN bool kmcmp_ValidateJsonFile( - std::fstream& f, - std::fstream& fd, - kmcmp_ValidateJsonMessageProc MessageProc, - void* context +// parameters in UTF-8 +// TODO typical usage: +// if(!kmcmp_LoadFileProc("filename.ico", "/tmp/filename.kmn", nullptr, &size)) { +// return error; +// } +// buf = new unsigned char[size]; +// if(!kmcmp_LoadFileProc("filename.ico", "/tmp/filename.kmn", buf, &size)) { +// delete[] buf; +// return error; +// } +typedef bool (*kmcmp_LoadFileProc)(char* loadFilename, char* baseFilename, void* buffer, int* bufferSize); + +// Parameters in UTF-8 +EXTERN bool kmcmp_CompileKeyboard( + const char* pszInfile, + const KMCMP_COMPILER_OPTIONS& options, + kmcmp_CompilerMessageProc messageProc, + kmcmp_LoadFileProc loadFileProc, + const void* procContext, + KMCMP_COMPILER_RESULT& result ); /** diff --git a/developer/src/kmcmplib/include/meson.build b/developer/src/kmcmplib/include/meson.build index f98a4d8c76..dde8e69f6b 100644 --- a/developer/src/kmcmplib/include/meson.build +++ b/developer/src/kmcmplib/include/meson.build @@ -20,7 +20,5 @@ configure_file( inc = include_directories( '.', '../../common/include', - '../../../../common/include', - '../../ext/json', - '../../ext/json-schema-validator' + '../../../../common/include' ) diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index 56ecf4cdcf..71e1e895f2 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -12,19 +12,6 @@ #include "../../../../common/windows/cpp/include/keymanversion.h" bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk); -bool CompileKeyboard(const char* pszInfile, - void* pfkBuffer, bool ASaveDebug, bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, kmcmp_CompilerMessageProc pMsgproc, const void* AmsgprocContext, - int Target); - -EXTERN bool kmcmp_SetCompilerOptions(KMCMP_COMPILER_OPTIONS* options) { - //printf("°°-> changed to SetCompilerOptions() of kmcmplib \n"); - if(!options || options->dwSize < sizeof(KMCMP_COMPILER_OPTIONS)) { - return FALSE; - } - kmcmp::FShouldAddCompilerVersion = options->ShouldAddCompilerVersion; - return TRUE; -} #ifdef __EMSCRIPTEN__ @@ -46,17 +33,12 @@ int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, char* szText, void* c return wasm_msgproc(line, dwMsgCode, szText, msgProc); } -struct COMPILER_INTERFACE { - bool saveDebug; - bool compilerWarningsAsErrors; - bool warnDeprecatedCode; - bool shouldAddCompilerVersion; - int target; // CKF_KEYMAN, CKF_KEYMANWEB +struct WASM_COMPILER_INTERFACE { std::string messageCallback; // int line, uint32_t dwMsgCode, char* szText - std::string loadFileCallback; // char* infile, char* filenameRelativeToInfile --> buffer + std::string loadFileCallback; // TODO: char* filename, char* baseFilename --> buffer }; -struct COMPILER_RESULT { +struct WASM_COMPILER_RESULT { bool result; // Following are pointer offsets in heap + buffer size int kmx; @@ -67,104 +49,101 @@ struct COMPILER_RESULT { // TODO: additional data to be passed back }; -COMPILER_RESULT kmcmp_compile(std::string pszInfile, const COMPILER_INTERFACE intf) { - COMPILER_RESULT r = {false}; +WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPILER_OPTIONS options, const WASM_COMPILER_INTERFACE intf) { + WASM_COMPILER_RESULT r = {false}; + KMCMP_COMPILER_RESULT kr; - FILE_KEYBOARD fk; - - // TODO: this should be included in CompileKeyboard? - kmcmp::FShouldAddCompilerVersion = intf.shouldAddCompilerVersion; + r.kmx = 0; + r.kmxSize = 0; + r.kvksFilename = ""; - r.result = CompileKeyboard( + r.result = kmcmp_CompileKeyboard( pszInfile.c_str(), - &fk, - intf.saveDebug, - intf.compilerWarningsAsErrors, - intf.warnDeprecatedCode, + options, wasm_CompilerMessageProc, + nullptr, //wasm_LoadFileProc, intf.messageCallback.c_str(), - intf.target); - - if(!r.result) { - return r; + kr + ); + + if(r.result) { + // TODO: additional data as required by kmc_kmw + r.kmx = (int) kr.kmx; + r.kmxSize = (int) kr.kmxSize; + r.kvksFilename = kr.kvksFilename; } - KMX_DWORD msg; - KMX_BYTE* data = nullptr; - size_t dataSize = 0; - msg = WriteCompiledKeyboard(&fk, &data, dataSize); - //TODO: FreeKeyboardPointers(fk); - - if(msg != CERR_None) { - AddCompileError(msg); - r.result = FALSE; - return r; - } - - r.kmx = (int) data; - r.kmxSize = (int) dataSize; - r.kvksFilename = string_from_u16string(fk.extra->kvksFilename); // convert to UTF8 - return r; } EMSCRIPTEN_BINDINGS(compiler_interface) { - emscripten::class_("CompilerInterface") + + emscripten::class_("CompilerOptions") + .constructor<>() + .property("saveDebug", &KMCMP_COMPILER_OPTIONS::saveDebug) + .property("compilerWarningsAsErrors", &KMCMP_COMPILER_OPTIONS::compilerWarningsAsErrors) + .property("warnDeprecatedCode", &KMCMP_COMPILER_OPTIONS::warnDeprecatedCode) + .property("shouldAddCompilerVersion", &KMCMP_COMPILER_OPTIONS::shouldAddCompilerVersion) + .property("target", &KMCMP_COMPILER_OPTIONS::target) + ; + + emscripten::class_("CompilerInterface") .constructor<>() - .property("saveDebug", &COMPILER_INTERFACE::saveDebug) - .property("compilerWarningsAsErrors", &COMPILER_INTERFACE::compilerWarningsAsErrors) - .property("warnDeprecatedCode", &COMPILER_INTERFACE::warnDeprecatedCode) - .property("shouldAddCompilerVersion", &COMPILER_INTERFACE::shouldAddCompilerVersion) - .property("target", &COMPILER_INTERFACE::target) - .property("messageCallback", &COMPILER_INTERFACE::messageCallback) - .property("loadFileCallback", &COMPILER_INTERFACE::loadFileCallback) + .property("messageCallback", &WASM_COMPILER_INTERFACE::messageCallback) + .property("loadFileCallback", &WASM_COMPILER_INTERFACE::loadFileCallback) ; - emscripten::class_("CompilerResult") + emscripten::class_("CompilerResult") .constructor<>() - .property("result", &COMPILER_RESULT::result) - .property("kmx", &COMPILER_RESULT::kmx) - .property("kmxSize", &COMPILER_RESULT::kmxSize) - .property("kvksFilename", &COMPILER_RESULT::kvksFilename) + .property("result", &WASM_COMPILER_RESULT::result) + .property("kmx", &WASM_COMPILER_RESULT::kmx) + .property("kmxSize", &WASM_COMPILER_RESULT::kmxSize) + .property("kvksFilename", &WASM_COMPILER_RESULT::kvksFilename) ; - emscripten::function("kmcmp_compile", &kmcmp_compile); + emscripten::function("kmcmp_compile", &kmcmp_wasm_compile); emscripten::function("kmcmp_parseUnicodeSet", &kmcmp_parseUnicodeSet); } #endif -bool CompileKeyboard(const char* pszInfile, - void* pfkBuffer, bool ASaveDebug, bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, kmcmp_CompilerMessageProc pMsgproc, const void* AmsgprocContext, - int Target) { +EXTERN bool kmcmp_CompileKeyboard( + const char* pszInfile, + const KMCMP_COMPILER_OPTIONS& options, + kmcmp_CompilerMessageProc messageProc, + kmcmp_LoadFileProc loadFileProc, + const void* procContext, + KMCMP_COMPILER_RESULT& result +) { FILE* fp_in = NULL; KMX_CHAR str[260]; + FILE_KEYBOARD fk; - kmcmp::FSaveDebug = ASaveDebug; // I3681 - kmcmp::FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 - AWarnDeprecatedCode_GLOBAL_LIB = AWarnDeprecatedCode; - - kmcmp::CompileTarget = Target; + kmcmp::FSaveDebug = options.saveDebug; // I3681 + kmcmp::FCompilerWarningsAsErrors = options.compilerWarningsAsErrors; // I4865 + AWarnDeprecatedCode_GLOBAL_LIB = options.warnDeprecatedCode; + kmcmp::FShouldAddCompilerVersion = options.shouldAddCompilerVersion; + kmcmp::CompileTarget = options.target; - if (!pMsgproc || !pszInfile || !pfkBuffer) { + if (!messageProc || !pszInfile) { // TODO: add loadFileProc AddCompileError(CERR_BadCallParams); return FALSE; } PKMX_STR p; - if ((p = strrchr_slash((char*)pszInfile)) != nullptr) - { + if ((p = strrchr_slash((char*)pszInfile)) != nullptr) { strncpy(kmcmp::CompileDir, pszInfile, (int)(p - pszInfile + 1)); // I3481 kmcmp::CompileDir[(int)(p - pszInfile + 1)] = 0; } - else + else { kmcmp::CompileDir[0] = 0; + } - msgproc = pMsgproc; - msgprocContext = (void*)AmsgprocContext; + msgproc = messageProc; + //TODO: loadfileproc = loadFileProc; + msgprocContext = (void*)procContext; kmcmp::currentLine = 0; kmcmp::nErrors = 0; @@ -196,71 +175,32 @@ bool CompileKeyboard(const char* pszInfile, } kmcmp::CodeConstants = new kmcmp::NamedCodeConstants; - bool result = CompileKeyboardHandle(fp_in, static_cast(pfkBuffer)); + bool success = CompileKeyboardHandle(fp_in, &fk); delete kmcmp::CodeConstants; fclose(fp_in); - if (kmcmp::nErrors > 0) { - return FALSE; - } - - return result; -} - -EXTERN bool kmcmp_CompileKeyboardFileToBuffer(char* pszInfile, void* pfkBuffer, bool ACompilerWarningsAsErrors, bool AWarnDeprecatedCode, - kmcmp_CompilerMessageProc pMsgproc, void* AmsgprocContext, int Target) // I4865 // I4866 -{ - if (!pMsgproc || !pszInfile || !pfkBuffer) { - AddCompileError(CERR_BadCallParams); - return FALSE; - } - - return CompileKeyboard(pszInfile, pfkBuffer, TRUE, ACompilerWarningsAsErrors, AWarnDeprecatedCode, - pMsgproc, AmsgprocContext, Target); -} - -EXTERN bool kmcmp_CompileKeyboardFile(char* pszInfile, - char* pszOutfile, bool ASaveDebug, bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, kmcmp_CompilerMessageProc pMsgproc, void* AmsgprocContext) // I4865 // I4866 -{ - if (!pMsgproc || !pszInfile || !pszOutfile) { - AddCompileError(CERR_BadCallParams); - return FALSE; - } - - FILE_KEYBOARD fk; - if(!CompileKeyboard(pszInfile, &fk, ASaveDebug, ACompilerWarningsAsErrors, AWarnDeprecatedCode, - pMsgproc, AmsgprocContext, CKF_KEYMAN)) { - // any errors will have been reported directly by CompileKeyboard - return FALSE; - } - - FILE* fp_out = Open_File(pszOutfile, "wb"); - if (fp_out == NULL) { - AddCompileError(CERR_CannotCreateOutfile); + if (kmcmp::nErrors > 0 || !success) { return FALSE; } + // fill in result data KMX_DWORD msg; KMX_BYTE* data = nullptr; size_t dataSize = 0; - if ((msg = WriteCompiledKeyboard(&fk, &data, dataSize)) != CERR_None) { - AddCompileError(msg); - } else { - if(fwrite(data, 1, dataSize, fp_out) != dataSize) { - AddCompileError(CERR_UnableToWriteFully); - } - delete[] data; - } + msg = WriteCompiledKeyboard(&fk, &data, dataSize); - fclose(fp_out); + //TODO: FreeKeyboardPointers(fk); - if (kmcmp::nErrors > 0) { - remove(pszOutfile); + if(msg != CERR_None) { + AddCompileError(msg); return FALSE; } + result.kmx = data; + result.kmxSize = dataSize; + result.kvksFilename = string_from_u16string(fk.extra->kvksFilename); // convert to UTF8 + return TRUE; } diff --git a/developer/src/kmcmplib/src/json-validation.cpp b/developer/src/kmcmplib/src/json-validation.cpp deleted file mode 100644 index f58d8c9505..0000000000 --- a/developer/src/kmcmplib/src/json-validation.cpp +++ /dev/null @@ -1,68 +0,0 @@ - -#include -#include - -#include -#include "kmcompx.h" - -using nlohmann::json; -using nlohmann::json_uri; -using nlohmann::json_schema_draft4::json_validator; - -static void loader(const json_uri &uri, json &schema) -{ - std::fstream lf("." + uri.path()); - if (!lf.good()) - throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path()); - - lf >> schema; -} - -EXTERN bool kmcmp_ValidateJsonFile(std::fstream& f, std::fstream& fd, kmcmp_ValidateJsonMessageProc MessageProc, void* context) { - if (!f.good()) { - MessageProc(-1, "Schema file could not be loaded.", context); - return FALSE; - } - - // 1) Read the schema for the document you want to validate - json schema; - try { - f >> schema; - } - catch (std::exception &e) { - MessageProc(f.tellp(), e.what(), context); - return FALSE; - } - - // 2) create the validator and - json_validator validator(loader, [](const std::string &, const std::string &) {}); - - try { - // insert this schema as the root to the validator - // this resolves remote-schemas, sub-schemas and references via the given loader-function - validator.set_root_schema(schema); - } - catch (std::exception &e) { - MessageProc(-2, e.what(), context); - return FALSE; - } - - // 3) do the actual validation of the document - json document; - - if (!fd.good()) { - MessageProc(-3, "Json file could not be loaded.", context); - return FALSE; - } - - try { - fd >> document; - validator.validate(document); - } - catch (std::exception &e) { - MessageProc(fd.tellp(), e.what(), context); - return FALSE; - } - - return TRUE; -} \ No newline at end of file diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 3cd9abd6e1..8264c3d28e 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -43,7 +43,6 @@ lib = library('kmcmplib', 'DeprecationChecks.cpp', 'Edition.cpp', 'filesystem.cpp', - 'json-validation.cpp', 'NamedCodeConstants.cpp', 'versioning.cpp', 'virtualcharkeys.cpp', @@ -62,9 +61,6 @@ lib = library('kmcmplib', '../../../../common/windows/cpp/src/vkeys.cpp', 'xstring.cpp', - '../../ext/json-schema-validator/json-schema-draft4.json.cpp', - '../../ext/json-schema-validator/json-uri.cpp', - '../../ext/json-schema-validator/json-validator.cpp', version_res, cpp_args: defns + warns + flags, link_args: links, diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index 4c46b1f1a4..4bb0276a79 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -20,8 +20,7 @@ #include void setup(); -void test_kmcmp_CompileKeyboardFile(); -void test_kmcmp_CompileKeyboardFileToBuffer(); +void test_kmcmp_CompileKeyboard(); std::vector error_vec; @@ -40,10 +39,7 @@ int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { int main(int argc, char *argv[]) { setup(); - test_kmcmp_CompileKeyboardFile(); - - setup(); - test_kmcmp_CompileKeyboardFileToBuffer(); + test_kmcmp_CompileKeyboard(); return 0; } @@ -52,24 +48,7 @@ void setup() { error_vec.clear(); } -void test_kmcmp_CompileKeyboardFile() { - char kmn_file[L_tmpnam], kmx_file[L_tmpnam]; - tmpnam(kmn_file); - tmpnam(kmx_file); - - // Create an empty file - FILE *fp = fopen(kmn_file, "w"); - fclose(fp); - - // It should fail when a zero-byte file is passed in - assert(!kmcmp_CompileKeyboardFile(kmn_file, kmx_file, true, false, true, msgproc, nullptr)); - assert(error_vec.size() == 1); - assert(error_vec[0] == CERR_CannotReadInfile); - - unlink(kmn_file); -} - -void test_kmcmp_CompileKeyboardFileToBuffer() { +void test_kmcmp_CompileKeyboard() { char kmn_file[L_tmpnam], kmx_file[L_tmpnam]; tmpnam(kmn_file); tmpnam(kmx_file); @@ -78,10 +57,15 @@ void test_kmcmp_CompileKeyboardFileToBuffer() { FILE *fp = fopen(kmn_file, "w"); fclose(fp); - FILE_KEYBOARD fk; - // It should fail when a zero-byte file is passed in - assert(!kmcmp_CompileKeyboardFileToBuffer(kmn_file, &fk, true, false, msgproc, nullptr, CKF_KEYMAN)); + KMCMP_COMPILER_RESULT result; + KMCMP_COMPILER_OPTIONS options; + options.saveDebug = true; + options.compilerWarningsAsErrors = false; + options.warnDeprecatedCode = true; + options.shouldAddCompilerVersion = false; + options.target = CKF_KEYMAN; + assert(!kmcmp_CompileKeyboard(kmn_file, options, msgproc, nullptr, nullptr, result)); assert(error_vec.size() == 1); assert(error_vec[0] == CERR_CannotReadInfile); diff --git a/developer/src/kmcmplib/tests/kmcompxtest.cpp b/developer/src/kmcmplib/tests/kmcompxtest.cpp index 1ce542917d..87d9e53cf7 100644 --- a/developer/src/kmcmplib/tests/kmcompxtest.cpp +++ b/developer/src/kmcmplib/tests/kmcompxtest.cpp @@ -71,14 +71,15 @@ int main(int argc, char *argv[]) char first5[6] = "CERR_"; char* pfirst5 = first5; - KMCMP_COMPILER_OPTIONS kcopts; - kcopts.dwSize = sizeof(KMCMP_COMPILER_OPTIONS); - kcopts.ShouldAddCompilerVersion = false; // So we can compare against existing compiled keyboards that also don't have compiler version - if(!kmcmp_SetCompilerOptions(&kcopts)) { - return __LINE__; - } - - if(kmcmp_CompileKeyboardFile(kmn_file, kmx_file, true, false, true, msgproc, nullptr)) { + KMCMP_COMPILER_RESULT result; + KMCMP_COMPILER_OPTIONS options; + options.saveDebug = true; + options.compilerWarningsAsErrors = false; + options.warnDeprecatedCode = true; + options.shouldAddCompilerVersion = false; + options.target = CKF_KEYMAN; + + if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, nullptr, nullptr, result)) { char* testname = strrchr( (char*) kmn_file, '/') + 1; if(strncmp(testname, pfirst5, 5) == 0){ return __LINE__; // exit code: CERR_ in Name + no Error found @@ -87,26 +88,24 @@ int main(int argc, char *argv[]) // On non-win32 platforms, we cannot get kmcmpdll.dll to build keyboards // legacy-mode, so we'll compare to a hopefully existing file that we've // been passed - FILE* fp1 = Open_File(kmx_file, "rb"); + FILE* fp1 = Open_File(kmx_file, "wb"); if(!fp1) return __LINE__; + // Write out for reference + fwrite(result.kmx, 1, result.kmxSize, fp1); + FILE* fp2 = Open_File(reference_kmx, "rb"); if(!fp2) return __LINE__; // exit code: fail if no reference kmx file in build-folder - fseek(fp1, 0, SEEK_END); - auto sz1 = ftell(fp1); - fseek(fp1, 0, SEEK_SET); fseek(fp2, 0, SEEK_END); auto sz2 = ftell(fp2); fseek(fp2, 0, SEEK_SET); - if (sz1 != sz2) return __LINE__; // exit code: size of kmx-file in build differs from size of kmx-file in source folder + if (result.kmxSize != sz2) return __LINE__; // exit code: size of kmx-file in build differs from size of kmx-file in source folder - char* buf1 = new char[sz1]; - char* buf2 = new char[sz1]; - fread(buf1, 1, sz1, fp1); - fread(buf2, 1, sz1, fp2); - return memcmp(buf1, buf2, sz1) ? __LINE__ : 0; // exit code: when contents of kmx-file in build differs from contents of kmx-file in source folder + char* buf2 = new char[result.kmxSize]; + fread(buf2, 1, result.kmxSize, fp2); + return memcmp(result.kmx, buf2, result.kmxSize) ? __LINE__ : 0; // exit code: when contents of kmx-file in build differs from contents of kmx-file in source folder // success: when contents of kmx-file in build and source folder are the same } else { /*if Errors found: check number (e.g. CERR_4061_balochi_phonetic.kmn should produce Error 4061)*/ diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas index 703a1e4a0b..2b6dea94ec 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas @@ -115,7 +115,7 @@ begin TProject.CompilerMessageFile := Self; options.dwSize := sizeof(COMPILER_OPTIONS); options.ShouldAddCompilerVersion := addVersion; - options.UseKmcmpLib := not useLegacyCompiler; + // TODO: useLegacyCompiler means we switch to kmcmpdll vs kmc if not SetCompilerOptions(@options, ProjectCompilerMessage) then begin Log(plsFatal, 'Unable to set compiler options', CERR_FATAL, 0); -- GitLab From 194d46693fc0689d48f536507550c86df42a8252 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 08:57:45 +0700 Subject: [PATCH 284/386] chore(web): extra build-script cleanup --- web/build.sh | 3 --- web/src/app/browser/build.sh | 10 ++------ web/src/app/ui/build.sh | 6 ++--- web/src/app/webview/build.sh | 10 ++------ web/src/engine/device-detect/build.sh | 10 ++------ web/src/engine/element-wrappers/build.sh | 10 ++------ web/src/engine/events/build.sh | 10 ++------ web/src/engine/osk/build.sh | 10 ++------ web/src/engine/package-cache/build.sh | 10 ++------ web/src/engine/paths/build.sh | 10 ++------ web/src/tools/testing/recorder/build.sh | 31 ++++-------------------- 11 files changed, 23 insertions(+), 97 deletions(-) diff --git a/web/build.sh b/web/build.sh index 4f607f35ed..7076289419 100755 --- a/web/build.sh +++ b/web/build.sh @@ -1,9 +1,6 @@ #!/usr/bin/env bash # # Compile keymanweb and copy compiled javascript and resources to output/embedded folder -# - -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index d91211f28e..7c88852926 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=app/browser +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=app/browser -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's website-integrating version for use in non-puppeted browsers." \ diff --git a/web/src/app/ui/build.sh b/web/src/app/ui/build.sh index e15ca2d54c..c67317ee21 100755 --- a/web/src/app/ui/build.sh +++ b/web/src/app/ui/build.sh @@ -6,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=app/ui +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=app/ui -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's desktop form-factor keyboard selection modules." \ diff --git a/web/src/app/webview/build.sh b/web/src/app/webview/build.sh index ad09f4c9d9..c276d6b593 100755 --- a/web/src/app/webview/build.sh +++ b/web/src/app/webview/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=app/webview +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=app/webview -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's puppetable version designed for use within WebViews." \ diff --git a/web/src/engine/device-detect/build.sh b/web/src/engine/device-detect/build.sh index 3075384157..1ed3e1fd9b 100755 --- a/web/src/engine/device-detect/build.sh +++ b/web/src/engine/device-detect/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=engine/device-detect +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/device-detect -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the device-detection component of Keyman Engine for Web (KMW)." \ diff --git a/web/src/engine/element-wrappers/build.sh b/web/src/engine/element-wrappers/build.sh index 1366689a07..165c45cf4a 100755 --- a/web/src/engine/element-wrappers/build.sh +++ b/web/src/engine/element-wrappers/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=engine/element-wrappers +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/element-wrappers -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds DOM-based OutputTarget subclasses used by the Keyman Engine for Web (KMW)." \ diff --git a/web/src/engine/events/build.sh b/web/src/engine/events/build.sh index 2b4e1adc3a..c93c535831 100755 --- a/web/src/engine/events/build.sh +++ b/web/src/engine/events/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=engine/events +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/events -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds specialized event-related modules utilized by Keyman Engine for Web." \ diff --git a/web/src/engine/osk/build.sh b/web/src/engine/osk/build.sh index cafade03d7..542b1f21bc 100755 --- a/web/src/engine/osk/build.sh +++ b/web/src/engine/osk/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=engine/osk +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/osk -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's On-Screen Keyboard package (OSK)." \ diff --git a/web/src/engine/package-cache/build.sh b/web/src/engine/package-cache/build.sh index cdc958e050..2f54efd2f8 100755 --- a/web/src/engine/package-cache/build.sh +++ b/web/src/engine/package-cache/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=engine/package-cache +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/package-cache -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds Keyman Engine modules for keyboard cloud-querying & caching + model caching." \ diff --git a/web/src/engine/paths/build.sh b/web/src/engine/paths/build.sh index 924b4d19a9..9904958767 100755 --- a/web/src/engine/paths/build.sh +++ b/web/src/engine/paths/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=engine/paths +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/paths -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds configuration subclasses used by the Keyman Engine for Web (KMW)." \ diff --git a/web/src/tools/testing/recorder/build.sh b/web/src/tools/testing/recorder/build.sh index 3f627345a8..6b5cb18342 100755 --- a/web/src/tools/testing/recorder/build.sh +++ b/web/src/tools/testing/recorder/build.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash # # Compile KeymanWeb's dev & test tool modules -# -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,14 +8,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=tools/testing/recorder +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -SUBPROJECT_NAME=tools/testing/recorder -. "$KEYMAN_ROOT/web/common.inc.sh" - ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's test-sequence recording tool" \ @@ -34,24 +31,6 @@ builder_describe_outputs \ builder_parse "$@" -### CONFIGURE ACTIONS - -if builder_start_action configure; then - verify_npm_setup - builder_finish_action success configure -fi - -### CLEAN ACTIONS - -if builder_start_action clean; then - rm -rf ../../../../build/$SUBPROJECT_NAME/ - builder_finish_action success clean -fi - -### BUILD ACTIONS - -if builder_start_action build; then - compile $SUBPROJECT_NAME - - builder_finish_action success build -fi \ No newline at end of file +builder_run_action configure verify_npm_setup +builder_run_action clean rm -rf ../../../../build/$SUBPROJECT_NAME/ +builder_run_action build compile $SUBPROJECT_NAME \ No newline at end of file -- GitLab From bd22eb7b4e090a082bcd20ac9071ef8b7e8f6778 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 09:04:11 +0700 Subject: [PATCH 285/386] chore(web): Revert changes spun off into #8831 This reverts commit a4881bc4172e9e76a068a4cf8eda5749c0858c74. --- resources/build/test/test.sh | 21 ++------------------- resources/builder.inc.sh | 7 ++----- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/resources/build/test/test.sh b/resources/build/test/test.sh index c6623e45ae..4cb78ab2c2 100755 --- a/resources/build/test/test.sh +++ b/resources/build/test/test.sh @@ -18,7 +18,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe - clean build builder_parse "build" if [[ "${_builder_chosen_action_targets[@]}" != "build:project" ]]; then - builder_die " Test: builder_parse, shorthand form 'build' should give us 'build:project'" + builder_die " Test: builder_parse, shorthand form 'build' should give us 'build:project" fi if builder_start_action build; then @@ -44,7 +44,7 @@ builder_describe_parse_short_test() { fi } -builder_describe_parse_short_test "clean build test" ":module :tools :app" "build:module build:tools build:app build:project" "build" +builder_describe_parse_short_test "clean build test" ":module :tools :app" "build:module build:tools build:app" "build" builder_describe_parse_short_test "clean build test" ":module :tools :app" "build:app" "build:app" builder_describe_parse_short_test "clean build test" ":module :tools :app" "build:app clean:module" "build:app clean:module" builder_describe_parse_short_test "clean build test" ":module :tools :app :project" "clean:module clean:tools clean:app clean:project build:app build:project" "clean build:app build:project" @@ -113,23 +113,6 @@ builder_describe \ "--zoom,-z Use zoom mode" \ "--feature=FOO Enable feature foo" -#---------------------------------------------------------------------- -# Test implicit :project targets - -builder_parse_test "clean:app test:engine" "" clean:app test:engine - -if builder_has_action test:project; then - builder_die "FAIL: test:project should not have matched project-wide test action" -fi - -builder_parse_test "clean:app test:app test:engine test:project" "" clean:app test - -if builder_has_action test:project; then - echo "PASS: test:project matched" -else - builder_die: "FAIL: test:project missing for untargeted test action" -fi - #---------------------------------------------------------------------- # Test --options diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index a63d75c681..8713a54531 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -1302,11 +1302,8 @@ _builder_parse_expanded_parameters() { for e in "${_builder_targets[@]}"; do _builder_chosen_action_targets+=("$action$e") done - - if ! _builder_item_in_array ":project" "${_builder_targets[@]}"; then - # Also include an action-target pair indicating that the non-targeted action was specified. - _builder_chosen_action_targets+=("$action:project") - fi + # Also include an action-target pair indicating that the non-targeted action was specified. + _builder_chosen_action_targets+=("$action:project") elif (( has_target )); then # apply the default action to the selected target -- GitLab From 803ebda56e814844a3cb1a8b2907ed388112f690 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 09:06:40 +0700 Subject: [PATCH 286/386] chore(web): reverts the remaining part (from diff commit) --- resources/builder.inc.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 8713a54531..6a47cbd529 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -1302,8 +1302,6 @@ _builder_parse_expanded_parameters() { for e in "${_builder_targets[@]}"; do _builder_chosen_action_targets+=("$action$e") done - # Also include an action-target pair indicating that the non-targeted action was specified. - _builder_chosen_action_targets+=("$action:project") elif (( has_target )); then # apply the default action to the selected target -- GitLab From 2e52b1c8369e3e4c466679ecf5a7ef6b35f5a3e1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 09:09:18 +0700 Subject: [PATCH 287/386] chore(web): post-reversion web/build.sh patchup --- web/build.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/build.sh b/web/build.sh index 7076289419..e24be75991 100755 --- a/web/build.sh +++ b/web/build.sh @@ -64,8 +64,9 @@ builder_run_child_actions clean ## Clean actions -# If a full-on general clean was requested, we can nuke the entire build folder. -builder_run_action clean:project rm -rf ./build +###--- Future tie-in: if #8831 gets accepted, uncomment the next two lines. ---### +# # If a full-on general clean was requested, we can nuke the entire build folder. +# builder_run_action clean:project rm -rf ./build builder_run_child_actions configure @@ -107,14 +108,15 @@ if builder_has_action build:app/browser; then builder_warn "Modularization work is not yet complete; consumers may find needed API or components to be missing" fi -if builder_start_action test:project; then +###--- If #8831 gets accepted, change to `test:project` rather than just `test`. ---### +if builder_start_action test; then TEST_OPTS= if builder_has_option --ci; then TEST_OPTS=--ci fi ./test.sh $TEST_OPTS - builder_finish_action success test:project + builder_finish_action success test fi if builder_has_action build:app/ui; then -- GitLab From 37e31530cd3d3504edf7a9359ca539fc7737daeb Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 09:13:31 +0700 Subject: [PATCH 288/386] chore(web): removes post chain-merge artifact --- web/build.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/build.sh b/web/build.sh index e24be75991..6c030f98ac 100755 --- a/web/build.sh +++ b/web/build.sh @@ -118,7 +118,3 @@ if builder_start_action test; then builder_finish_action success test fi - -if builder_has_action build:app/ui; then - builder_die "Modularization work is not yet complete; builds dependent on this will fail." -fi -- GitLab From 05fe88bb3ada8437cc2e30d2bcb515c24fbadc93 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 May 2023 10:57:57 +0700 Subject: [PATCH 289/386] chore(developer): remove usekmcmplib flag from kmcomp --- developer/src/kmcomp/main.pas | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/developer/src/kmcomp/main.pas b/developer/src/kmcomp/main.pas index 5a29927a80..4aa29cf6c1 100644 --- a/developer/src/kmcomp/main.pas +++ b/developer/src/kmcomp/main.pas @@ -65,7 +65,7 @@ uses UKeymanTargets; function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; forward; // I4706 -function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion, FUseKmcmpLib: Boolean): Boolean; forward; +function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: Boolean): Boolean; forward; //function CompilerMessage(line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; forward; procedure FixupPathSlashes(var path: string); forward; @@ -90,7 +90,6 @@ var FParamDistribution: Boolean; FMergingValidateIds: Boolean; FShouldAddCompilerVersion: Boolean; - FUseKmcmpLib: Boolean; FJsonSchemaPath: string; FParamSourcePath: string; FParamHelpLink: string; @@ -117,7 +116,6 @@ begin FColorMode := cmDefault; FShouldAddCompilerVersion := True; - FUseKmcmpLib := True; FParamInfile := ''; FParamOutfile := ''; @@ -193,8 +191,6 @@ begin FColorMode := cmForceNoColor else if s = '-no-compiler-version' then FShouldAddCompilerVersion := False - else if s = '-use-legacy-compiler' then - FUseKmcmpLib := False else if (s = '-help') or (s = '-h') then begin // Force help @@ -223,8 +219,6 @@ begin writeln(SKeymanDeveloperName + ' Compiler (32-bit)'); {$ENDIF} writeln('Version ' + CKeymanVersionInfo.VersionWithTag + ', ' + GetVersionCopyright); - if not FUseKmcmpLib then - writeln('Note: using legacy compiler'); end; if FError or (FParamInfile = '') then @@ -234,7 +228,7 @@ begin writeln(''); writeln('Usage: '+cmd+' [-s[s]] [-nologo] [-c] [-d] [-w] [-cfc] [-v[s|d]] [-source-path path] [-schema-path path] '); writeln(' '+spc+' [-m] infile [-m infile] [-t target] [outfile.kmx|outfile.js [error.log]]'); // I4699 - writeln(' '+spc+' [-add-help-link path] [-color|-no-color] [-no-compiler-version] [-use-legacy-compiler]'); + writeln(' '+spc+' [-add-help-link path] [-color|-no-color] [-no-compiler-version]'); writeln(' '+spc+' [-extract-keyboard-info field[,field...]]'); writeln(' infile can be a .kmn file (Keyboard Source, .kps file (Package Source), or .kpj (project)'); // I4699 // I4825 writeln(' if -v specified, can also be a .keyboard_info file'); @@ -258,7 +252,6 @@ begin writeln(' uses console mode to determine whether color should be used.'); writeln; writeln(' -no-compiler-version Don''t embed the compiler version stores, useful for regression tests.'); - writeln(' -use-legacy-compiler Use legacy compiler (will be removed in 18.0)'); writeln; writeln(' JSON .keyboard_info compile targets:'); writeln(' -v[s] validate infile against source schema'); @@ -298,7 +291,7 @@ begin TProjectLogConsole.Create(FSilent, FFullySilent, hOutfile, FColorMode); - KCSetCompilerOptions(FParamInfile, FShouldAddCompilerVersion, FUseKmcmpLib); + KCSetCompilerOptions(FParamInfile, FShouldAddCompilerVersion); if FValidateRepoChanges then FError := not TValidateRepoChanges.Execute(FParamInfile, FParamOutfile) @@ -324,7 +317,7 @@ begin ExitCode := 1; end; -function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion, FUseKmcmpLib: Boolean): Boolean; +function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: Boolean): Boolean; var opt: TCompilerOptions; begin @@ -332,7 +325,6 @@ begin opt.dwSize := sizeof(TCompilerOptions); opt.ShouldAddCompilerVersion := FShouldAddCompilerVersion; - opt.UseKmcmpLib := FUseKmcmpLib; Result := SetCompilerOptions(@opt, @CompilerMessageW); -- GitLab From 10868dce97eb5dfb7a814fd065560a043882120d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 11:37:08 +0700 Subject: [PATCH 290/386] chore(web): prepares a build/publish folder with all artifacts neatly arranged for distribution --- web/common.inc.sh | 29 ++++++++++++++++++++++++++++- web/src/app/browser/build.sh | 3 +++ web/src/app/ui/build.sh | 3 +++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/web/common.inc.sh b/web/common.inc.sh index efd6b16d6a..6e5ad3c3f0 100644 --- a/web/common.inc.sh +++ b/web/common.inc.sh @@ -23,7 +23,6 @@ compile ( ) { tsc -b "${KEYMAN_ROOT}/web/src/$COMPILE_TARGET" -v - # COMPILE_TARGET entries are all prefixed with `engine`, so remove that. if [ -f "./build-bundler.js" ]; then node "./build-bundler.js" @@ -32,6 +31,34 @@ compile ( ) { fi } +_copy_dir_if_exists ( ) { + local SRC=$1 + local DST=$2 + + if [ -d "$SRC" ]; then + cp -rf "$SRC/." "$DST" + fi +} + +# Copies top-level build artifacts into common 'debug' and 'release' config folders +# for use in publishing. +prepare ( ) { + local CHILD_BUILD_ROOT="$KEYMAN_ROOT/web/build/app" + local PUBLISH_BUILD_ROOT="$KEYMAN_ROOT/web/build/publish" + + mkdir -p "$PUBLISH_BUILD_ROOT/debug" + mkdir -p "$PUBLISH_BUILD_ROOT/release" + + _copy_dir_if_exists "$CHILD_BUILD_ROOT/browser/debug" "$PUBLISH_BUILD_ROOT/debug" + _copy_dir_if_exists "$CHILD_BUILD_ROOT/browser/release" "$PUBLISH_BUILD_ROOT/release" + + _copy_dir_if_exists "$CHILD_BUILD_ROOT/resources" "$PUBLISH_BUILD_ROOT/debug" + _copy_dir_if_exists "$CHILD_BUILD_ROOT/resources" "$PUBLISH_BUILD_ROOT/release" + + _copy_dir_if_exists "$CHILD_BUILD_ROOT/ui/debug" "$PUBLISH_BUILD_ROOT/debug" + _copy_dir_if_exists "$CHILD_BUILD_ROOT/ui/release" "$PUBLISH_BUILD_ROOT/release" +} + # Runs all headless tests corresponding to the specified target. # This should be called from the working directory of a child project's # build script. diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index 7c88852926..a578917dc9 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -45,6 +45,9 @@ compile_and_copy() { mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk" cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/" + + # Update the build/publish copy of our build artifacts + prepare } builder_run_action configure verify_npm_setup diff --git a/web/src/app/ui/build.sh b/web/src/app/ui/build.sh index c67317ee21..96ea21ae5d 100755 --- a/web/src/app/ui/build.sh +++ b/web/src/app/ui/build.sh @@ -44,6 +44,9 @@ compile_and_copy() { mkdir -p "$KEYMAN_ROOT/web/build/app/resources/ui" cp -R "$KEYMAN_ROOT/web/src/resources/ui/." "$KEYMAN_ROOT/web/build/app/resources/ui/" + + # Update the build/publish copy of our build artifacts + prepare } builder_run_action configure verify_npm_setup -- GitLab From 05ceab5a603a86678a281cdabff4750da238caad Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 11:37:45 +0700 Subject: [PATCH 291/386] change(web): updates ci step scripts to use prep steps from prior commit --- web/ci.sh | 21 +++++-------------- .../tools/building/sourcemap-root/build.sh | 4 +--- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index 38c231d8a7..b14e916fdf 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -118,9 +118,9 @@ if builder_start_action prepare:s.keyman.com; then echo "FOLDER: $BASE_PUBLISH_FOLDER" mkdir -p "$BASE_PUBLISH_FOLDER/resources" - cp -Rf build/app/browser/release/* "$BASE_PUBLISH_FOLDER" - cp -Rf build/app/resources/* "$BASE_PUBLISH_FOLDER/resources" - cp -Rf build/app/ui/release/* "$BASE_PUBLISH_FOLDER" + # s.keyman.com - release-config only. It's notably smaller, thus far more favorable + # for dynamic linking. + cp -Rf build/publish/release/* "$BASE_PUBLISH_FOLDER" # Third phase: tweak the sourcemaps # We can use an alt-mode of Web's sourcemap-root tool for this. @@ -164,20 +164,9 @@ if builder_start_action prepare:downloads.keyman.com; then fi fi - pushd build/app/browser/release + pushd build/publish + # Zip both the 'debug' and 'release' configurations together. "${COMPRESS_CMD}" $COMPRESS_ADD ../../../../$ZIP * - cd .. - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP debug - popd - - pushd build/app/resources - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP * - popd - - pushd build/app/ui/release - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../../$ZIP * - cd .. - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP debug popd # --- Second action artifact - the 'static' folder (hosted user testing on downloads.keyman.com) --- diff --git a/web/src/tools/building/sourcemap-root/build.sh b/web/src/tools/building/sourcemap-root/build.sh index ec2f5d8b09..34155a0883 100755 --- a/web/src/tools/building/sourcemap-root/build.sh +++ b/web/src/tools/building/sourcemap-root/build.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash # # Compile KeymanWeb's dev & test tool modules -# -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -25,7 +23,7 @@ builder_describe "Builds the sourcemap-sanitizing script used for Keyman Engine builder_describe_outputs \ configure /node_modules \ - build /web/build/tools/sourcemap-root/index.js + build /web/build/tools/building/sourcemap-root/index.js builder_parse "$@" -- GitLab From 7621de2608a5a9ec702e0dc7c7059d1038b969f3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 11:39:17 +0700 Subject: [PATCH 292/386] docs(web): better comment --- web/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/ci.sh b/web/ci.sh index b14e916fdf..e3b494f8db 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -119,7 +119,7 @@ if builder_start_action prepare:s.keyman.com; then mkdir -p "$BASE_PUBLISH_FOLDER/resources" # s.keyman.com - release-config only. It's notably smaller, thus far more favorable - # for dynamic linking. + # for distribution via cloud service. cp -Rf build/publish/release/* "$BASE_PUBLISH_FOLDER" # Third phase: tweak the sourcemaps -- GitLab From 0ca7e0f918e6c8d3a814a83cae28b9d370b3840f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 12:46:39 +0700 Subject: [PATCH 293/386] chore(web): missed adding one script's changes --- web/src/engine/main/build.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web/src/engine/main/build.sh b/web/src/engine/main/build.sh index 8c118851ec..0e4d0a3db3 100755 --- a/web/src/engine/main/build.sh +++ b/web/src/engine/main/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BA . "$(dirname "$THIS_SCRIPT")/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=engine/main +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=engine/main -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's common top-level base classes." \ -- GitLab From adfb4ab2313bcbd3eebd2e397a83c13044d2159f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 12:55:49 +0700 Subject: [PATCH 294/386] chore(web): general cleanup --- web/src/app/browser/src/keymanEngine.ts | 2 +- web/src/engine/namespaced-main/kmwbase.ts | 40 ------------------- .../web/build-visual-keyboard/index.html | 5 --- web/src/test/manual/web/inline-osk/index.html | 2 +- 4 files changed, 2 insertions(+), 47 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 3ab724d74c..d5ee66502b 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -535,7 +535,7 @@ export default class KeymanEngine extends KeymanEngineBase void; - - /** - * Create copy of the OSK that can be used for embedding in documentation or help - * The currently active keyboard will be returned if PInternalName is null - * - * @param {string} PInternalName internal name of keyboard, with or without Keyboard_ prefix - * @param {number} Pstatic static keyboard flag (unselectable elements) - * @param {string=} argFormFactor layout form factor, defaulting to 'desktop' - * @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default' - * @return {Object} DIV object with filled keyboard layer content - */ - ['BuildVisualKeyboard'](PInternalName, Pstatic, argFormFactor, argLayerId): HTMLElement { - let PKbd: com.keyman.keyboards.Keyboard = null; - - if(PInternalName != null) { - var p=PInternalName.toLowerCase().replace('keyboard_',''); - var keyboardsList = this.keyboardManager.keyboards; - - for(let Ln=0; Ln section. - // const stylesheet = keyman.osk._Box.lastChild; - // stylesheet.parentElement.removeChild(stylesheet); - shadowfy(container); } diff --git a/web/src/test/manual/web/inline-osk/index.html b/web/src/test/manual/web/inline-osk/index.html index 5b7e561edc..87c68ade8a 100644 --- a/web/src/test/manual/web/inline-osk/index.html +++ b/web/src/test/manual/web/inline-osk/index.html @@ -52,7 +52,7 @@ var kmw=window.keyman; kmw.init({ - attachType:'auto', + attachType:'auto', resources:'../../resources' }).then(function() { setOSK('windows'); -- GitLab From 1085cb8de7b303fe6f8d8e820eab199dc9e164ab Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 13:04:21 +0700 Subject: [PATCH 295/386] chore(web): post-merge patchup --- web/src/app/browser/src/utilApiEndpoint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/app/browser/src/utilApiEndpoint.ts b/web/src/app/browser/src/utilApiEndpoint.ts index 4cf7955bfb..8232214556 100644 --- a/web/src/app/browser/src/utilApiEndpoint.ts +++ b/web/src/app/browser/src/utilApiEndpoint.ts @@ -211,8 +211,8 @@ export class UtilApiEndpoint { getStyleValue = getStyleValue; private get alertHost(): AlertHost { - if(this.config.signalUser) { - return this.config.signalUser; + if(this.config.alertHost) { + return this.config.alertHost; } else if(!this._alertHost) { // Lazy init: if KMW is set to not show alerts, we try not to initialize the alert host. // If the .alert API is called, though, we have no choice. -- GitLab From f36a908d9435eb7fa19696298d04742e3d53dc6c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 May 2023 13:04:39 +0700 Subject: [PATCH 296/386] chore(developer): move keyboard repo fixtures Relocates the keyboard-repo fixtures so we can add other local fixtures for unit tests. --- developer/src/kmcmplib/checkout-keyboards.inc.sh | 4 ++-- .../fixtures/{ => keyboards-repo}/adiga_danef.kmx | Bin .../fixtures/{ => keyboards-repo}/ahom_star.kmx | Bin .../{ => keyboards-repo}/aksarabali_panlex.kmx | Bin .../fixtures/{ => keyboards-repo}/alephwithbeth.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/anii.kmx | Bin .../fixtures/{ => keyboards-repo}/arabic_izza.kmx | Bin .../{ => keyboards-repo}/aramaic_hebrew.kmx | Bin .../{ => keyboards-repo}/armenian_mnemonic.kmx | Bin .../{ => keyboards-repo}/armenian_mnemonic_r.kmx | Bin .../fixtures/{ => keyboards-repo}/athinkra_vai.kmx | Bin .../athinkra_vai_typewriter.kmx | Bin .../fixtures/{ => keyboards-repo}/ausephon.kmx | Bin .../{ => keyboards-repo}/balochi_inpage.kmx | Bin .../fixtures/{ => keyboards-repo}/balochi_latin.kmx | Bin .../{ => keyboards-repo}/balochi_persian.kmx | Bin .../{ => keyboards-repo}/balochi_phonetic.kmx | Bin .../{ => keyboards-repo}/balochi_scientific.kmx | Bin .../fixtures/{ => keyboards-repo}/balochi_urdu.kmx | Bin .../fixtures/{ => keyboards-repo}/bangla_joy.kmx | Bin .../fixtures/{ => keyboards-repo}/bangla_munir.kmx | Bin .../{ => keyboards-repo}/bangla_probhat.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbda1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbda2.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbda3.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdadlm.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdal.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdarme.kmx | Bin .../{ => keyboards-repo}/basic_kbdarmph.kmx | Bin .../{ => keyboards-repo}/basic_kbdarmty.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdarmw.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdaze.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdazel.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdazst.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbash.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbe.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbene.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbgph.kmx | Bin .../{ => keyboards-repo}/basic_kbdbgph1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbhc.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdblr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbu.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbug.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdbulg.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdca.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdcan.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdcher.kmx | Bin .../{ => keyboards-repo}/basic_kbdcherp.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdcr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdcz.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdcz1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdcz2.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdda.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbddiv1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbddiv2.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbddv.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbddzo.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdes.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdest.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdfa.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdfar.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdfi.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdfi1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdfo.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdfr.kmx | Bin .../{ => keyboards-repo}/basic_kbdfthrk.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdgae.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdgeo.kmx | Bin .../{ => keyboards-repo}/basic_kbdgeoer.kmx | Bin .../{ => keyboards-repo}/basic_kbdgeome.kmx | Bin .../{ => keyboards-repo}/basic_kbdgeooa.kmx | Bin .../{ => keyboards-repo}/basic_kbdgeoqw.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdgkl.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdgn.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdgr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdgr1.kmx | Bin .../{ => keyboards-repo}/basic_kbdgrlnd.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdgthc.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdhau.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdhaw.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdhe.kmx | Bin .../{ => keyboards-repo}/basic_kbdhe220.kmx | Bin .../{ => keyboards-repo}/basic_kbdhe319.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdheb.kmx | Bin .../{ => keyboards-repo}/basic_kbdhebl3.kmx | Bin .../{ => keyboards-repo}/basic_kbdhela2.kmx | Bin .../{ => keyboards-repo}/basic_kbdhela3.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdhept.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdhu.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdhu1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdibo.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdic.kmx | Bin .../{ => keyboards-repo}/basic_kbdinasa.kmx | Bin .../{ => keyboards-repo}/basic_kbdinbe2.kmx | Bin .../{ => keyboards-repo}/basic_kbdinben.kmx | Bin .../{ => keyboards-repo}/basic_kbdindev.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdinen.kmx | Bin .../{ => keyboards-repo}/basic_kbdinguj.kmx | Bin .../{ => keyboards-repo}/basic_kbdinhin.kmx | Bin .../{ => keyboards-repo}/basic_kbdinkan.kmx | Bin .../{ => keyboards-repo}/basic_kbdinmal.kmx | Bin .../{ => keyboards-repo}/basic_kbdinmar.kmx | Bin .../{ => keyboards-repo}/basic_kbdinori.kmx | Bin .../{ => keyboards-repo}/basic_kbdinpun.kmx | Bin .../{ => keyboards-repo}/basic_kbdintam.kmx | Bin .../{ => keyboards-repo}/basic_kbdintel.kmx | Bin .../{ => keyboards-repo}/basic_kbdinuk2.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdir.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdit.kmx | Bin .../{ => keyboards-repo}/basic_kbdit142.kmx | Bin .../{ => keyboards-repo}/basic_kbdiulat.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdjav.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdkaz.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdkhmr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdkni.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdkurd.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdkyr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdla.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdlao.kmx | Bin .../{ => keyboards-repo}/basic_kbdlisub.kmx | Bin .../{ => keyboards-repo}/basic_kbdlisus.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdlt.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdlt1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdlt2.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdlv.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdlv1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdlvst.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdmac.kmx | Bin .../{ => keyboards-repo}/basic_kbdmacst.kmx | Bin .../{ => keyboards-repo}/basic_kbdmaori.kmx | Bin .../{ => keyboards-repo}/basic_kbdmlt47.kmx | Bin .../{ => keyboards-repo}/basic_kbdmlt48.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdmon.kmx | Bin .../{ => keyboards-repo}/basic_kbdmonmo.kmx | Bin .../{ => keyboards-repo}/basic_kbdmonst.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdmyan.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdne.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdnepr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdnko.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdno.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdno1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdnso.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdntl.kmx | Bin .../{ => keyboards-repo}/basic_kbdogham.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdolch.kmx | Bin .../{ => keyboards-repo}/basic_kbdoldit.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdosa.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdosm.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdpash.kmx | Bin .../{ => keyboards-repo}/basic_kbdphags.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdpl.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdpl1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdpo.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdropr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdrost.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdru.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdru1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdrum.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsf.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsg.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsl.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsl1.kmx | Bin .../{ => keyboards-repo}/basic_kbdsmsfi.kmx | Bin .../{ => keyboards-repo}/basic_kbdsmsno.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsn1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsora.kmx | Bin .../{ => keyboards-repo}/basic_kbdsorex.kmx | Bin .../{ => keyboards-repo}/basic_kbdsors1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsp.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsw.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsw09.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsyr1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdsyr2.kmx | Bin .../{ => keyboards-repo}/basic_kbdtaile.kmx | Bin .../{ => keyboards-repo}/basic_kbdtajik.kmx | Bin .../{ => keyboards-repo}/basic_kbdtam99.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdth0.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdth1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdth2.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdth3.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdtifi.kmx | Bin .../{ => keyboards-repo}/basic_kbdtifi2.kmx | Bin .../{ => keyboards-repo}/basic_kbdtiprd.kmx | Bin .../{ => keyboards-repo}/basic_kbdtt102.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdtuf.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdtuq.kmx | Bin .../{ => keyboards-repo}/basic_kbdturme.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdtzm.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdughr.kmx | Bin .../{ => keyboards-repo}/basic_kbdughr1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbduk.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdukx.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdur.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdur1.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdurdu.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdus.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdusa.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdusl.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdusr.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdusx.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbduzb.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdvntc.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdwol.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdyak.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdyba.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdycc.kmx | Bin .../fixtures/{ => keyboards-repo}/basic_kbdycl.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/batak.kmx | Bin .../fixtures/{ => keyboards-repo}/baybayin.kmx | Bin .../{ => keyboards-repo}/bhaiksuki_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/bj_cree_east.kmx | Bin .../{ => keyboards-repo}/bj_cree_east_james_bay.kmx | Bin .../{ => keyboards-repo}/bj_cree_east_latn.kmx | Bin .../{ => keyboards-repo}/bj_cree_west_latn.kmx | Bin .../fixtures/{ => keyboards-repo}/bj_cree_woods.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/bj_innu.kmx | Bin .../{ => keyboards-repo}/bj_innu_phonemic.kmx | Bin .../{ => keyboards-repo}/bj_mista_wasaha_cree.kmx | Bin .../{ => keyboards-repo}/bj_naskapi_classic.kmx | Bin .../{ => keyboards-repo}/bj_naskapi_common.kmx | Bin .../fixtures/{ => keyboards-repo}/bj_oji_cree.kmx | Bin .../{ => keyboards-repo}/brahmi_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/btl_kenya.kmx | Bin .../fixtures/{ => keyboards-repo}/bu_phonetic.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/buhid.kmx | Bin .../{ => keyboards-repo}/burushaski_girminas.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/cabecar.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/carian.kmx | Bin .../fixtures/{ => keyboards-repo}/chalchiteko.kmx | Bin .../fixtures/{ => keyboards-repo}/chechen_latin.kmx | Bin .../fixtures/{ => keyboards-repo}/chinuk_wawa.kmx | Bin .../{ => keyboards-repo}/choctaw_modern.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/cim.kmx | Bin .../fixtures/{ => keyboards-repo}/clavbur9.kmx | Bin .../fixtures/{ => keyboards-repo}/colchis_latin.kmx | Bin .../{ => keyboards-repo}/colchis_phonetic.kmx | Bin .../{ => keyboards-repo}/common_devanagari.kmx | Bin .../fixtures/{ => keyboards-repo}/coptic_greek.kmx | Bin .../fixtures/{ => keyboards-repo}/coptic_qwerty.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/dagbani.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/dega.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/dene.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/deseret.kmx | Bin .../{ => keyboards-repo}/dogra_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/easy_chakma.kmx | Bin .../fixtures/{ => keyboards-repo}/ekwtamil99uni.kmx | Bin .../fixtures/{ => keyboards-repo}/el_dinka.kmx | Bin .../{ => keyboards-repo}/el_harari_latin.kmx | Bin .../fixtures/{ => keyboards-repo}/el_naija.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/el_nuer.kmx | Bin .../fixtures/{ => keyboards-repo}/el_pasifika.kmx | Bin .../fixtures/{ => keyboards-repo}/el_yolngu.kmx | Bin .../fixtures/{ => keyboards-repo}/embera_north.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/enga.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/enggano.kmx | Bin .../{ => keyboards-repo}/english_shavian_igc.kmx | Bin .../{ => keyboards-repo}/english_shavian_qwerty.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/engram.kmx | Bin .../fixtures/{ => keyboards-repo}/esperuni.kmx | Bin .../fixtures/{ => keyboards-repo}/farsiman.kmx | Bin .../fixtures/{ => keyboards-repo}/finongan.kmx | Bin .../{ => keyboards-repo}/fulfulde_ajami_qwerty.kmx | Bin .../{ => keyboards-repo}/fulfulde_latin_qwerty.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_dakelh.kmx | Bin .../{ => keyboards-repo}/fv_dane_zaa_zaage.kmx | Bin .../{ => keyboards-repo}/fv_denesuline_epsilon.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_diitiidatx.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_gitsenimx.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_gwichin.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_hailzaqvla.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_haisla.kmx | Bin .../{ => keyboards-repo}/fv_halqemeylem.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/fv_han.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_henqeminem.kmx | Bin .../fv_hlgaagilda_xaayda_kil.kmx | Bin .../{ => keyboards-repo}/fv_kanienkeha_e.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_ktunaxa.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_kwakwala.kmx | Bin .../{ => keyboards-repo}/fv_kwakwala_liqwala.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_migmaq.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_natwits.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_nisgaa.kmx | Bin .../{ => keyboards-repo}/fv_nlekepmxcin.kmx | Bin .../{ => keyboards-repo}/fv_northern_tutchone.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_nsilxcen.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_nuucaanul.kmx | Bin .../{ => keyboards-repo}/fv_secwepemctsin.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_sencoten.kmx | Bin .../{ => keyboards-repo}/fv_shashishalhem.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_smalgyax.kmx | Bin .../{ => keyboards-repo}/fv_southern_tutchone.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_statimcets.kmx | Bin .../{ => keyboards-repo}/fv_stlatlimxec.kmx | Bin .../{ => keyboards-repo}/fv_tagizi_dene.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_taltan.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_tlingit.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_tsekehne.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_tsilhqotin.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_uwikala.kmx | Bin .../fixtures/{ => keyboards-repo}/fv_xaislakala.kmx | Bin .../{ => keyboards-repo}/galaxie_greek_mnemonic.kmx | Bin .../galaxie_greek_positional.kmx | Bin .../galaxie_hebrew_mnemonic.kmx | Bin .../galaxie_hebrew_positional.kmx | Bin .../fixtures/{ => keyboards-repo}/gandhari.kmx | Bin .../fixtures/{ => keyboards-repo}/geezbrhan.kmx | Bin .../fixtures/{ => keyboards-repo}/gff_amh_7.kmx | Bin .../fixtures/{ => keyboards-repo}/gff_amharic.kmx | Bin .../fixtures/{ => keyboards-repo}/gff_blin.kmx | Bin .../{ => keyboards-repo}/gff_ethiopic_7.kmx | Bin .../fixtures/{ => keyboards-repo}/gff_geez.kmx | Bin .../fixtures/{ => keyboards-repo}/gff_gurage.kmx | Bin .../{ => keyboards-repo}/gff_gurage_legacy.kmx | Bin .../fixtures/{ => keyboards-repo}/gff_musnad.kmx | Bin .../{ => keyboards-repo}/gff_tigrinya_eritrea.kmx | Bin .../{ => keyboards-repo}/gff_tigrinya_ethiopia.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/ghana.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/gilaki.kmx | Bin .../{ => keyboards-repo}/gilaki_phonetic.kmx | Bin .../fixtures/{ => keyboards-repo}/gondi_dev.kmx | Bin .../fixtures/{ => keyboards-repo}/gondi_gunjala.kmx | Bin .../fixtures/{ => keyboards-repo}/gondi_tel.kmx | Bin .../{ => keyboards-repo}/greekclassical.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/hanunoo.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/haroi.kmx | Bin .../{ => keyboards-repo}/hatran_inscript.kmx | Bin .../{ => keyboards-repo}/hausa_ajami_qwerty.kmx | Bin .../fixtures/{ => keyboards-repo}/hausa_kano.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/hcesar.kmx | Bin .../fixtures/{ => keyboards-repo}/hieroglyphic.kmx | Bin .../{ => keyboards-repo}/himyarit_musnad.kmx | Bin .../fixtures/{ => keyboards-repo}/hindi_modular.kmx | Bin .../fixtures/{ => keyboards-repo}/indigenous_nt.kmx | Bin .../fixtures/{ => keyboards-repo}/indonesia.kmx | Bin .../{ => keyboards-repo}/indonesian_suku.kmx | Bin .../{ => keyboards-repo}/inuktitut_naqittaut.kmx | Bin .../{ => keyboards-repo}/ishkashimi_cyrillic.kmx | Bin .../{ => keyboards-repo}/itrans_bengali.kmx | Bin .../itrans_devanagari_hindi.kmx | Bin .../itrans_devanagari_sanskrit_vedic.kmx | Bin .../{ => keyboards-repo}/itrans_gujarati.kmx | Bin .../{ => keyboards-repo}/itrans_gurmukhi.kmx | Bin .../fixtures/{ => keyboards-repo}/itrans_odia.kmx | Bin .../fixtures/{ => keyboards-repo}/itrans_roman.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/jawa.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/jorai.kmx | Bin .../{ => keyboards-repo}/karakalpak_cyrillic.kmx | Bin .../{ => keyboards-repo}/karakalpak_latin.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/kayan.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/kbdsn1.kmx | Bin .../{ => keyboards-repo}/kharoshthi_inscript.kmx | Bin .../{ => keyboards-repo}/khmer_advanced.kmx | Bin .../fixtures/{ => keyboards-repo}/khmer_angkor.kmx | Bin .../{ => keyboards-repo}/khojki_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/kmhmu_2008.kmx | Bin .../fixtures/{ => keyboards-repo}/koalibrere.kmx | Bin .../fixtures/{ => keyboards-repo}/korean_rr.kmx | Bin .../fixtures/{ => keyboards-repo}/koreguaje.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/krung.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/lahu.kmx | Bin .../fixtures/{ => keyboards-repo}/lamkaang.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/landuma.kmx | Bin .../{ => keyboards-repo}/lao_2008_basic.kmx | Bin .../{ => keyboards-repo}/lao_2008_rapid.kmx | Bin .../fixtures/{ => keyboards-repo}/lao_pali.kmx | Bin .../fixtures/{ => keyboards-repo}/lao_pali_us.kmx | Bin .../fixtures/{ => keyboards-repo}/lao_phonetic.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/lazuri.kmx | Bin .../fixtures/{ => keyboards-repo}/libtralo.kmx | Bin .../{ => keyboards-repo}/makasar_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/malar_braille.kmx | Bin .../{ => keyboards-repo}/malar_malayalam.kmx | Bin .../malar_malayalam_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/malar_tirhuta.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/maltese.kmx | Bin .../{ => keyboards-repo}/mandaic_phonetic.kmx | Bin .../fixtures/{ => keyboards-repo}/masaram_gondi.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/me_en.kmx | Bin .../fixtures/{ => keyboards-repo}/meitei_legacy.kmx | Bin .../{ => keyboards-repo}/miluk_hanis_siuslaw.kmx | Bin .../fixtures/{ => keyboards-repo}/modi_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/mon_anonta.kmx | Bin .../fixtures/{ => keyboards-repo}/mon_phonetic.kmx | Bin .../mongolian_cyrillic_qwerty.kmx | Bin .../{ => keyboards-repo}/mozhi_malayalam.kmx | Bin .../fixtures/{ => keyboards-repo}/mro_phonetic.kmx | Bin .../{ => keyboards-repo}/multani_inscript.kmx | Bin .../{ => keyboards-repo}/multi_pak_phonetic.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/munji.kmx | Bin .../fixtures/{ => keyboards-repo}/myancode_san.kmx | Bin .../{ => keyboards-repo}/nabataean_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/naijatype.kmx | Bin .../fixtures/{ => keyboards-repo}/nailangs.kmx | Bin .../fixtures/{ => keyboards-repo}/nasa_yuwe.kmx | Bin .../{ => keyboards-repo}/nepali_traditional.kmx | Bin .../{ => keyboards-repo}/newa_romanized.kmx | Bin .../{ => keyboards-repo}/newa_traditional.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/nias.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/nisenan.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/nko.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/nkonya.kmx | Bin .../{ => keyboards-repo}/nlci_bengali_winscript.kmx | Bin .../nlci_devanagari_winscript.kmx | Bin .../nlci_gujarati_winscript.kmx | Bin .../nlci_gurmukhi_winscript.kmx | Bin .../fixtures/{ => keyboards-repo}/nlci_ipa.kmx | Bin .../{ => keyboards-repo}/nlci_kannada_winscript.kmx | Bin .../nlci_malayalam_winscript.kmx | Bin .../{ => keyboards-repo}/nlci_oriya_winscript.kmx | Bin .../{ => keyboards-repo}/nlci_tamil_winscript.kmx | Bin .../{ => keyboards-repo}/nlci_telugu_winscript.kmx | Bin .../fixtures/{ => keyboards-repo}/nrc_makah.kmx | Bin .../fixtures/{ => keyboards-repo}/ntl_onekey.kmx | Bin .../fixtures/{ => keyboards-repo}/numanggang.kmx | Bin .../{ => keyboards-repo}/nw_iranian_latin.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/o_tissi.kmx | Bin .../fixtures/{ => keyboards-repo}/obolo_chwerty.kmx | Bin .../fixtures/{ => keyboards-repo}/obolo_qwerty.kmx | Bin .../fixtures/{ => keyboards-repo}/old_hungarian.kmx | Bin .../old_turkic_udw21_qwerty.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/orma.kmx | Bin .../fixtures/{ => keyboards-repo}/osage_nation.kmx | Bin .../{ => keyboards-repo}/osage_nation_new.kmx | Bin .../{ => keyboards-repo}/otoe_missouria.kmx | Bin .../{ => keyboards-repo}/persian_phonetic.kmx | Bin .../fixtures/{ => keyboards-repo}/pid_piaroa.kmx | Bin .../fixtures/{ => keyboards-repo}/pingelap.kmx | Bin .../postmodern_english_uk_dualstroke.kmx | Bin .../postmodern_english_uk_natural.kmx | Bin .../postmodern_english_us_dualstroke.kmx | Bin .../postmodern_english_us_natural.kmx | Bin .../fixtures/{ => keyboards-repo}/pukapuka.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/qom.kmx | Bin .../fixtures/{ => keyboards-repo}/quinault.kmx | Bin .../fixtures/{ => keyboards-repo}/qwerty_farang.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/rac_aer.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_arabic.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_balti.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_brahui.kmx | Bin .../{ => keyboards-repo}/rac_brahui_latin.kmx | Bin .../{ => keyboards-repo}/rac_burushaski.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_dameli.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_dhatki.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_dogri.kmx | Bin .../{ => keyboards-repo}/rac_gawar_bati.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_gawri.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_hazaragi.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_hindko.kmx | Bin .../{ => keyboards-repo}/rac_indus_kohistani.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_kalasha.kmx | Bin .../{ => keyboards-repo}/rac_kashmir_shina.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_kashmiri.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_khowar.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_marwari.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_munji.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_oadki.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_ormuri.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_pahari.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_palula.kmx | Bin .../{ => keyboards-repo}/rac_parkari_koli.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_pashai.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_pashto.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_saraiki.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_shina.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_sindhi.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_torwali.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_urdu.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_ushojo.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_uyghur.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_wadiyara.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_wakhi.kmx | Bin .../{ => keyboards-repo}/rac_western_punjabi.kmx | Bin .../fixtures/{ => keyboards-repo}/rac_yidgha.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/rawang.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/rejang.kmx | Bin .../{ => keyboards-repo}/remington_gail.kmx | Bin .../fixtures/{ => keyboards-repo}/rohingya_arab.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/rossel.kmx | Bin .../fixtures/{ => keyboards-repo}/runeboard.kmx | Bin .../{ => keyboards-repo}/russian_mnemonic_r.kmx | Bin .../{ => keyboards-repo}/sabdalipi_assamese.kmx | Bin .../{ => keyboards-repo}/sahaptin_umatilla.kmx | Bin .../{ => keyboards-repo}/sahaptin_yakima.kmx | Bin .../{ => keyboards-repo}/sanjha_punjabi.kmx | Bin .../fixtures/{ => keyboards-repo}/santali_latin.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/saraiki.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/satere.kmx | Bin .../{ => keyboards-repo}/shahmukhi_phonetic.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/shan.kmx | Bin .../fixtures/{ => keyboards-repo}/shaw_2layer.kmx | Bin .../{ => keyboards-repo}/siddham_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_akha_act.kmx | Bin .../{ => keyboards-repo}/sil_arabic_phonetic.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_areare.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_bari.kmx | Bin .../{ => keyboards-repo}/sil_bengali_phonetic.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_bolivia.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_boonkit.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_brao.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/sil_bru.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_buang.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_bunong.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_busa.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_bwe_karen.kmx | Bin .../{ => keyboards-repo}/sil_cameroon_azerty.kmx | Bin .../{ => keyboards-repo}/sil_cameroon_qwerty.kmx | Bin .../{ => keyboards-repo}/sil_cherokee_nation.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_cheyenne.kmx | Bin .../{ => keyboards-repo}/sil_cipher_music.kmx | Bin .../sil_devanagari_phonetic.kmx | Bin .../sil_devanagari_romanized.kmx | Bin .../sil_devanagari_typewriter.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_dzongkha.kmx | Bin .../{ => keyboards-repo}/sil_eastern_congo.kmx | Bin .../{ => keyboards-repo}/sil_el_ethiopian_latin.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_ethiopic.kmx | Bin .../{ => keyboards-repo}/sil_ethiopic_power_g.kmx | Bin .../{ => keyboards-repo}/sil_euro_latin.kmx | Bin .../{ => keyboards-repo}/sil_extended_urdu_np.kmx | Bin .../{ => keyboards-repo}/sil_greek_polytonic.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_hawaiian.kmx | Bin .../{ => keyboards-repo}/sil_hebr_grek_trans.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_hebrew.kmx | Bin .../{ => keyboards-repo}/sil_hebrew_legacy.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_hmd_plrd.kmx | Bin .../{ => keyboards-repo}/sil_indic_roman.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/sil_ipa.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_jarai.kmx | Bin .../{ => keyboards-repo}/sil_kayah_kali.kmx | Bin .../{ => keyboards-repo}/sil_kayah_latn.kmx | Bin .../{ => keyboards-repo}/sil_kayah_mymr.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_khamti.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_khmer.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_khowar.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_kmhmu.kmx | Bin .../{ => keyboards-repo}/sil_korda_jamo.kmx | Bin .../{ => keyboards-repo}/sil_korda_latin.kmx | Bin .../{ => keyboards-repo}/sil_korean_morse.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_kvl_kayaw.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_lepcha.kmx | Bin .../{ => keyboards-repo}/sil_limbu_phonetic.kmx | Bin .../{ => keyboards-repo}/sil_limbu_typewriter.kmx | Bin .../{ => keyboards-repo}/sil_lisu_basic.kmx | Bin .../{ => keyboards-repo}/sil_lisu_standard.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_lpo_plrd.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_madi.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_makuri.kmx | Bin .../{ => keyboards-repo}/sil_mali_azerty.kmx | Bin .../{ => keyboards-repo}/sil_mali_qwerty.kmx | Bin .../{ => keyboards-repo}/sil_mali_qwertz.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_moore.kmx | Bin .../{ => keyboards-repo}/sil_myanmar_my3.kmx | Bin .../{ => keyboards-repo}/sil_myanmar_mywinext.kmx | Bin .../{ => keyboards-repo}/sil_nigeria_dot.kmx | Bin .../{ => keyboards-repo}/sil_nigeria_odd_vowels.kmx | Bin .../{ => keyboards-repo}/sil_nigeria_underline.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/sil_nko.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_nubian.kmx | Bin .../sil_pan_africa_mnemonic.kmx | Bin .../sil_pan_africa_positional.kmx | Bin .../{ => keyboards-repo}/sil_philippines.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_sahu.kmx | Bin .../{ => keyboards-repo}/sil_senegal_bsc_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_cou_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_dyo_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_gsl_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_krx_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_ndv_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_sav_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_snf_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_srr_azerty.kmx | Bin .../{ => keyboards-repo}/sil_senegal_wo_azerty.kmx | Bin .../{ => keyboards-repo}/sil_sgaw_karen.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_shan.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_tai_dam.kmx | Bin .../{ => keyboards-repo}/sil_tai_dam_lao.kmx | Bin .../{ => keyboards-repo}/sil_tai_dam_latin.kmx | Bin .../{ => keyboards-repo}/sil_tai_dam_typewriter.kmx | Bin .../{ => keyboards-repo}/sil_tawallammat.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_tchad.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_tepehuan.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_torwali.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_tunisian.kmx | Bin .../{ => keyboards-repo}/sil_uganda_tanzania.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/sil_vai.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_wayuu.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_ygp_plrd.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/sil_yi.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_yna_plrd.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_yoruba8.kmx | Bin .../{ => keyboards-repo}/sil_yoruba_bar.kmx | Bin .../{ => keyboards-repo}/sil_yoruba_dot.kmx | Bin .../{ => keyboards-repo}/sil_yupik_cyrillic.kmx | Bin .../{ => keyboards-repo}/sil_yupik_cyrillic_ru.kmx | Bin .../fixtures/{ => keyboards-repo}/sil_ywq_plrd.kmx | Bin .../fixtures/{ => keyboards-repo}/slc_saliba.kmx | Bin .../{ => keyboards-repo}/soqotri_arabic.kmx | Bin .../{ => keyboards-repo}/srr_ajami_qwerty.kmx | Bin .../fixtures/{ => keyboards-repo}/sundanese.kmx | Bin .../{ => keyboards-repo}/sundanese_latin.kmx | Bin .../{ => keyboards-repo}/swanalekha_malayalam.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/sxava.kmx | Bin .../fixtures/{ => keyboards-repo}/sxava_eo.kmx | Bin .../fixtures/{ => keyboards-repo}/sylheti_nagri.kmx | Bin .../fixtures/{ => keyboards-repo}/syriac_arabic.kmx | Bin .../{ => keyboards-repo}/syriac_phonetic.kmx | Bin .../{ => keyboards-repo}/tagbanwa_inscript.kmx | Bin .../fixtures/{ => keyboards-repo}/taigi_poj.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/tainua.kmx | Bin .../fixtures/{ => keyboards-repo}/tangsa_lakhum.kmx | Bin .../{ => keyboards-repo}/tawallammat_latin.kmx | Bin .../{ => keyboards-repo}/teggargrent_lat.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/tem_kdh.kmx | Bin .../{ => keyboards-repo}/thamizha_anjal_paangu.kmx | Bin .../{ => keyboards-repo}/thamizha_bamini.kmx | Bin .../thamizha_new_typewriter.kmx | Bin .../{ => keyboards-repo}/thamizha_tamil99_ext.kmx | Bin .../{ => keyboards-repo}/tibetan_direct_input.kmx | Bin .../fixtures/{ => keyboards-repo}/tibetan_ewts.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/tirhuta.kmx | Bin .../fixtures/{ => keyboards-repo}/tlahuica.kmx | Bin .../fixtures/{ => keyboards-repo}/tsakonian.kmx | Bin .../{ => keyboards-repo}/tuareg_tifinagh.kmx | Bin .../fixtures/{ => keyboards-repo}/turkmen_cyrl.kmx | Bin .../fixtures/{ => keyboards-repo}/txo_toto.kmx | Bin .../fixtures/{ => keyboards-repo}/udi_keyboard.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/ukwuani.kmx | Bin .../fixtures/{ => keyboards-repo}/uma_graphic.kmx | Bin .../fixtures/{ => keyboards-repo}/uma_phonetic.kmx | Bin .../fixtures/{ => keyboards-repo}/urdu_phonetic.kmx | Bin .../{ => keyboards-repo}/urdu_phonetic_crulp.kmx | Bin .../{ => keyboards-repo}/venetia_et_histria.kmx | Bin .../{ => keyboards-repo}/vm_tamil_modular.kmx | Bin .../{ => keyboards-repo}/vm_tamil_typewriter.kmx | Bin .../{ => keyboards-repo}/wakhi_anglicized.kmx | Bin .../{ => keyboards-repo}/wakhi_cyrillic.kmx | Bin .../{ => keyboards-repo}/wakhi_standard.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/wancho.kmx | Bin .../fixtures/{ => keyboards-repo}/warang_citi.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/wolofal.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/xinaliq.kmx | Bin .../{ => keyboards-repo}/yiddish_pasekh.kmx | Bin .../tests/fixtures/{ => keyboards-repo}/yidgha.kmx | Bin .../younger_futhark_short_twig.kmx | Bin developer/src/kmcmplib/tests/meson.build | 2 +- developer/src/kmcmplib/tests/prep.sh | 12 ++++++------ 647 files changed, 9 insertions(+), 9 deletions(-) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/adiga_danef.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/ahom_star.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/aksarabali_panlex.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/alephwithbeth.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/anii.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/arabic_izza.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/aramaic_hebrew.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/armenian_mnemonic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/armenian_mnemonic_r.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/athinkra_vai.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/athinkra_vai_typewriter.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/ausephon.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/balochi_inpage.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/balochi_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/balochi_persian.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/balochi_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/balochi_scientific.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/balochi_urdu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bangla_joy.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bangla_munir.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bangla_probhat.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbda1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbda2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbda3.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdadlm.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdal.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdarme.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdarmph.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdarmty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdarmw.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdaze.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdazel.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdazst.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbash.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbe.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbene.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbgph.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbgph1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbhc.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdblr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbug.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdbulg.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdca.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdcan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdcher.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdcherp.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdcr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdcz.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdcz1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdcz2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdda.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbddiv1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbddiv2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbddv.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbddzo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdes.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdest.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdfa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdfar.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdfi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdfi1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdfo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdfr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdfthrk.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgae.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgeo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgeoer.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgeome.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgeooa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgeoqw.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgkl.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgn.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgr1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgrlnd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdgthc.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhau.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhaw.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhe.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhe220.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhe319.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdheb.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhebl3.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhela2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhela3.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhept.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdhu1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdibo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinasa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinbe2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinben.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdindev.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinen.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinguj.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinhin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinkan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinmal.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinmar.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinori.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinpun.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdintam.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdintel.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdinuk2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdir.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdit.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdit142.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdiulat.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdjav.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdkaz.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdkhmr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdkni.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdkurd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdkyr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdla.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlao.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlisub.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlisus.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlt.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlt1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlt2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlv.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlv1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdlvst.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmac.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmacst.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmaori.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmlt47.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmlt48.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmon.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmonmo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmonst.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdmyan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdne.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdnepr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdnko.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdno.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdno1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdnso.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdntl.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdogham.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdolch.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdoldit.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdosa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdosm.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdpash.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdphags.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdpl.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdpl1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdpo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdropr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdrost.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdru.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdru1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdrum.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsf.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsg.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsl.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsl1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsmsfi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsmsno.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsn1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsora.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsorex.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsors1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsp.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsw.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsw09.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsyr1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdsyr2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtaile.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtajik.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtam99.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdth0.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdth1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdth2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdth3.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtifi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtifi2.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtiprd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtt102.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtuf.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtuq.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdturme.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdtzm.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdughr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdughr1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbduk.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdukx.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdur.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdur1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdurdu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdus.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdusa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdusl.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdusr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdusx.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbduzb.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdvntc.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdwol.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdyak.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdyba.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdycc.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/basic_kbdycl.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/batak.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/baybayin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bhaiksuki_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_cree_east.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_cree_east_james_bay.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_cree_east_latn.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_cree_west_latn.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_cree_woods.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_innu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_innu_phonemic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_mista_wasaha_cree.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_naskapi_classic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_naskapi_common.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bj_oji_cree.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/brahmi_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/btl_kenya.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/bu_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/buhid.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/burushaski_girminas.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/cabecar.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/carian.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/chalchiteko.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/chechen_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/chinuk_wawa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/choctaw_modern.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/cim.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/clavbur9.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/colchis_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/colchis_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/common_devanagari.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/coptic_greek.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/coptic_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/dagbani.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/dega.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/dene.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/deseret.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/dogra_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/easy_chakma.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/ekwtamil99uni.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/el_dinka.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/el_harari_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/el_naija.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/el_nuer.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/el_pasifika.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/el_yolngu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/embera_north.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/enga.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/enggano.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/english_shavian_igc.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/english_shavian_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/engram.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/esperuni.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/farsiman.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/finongan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fulfulde_ajami_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fulfulde_latin_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_dakelh.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_dane_zaa_zaage.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_denesuline_epsilon.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_diitiidatx.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_gitsenimx.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_gwichin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_hailzaqvla.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_haisla.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_halqemeylem.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_han.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_henqeminem.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_hlgaagilda_xaayda_kil.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_kanienkeha_e.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_ktunaxa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_kwakwala.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_kwakwala_liqwala.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_migmaq.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_natwits.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_nisgaa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_nlekepmxcin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_northern_tutchone.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_nsilxcen.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_nuucaanul.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_secwepemctsin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_sencoten.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_shashishalhem.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_smalgyax.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_southern_tutchone.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_statimcets.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_stlatlimxec.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_tagizi_dene.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_taltan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_tlingit.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_tsekehne.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_tsilhqotin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_uwikala.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/fv_xaislakala.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/galaxie_greek_mnemonic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/galaxie_greek_positional.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/galaxie_hebrew_mnemonic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/galaxie_hebrew_positional.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gandhari.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/geezbrhan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_amh_7.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_amharic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_blin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_ethiopic_7.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_geez.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_gurage.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_gurage_legacy.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_musnad.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_tigrinya_eritrea.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gff_tigrinya_ethiopia.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/ghana.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gilaki.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gilaki_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gondi_dev.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gondi_gunjala.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/gondi_tel.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/greekclassical.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/hanunoo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/haroi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/hatran_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/hausa_ajami_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/hausa_kano.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/hcesar.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/hieroglyphic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/himyarit_musnad.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/hindi_modular.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/indigenous_nt.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/indonesia.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/indonesian_suku.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/inuktitut_naqittaut.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/ishkashimi_cyrillic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/itrans_bengali.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/itrans_devanagari_hindi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/itrans_devanagari_sanskrit_vedic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/itrans_gujarati.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/itrans_gurmukhi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/itrans_odia.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/itrans_roman.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/jawa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/jorai.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/karakalpak_cyrillic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/karakalpak_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/kayan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/kbdsn1.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/kharoshthi_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/khmer_advanced.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/khmer_angkor.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/khojki_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/kmhmu_2008.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/koalibrere.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/korean_rr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/koreguaje.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/krung.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lahu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lamkaang.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/landuma.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lao_2008_basic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lao_2008_rapid.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lao_pali.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lao_pali_us.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lao_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/lazuri.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/libtralo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/makasar_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/malar_braille.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/malar_malayalam.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/malar_malayalam_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/malar_tirhuta.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/maltese.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/mandaic_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/masaram_gondi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/me_en.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/meitei_legacy.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/miluk_hanis_siuslaw.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/modi_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/mon_anonta.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/mon_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/mongolian_cyrillic_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/mozhi_malayalam.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/mro_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/multani_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/multi_pak_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/munji.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/myancode_san.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nabataean_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/naijatype.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nailangs.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nasa_yuwe.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nepali_traditional.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/newa_romanized.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/newa_traditional.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nias.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nisenan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nko.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nkonya.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_bengali_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_devanagari_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_gujarati_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_gurmukhi_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_ipa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_kannada_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_malayalam_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_oriya_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_tamil_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nlci_telugu_winscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nrc_makah.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/ntl_onekey.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/numanggang.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/nw_iranian_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/o_tissi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/obolo_chwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/obolo_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/old_hungarian.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/old_turkic_udw21_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/orma.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/osage_nation.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/osage_nation_new.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/otoe_missouria.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/persian_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/pid_piaroa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/pingelap.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/postmodern_english_uk_dualstroke.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/postmodern_english_uk_natural.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/postmodern_english_us_dualstroke.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/postmodern_english_us_natural.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/pukapuka.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/qom.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/quinault.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/qwerty_farang.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_aer.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_arabic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_balti.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_brahui.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_brahui_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_burushaski.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_dameli.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_dhatki.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_dogri.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_gawar_bati.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_gawri.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_hazaragi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_hindko.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_indus_kohistani.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_kalasha.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_kashmir_shina.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_kashmiri.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_khowar.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_marwari.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_munji.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_oadki.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_ormuri.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_pahari.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_palula.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_parkari_koli.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_pashai.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_pashto.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_saraiki.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_shina.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_sindhi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_torwali.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_urdu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_ushojo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_uyghur.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_wadiyara.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_wakhi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_western_punjabi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rac_yidgha.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rawang.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rejang.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/remington_gail.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rohingya_arab.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/rossel.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/runeboard.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/russian_mnemonic_r.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sabdalipi_assamese.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sahaptin_umatilla.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sahaptin_yakima.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sanjha_punjabi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/santali_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/saraiki.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/satere.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/shahmukhi_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/shan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/shaw_2layer.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/siddham_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_akha_act.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_arabic_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_areare.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_bari.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_bengali_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_bolivia.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_boonkit.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_brao.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_bru.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_buang.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_bunong.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_busa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_bwe_karen.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_cameroon_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_cameroon_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_cherokee_nation.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_cheyenne.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_cipher_music.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_devanagari_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_devanagari_romanized.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_devanagari_typewriter.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_dzongkha.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_eastern_congo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_el_ethiopian_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_ethiopic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_ethiopic_power_g.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_euro_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_extended_urdu_np.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_greek_polytonic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_hawaiian.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_hebr_grek_trans.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_hebrew.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_hebrew_legacy.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_hmd_plrd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_indic_roman.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_ipa.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_jarai.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_kayah_kali.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_kayah_latn.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_kayah_mymr.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_khamti.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_khmer.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_khowar.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_kmhmu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_korda_jamo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_korda_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_korean_morse.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_kvl_kayaw.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_lepcha.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_limbu_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_limbu_typewriter.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_lisu_basic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_lisu_standard.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_lpo_plrd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_madi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_makuri.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_mali_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_mali_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_mali_qwertz.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_moore.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_myanmar_my3.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_myanmar_mywinext.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_nigeria_dot.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_nigeria_odd_vowels.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_nigeria_underline.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_nko.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_nubian.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_pan_africa_mnemonic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_pan_africa_positional.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_philippines.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_sahu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_bsc_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_cou_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_dyo_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_gsl_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_krx_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_ndv_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_sav_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_snf_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_srr_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_senegal_wo_azerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_sgaw_karen.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_shan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tai_dam.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tai_dam_lao.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tai_dam_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tai_dam_typewriter.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tawallammat.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tchad.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tepehuan.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_torwali.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_tunisian.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_uganda_tanzania.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_vai.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_wayuu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_ygp_plrd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_yi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_yna_plrd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_yoruba8.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_yoruba_bar.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_yoruba_dot.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_yupik_cyrillic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_yupik_cyrillic_ru.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sil_ywq_plrd.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/slc_saliba.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/soqotri_arabic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/srr_ajami_qwerty.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sundanese.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sundanese_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/swanalekha_malayalam.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sxava.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sxava_eo.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/sylheti_nagri.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/syriac_arabic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/syriac_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tagbanwa_inscript.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/taigi_poj.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tainua.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tangsa_lakhum.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tawallammat_latin.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/teggargrent_lat.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tem_kdh.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/thamizha_anjal_paangu.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/thamizha_bamini.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/thamizha_new_typewriter.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/thamizha_tamil99_ext.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tibetan_direct_input.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tibetan_ewts.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tirhuta.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tlahuica.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tsakonian.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/tuareg_tifinagh.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/turkmen_cyrl.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/txo_toto.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/udi_keyboard.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/ukwuani.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/uma_graphic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/uma_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/urdu_phonetic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/urdu_phonetic_crulp.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/venetia_et_histria.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/vm_tamil_modular.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/vm_tamil_typewriter.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/wakhi_anglicized.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/wakhi_cyrillic.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/wakhi_standard.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/wancho.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/warang_citi.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/wolofal.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/xinaliq.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/yiddish_pasekh.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/yidgha.kmx (100%) rename developer/src/kmcmplib/tests/fixtures/{ => keyboards-repo}/younger_futhark_short_twig.kmx (100%) diff --git a/developer/src/kmcmplib/checkout-keyboards.inc.sh b/developer/src/kmcmplib/checkout-keyboards.inc.sh index defcb479ef..6c76d53caa 100644 --- a/developer/src/kmcmplib/checkout-keyboards.inc.sh +++ b/developer/src/kmcmplib/checkout-keyboards.inc.sh @@ -9,8 +9,8 @@ function locate_keyboards_repo() { builder_die "keyboards_commit_ref.txt does not exist, run prep.sh" fi - if [[ ! -d "$THIS_SCRIPT_PATH/tests/fixtures" ]]; then - builder_die "fixtures folder does not exist, run prep.sh" + if [[ ! -d "$THIS_SCRIPT_PATH/tests/fixtures/keyboards-repo" ]]; then + builder_die "fixtures/keyboards-repo folder does not exist, run prep.sh" fi if [[ -z "${KEYBOARDS_ROOT+x}" ]]; then diff --git a/developer/src/kmcmplib/tests/fixtures/adiga_danef.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/adiga_danef.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/adiga_danef.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/adiga_danef.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ahom_star.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ahom_star.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ahom_star.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ahom_star.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/aksarabali_panlex.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/aksarabali_panlex.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/aksarabali_panlex.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/aksarabali_panlex.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/alephwithbeth.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/alephwithbeth.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/alephwithbeth.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/alephwithbeth.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/anii.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/anii.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/anii.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/anii.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/arabic_izza.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/arabic_izza.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/arabic_izza.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/arabic_izza.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/aramaic_hebrew.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/aramaic_hebrew.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/aramaic_hebrew.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/aramaic_hebrew.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/armenian_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/armenian_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/armenian_mnemonic_r.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic_r.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/armenian_mnemonic_r.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic_r.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/athinkra_vai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/athinkra_vai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/athinkra_vai_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/athinkra_vai_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ausephon.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ausephon.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ausephon.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ausephon.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_inpage.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_inpage.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_inpage.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_inpage.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_persian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_persian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_persian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_persian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_scientific.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_scientific.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_scientific.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_scientific.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_urdu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_urdu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_urdu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_urdu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bangla_joy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_joy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bangla_joy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_joy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bangla_munir.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_munir.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bangla_munir.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_munir.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bangla_probhat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_probhat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bangla_probhat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_probhat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbda1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbda1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbda2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbda2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbda3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbda3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdadlm.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdadlm.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdadlm.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdadlm.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdal.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdal.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdal.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdal.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarme.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarme.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarme.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarme.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarmph.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmph.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarmph.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmph.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarmty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarmty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarmw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarmw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdaze.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdaze.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdaze.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdaze.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdazel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdazel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdazst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdazst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbash.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbash.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbash.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbash.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbe.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbe.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbe.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbe.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbene.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbene.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbene.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbene.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbgph.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbgph.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbgph1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbgph1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbhc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbhc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbhc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbhc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdblr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdblr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdblr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdblr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbug.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbug.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbug.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbug.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbulg.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbulg.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbulg.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbulg.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdca.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdca.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdca.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdca.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcher.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcher.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcher.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcher.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcherp.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcherp.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcherp.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcherp.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcz.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcz.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcz1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcz1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcz2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcz2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdda.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdda.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdda.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdda.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddiv1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddiv1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddiv2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddiv2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddv.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddv.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddv.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddv.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddzo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddzo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddzo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddzo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdes.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdes.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdes.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdes.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdest.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdest.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdest.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdest.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfi1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfi1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfthrk.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfthrk.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfthrk.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfthrk.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgae.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgae.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgae.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgae.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeoer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeoer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeome.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeome.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeome.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeome.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeooa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeooa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeooa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeooa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeoqw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoqw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeoqw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoqw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgkl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgkl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgkl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgkl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgr1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgr1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgrlnd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgrlnd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgrlnd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgrlnd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgthc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgthc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgthc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgthc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhau.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhau.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhau.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhau.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhaw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhaw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhaw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhaw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhe.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhe.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhe220.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe220.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhe220.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe220.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhe319.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe319.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhe319.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe319.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdheb.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdheb.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdheb.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdheb.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhebl3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhebl3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhebl3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhebl3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhela2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhela2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhela3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhela3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhept.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhept.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhept.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhept.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhu1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhu1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdibo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdibo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdibo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdibo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinasa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinasa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinasa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinasa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinbe2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinbe2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinbe2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinbe2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinben.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinben.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinben.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinben.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdindev.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdindev.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdindev.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdindev.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinguj.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinguj.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinguj.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinguj.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinhin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinhin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinhin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinhin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinkan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinkan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinkan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinkan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinmal.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmal.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinmal.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmal.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinmar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinmar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinori.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinori.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinori.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinori.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinpun.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinpun.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinpun.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinpun.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdintam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdintam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdintel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdintel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinuk2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinuk2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinuk2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinuk2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdir.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdir.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdir.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdir.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdit142.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit142.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdit142.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit142.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdiulat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdiulat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdiulat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdiulat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdjav.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdjav.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdjav.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdjav.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkaz.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkaz.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkaz.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkaz.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkhmr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkhmr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkhmr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkhmr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkni.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkni.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkni.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkni.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkurd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkurd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkurd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkurd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkyr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkyr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkyr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkyr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlao.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlao.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlao.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlao.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlisub.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisub.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlisub.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisub.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlisus.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisus.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlisus.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisus.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlt.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlt.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlt1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlt1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlt2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlt2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlv.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlv.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlv1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlv1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlvst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlvst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlvst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlvst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmac.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmac.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmac.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmac.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmacst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmacst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmacst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmacst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmaori.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmaori.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmaori.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmaori.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmlt47.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt47.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmlt47.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt47.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmlt48.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt48.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmlt48.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt48.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmon.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmon.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmon.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmon.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmonmo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonmo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmonmo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonmo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmonst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmonst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmyan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmyan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmyan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmyan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdne.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdne.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdne.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdne.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdnepr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnepr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdnepr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnepr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdnko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdnko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdno.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdno.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdno1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdno1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdnso.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnso.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdnso.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnso.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdntl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdntl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdntl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdntl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdogham.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdogham.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdogham.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdogham.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdolch.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdolch.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdolch.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdolch.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdoldit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdoldit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdoldit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdoldit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdosa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdosa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdosm.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosm.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdosm.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosm.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpash.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpash.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpash.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpash.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdphags.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdphags.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdphags.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdphags.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpl1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpl1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdropr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdropr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdropr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdropr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdrost.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrost.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdrost.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrost.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdru.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdru.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdru1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdru1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdrum.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrum.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdrum.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrum.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsf.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsf.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsf.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsf.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsg.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsg.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsg.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsg.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsl1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsl1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsmsfi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsfi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsmsfi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsfi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsmsno.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsno.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsmsno.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsno.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsn1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsn1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsn1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsn1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsora.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsora.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsora.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsora.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsorex.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsorex.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsorex.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsorex.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsors1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsors1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsors1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsors1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsp.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsp.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsp.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsp.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsw09.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw09.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsw09.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw09.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsyr1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsyr1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsyr2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsyr2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtaile.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtaile.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtaile.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtaile.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtajik.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtajik.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtajik.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtajik.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtam99.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtam99.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtam99.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtam99.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth0.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth0.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth0.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth0.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtifi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtifi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtifi2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtifi2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtiprd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtiprd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtiprd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtiprd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtt102.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtt102.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtt102.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtt102.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtuf.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuf.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtuf.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuf.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtuq.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuq.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtuq.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuq.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdturme.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdturme.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdturme.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdturme.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtzm.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtzm.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtzm.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtzm.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdughr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdughr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdughr1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdughr1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbduk.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduk.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbduk.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduk.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdukx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdukx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdukx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdukx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdur.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdur.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdur1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdur1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdurdu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdurdu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdurdu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdurdu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdus.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdus.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdus.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdus.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbduzb.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduzb.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbduzb.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduzb.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdvntc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdvntc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdvntc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdvntc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdwol.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdwol.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdwol.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdwol.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdyak.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyak.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdyak.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyak.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdyba.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyba.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdyba.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyba.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdycc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdycc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdycl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdycl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/batak.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/batak.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/batak.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/batak.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/baybayin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/baybayin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/baybayin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/baybayin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bhaiksuki_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bhaiksuki_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bhaiksuki_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bhaiksuki_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_east.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_east.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_east_james_bay.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_james_bay.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_east_james_bay.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_james_bay.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_east_latn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_latn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_east_latn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_latn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_west_latn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_west_latn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_west_latn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_west_latn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_woods.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_woods.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_woods.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_woods.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_innu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_innu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_innu_phonemic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu_phonemic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_innu_phonemic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu_phonemic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_mista_wasaha_cree.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_mista_wasaha_cree.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_mista_wasaha_cree.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_mista_wasaha_cree.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_naskapi_classic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_classic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_naskapi_classic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_classic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_naskapi_common.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_common.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_naskapi_common.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_common.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_oji_cree.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_oji_cree.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_oji_cree.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_oji_cree.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/brahmi_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/brahmi_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/brahmi_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/brahmi_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/btl_kenya.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/btl_kenya.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/btl_kenya.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/btl_kenya.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bu_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bu_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bu_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bu_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/buhid.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/buhid.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/buhid.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/buhid.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/burushaski_girminas.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/burushaski_girminas.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/burushaski_girminas.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/burushaski_girminas.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/cabecar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/cabecar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/cabecar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/cabecar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/carian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/carian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/carian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/carian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/chalchiteko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/chalchiteko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/chalchiteko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/chalchiteko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/chechen_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/chechen_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/chechen_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/chechen_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/chinuk_wawa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/chinuk_wawa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/chinuk_wawa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/chinuk_wawa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/choctaw_modern.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/choctaw_modern.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/choctaw_modern.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/choctaw_modern.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/cim.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/cim.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/cim.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/cim.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/clavbur9.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/clavbur9.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/clavbur9.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/clavbur9.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/colchis_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/colchis_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/colchis_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/colchis_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/common_devanagari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/common_devanagari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/common_devanagari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/common_devanagari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/coptic_greek.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_greek.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/coptic_greek.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_greek.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/coptic_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/coptic_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dagbani.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dagbani.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dagbani.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dagbani.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dega.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dega.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dega.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dega.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dene.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dene.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dene.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dene.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/deseret.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/deseret.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/deseret.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/deseret.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dogra_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dogra_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dogra_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dogra_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/easy_chakma.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/easy_chakma.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/easy_chakma.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/easy_chakma.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ekwtamil99uni.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ekwtamil99uni.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ekwtamil99uni.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ekwtamil99uni.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_dinka.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_dinka.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_dinka.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_dinka.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_harari_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_harari_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_harari_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_harari_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_naija.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_naija.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_naija.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_naija.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_nuer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_nuer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_nuer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_nuer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_pasifika.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_pasifika.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_pasifika.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_pasifika.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_yolngu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_yolngu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_yolngu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_yolngu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/embera_north.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/embera_north.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/embera_north.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/embera_north.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/enga.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/enga.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/enga.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/enga.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/enggano.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/enggano.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/enggano.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/enggano.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/english_shavian_igc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_igc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/english_shavian_igc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_igc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/english_shavian_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/english_shavian_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/engram.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/engram.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/engram.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/engram.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/esperuni.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/esperuni.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/esperuni.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/esperuni.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/farsiman.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/farsiman.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/farsiman.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/farsiman.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/finongan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/finongan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/finongan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/finongan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fulfulde_ajami_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_ajami_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fulfulde_ajami_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_ajami_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fulfulde_latin_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_latin_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fulfulde_latin_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_latin_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_dakelh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dakelh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_dakelh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dakelh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_dane_zaa_zaage.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dane_zaa_zaage.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_dane_zaa_zaage.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dane_zaa_zaage.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_denesuline_epsilon.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_denesuline_epsilon.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_denesuline_epsilon.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_denesuline_epsilon.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_diitiidatx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_diitiidatx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_diitiidatx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_diitiidatx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_gitsenimx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gitsenimx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_gitsenimx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gitsenimx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_gwichin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gwichin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_gwichin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gwichin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_hailzaqvla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hailzaqvla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_hailzaqvla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hailzaqvla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_haisla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_haisla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_haisla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_haisla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_halqemeylem.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_halqemeylem.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_halqemeylem.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_halqemeylem.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_han.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_han.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_han.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_han.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_henqeminem.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_henqeminem.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_henqeminem.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_henqeminem.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_hlgaagilda_xaayda_kil.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hlgaagilda_xaayda_kil.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_hlgaagilda_xaayda_kil.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hlgaagilda_xaayda_kil.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_kanienkeha_e.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kanienkeha_e.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_kanienkeha_e.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kanienkeha_e.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_ktunaxa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_ktunaxa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_ktunaxa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_ktunaxa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_kwakwala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_kwakwala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_kwakwala_liqwala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala_liqwala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_kwakwala_liqwala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala_liqwala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_migmaq.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_migmaq.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_migmaq.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_migmaq.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_natwits.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_natwits.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_natwits.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_natwits.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nisgaa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nisgaa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nisgaa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nisgaa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nlekepmxcin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nlekepmxcin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nlekepmxcin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nlekepmxcin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_northern_tutchone.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_northern_tutchone.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_northern_tutchone.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_northern_tutchone.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nsilxcen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nsilxcen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nsilxcen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nsilxcen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nuucaanul.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nuucaanul.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nuucaanul.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nuucaanul.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_secwepemctsin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_secwepemctsin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_secwepemctsin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_secwepemctsin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_sencoten.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_sencoten.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_sencoten.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_sencoten.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_shashishalhem.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_shashishalhem.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_shashishalhem.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_shashishalhem.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_smalgyax.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_smalgyax.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_smalgyax.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_smalgyax.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_southern_tutchone.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_southern_tutchone.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_southern_tutchone.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_southern_tutchone.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_statimcets.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_statimcets.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_statimcets.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_statimcets.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_stlatlimxec.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_stlatlimxec.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_stlatlimxec.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_stlatlimxec.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tagizi_dene.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tagizi_dene.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tagizi_dene.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tagizi_dene.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_taltan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_taltan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_taltan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_taltan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tlingit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tlingit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tlingit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tlingit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tsekehne.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsekehne.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tsekehne.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsekehne.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tsilhqotin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsilhqotin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tsilhqotin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsilhqotin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_uwikala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_uwikala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_uwikala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_uwikala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_xaislakala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_xaislakala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_xaislakala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_xaislakala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_greek_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_greek_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_greek_positional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_positional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_greek_positional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_positional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_positional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_positional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_positional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_positional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gandhari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gandhari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gandhari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gandhari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/geezbrhan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/geezbrhan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/geezbrhan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/geezbrhan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_amh_7.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amh_7.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_amh_7.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amh_7.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_amharic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amharic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_amharic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amharic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_blin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_blin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_blin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_blin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_ethiopic_7.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_ethiopic_7.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_ethiopic_7.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_ethiopic_7.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_geez.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_geez.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_geez.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_geez.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_gurage.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_gurage.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_gurage_legacy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage_legacy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_gurage_legacy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage_legacy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_musnad.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_musnad.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_musnad.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_musnad.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_tigrinya_eritrea.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_eritrea.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_tigrinya_eritrea.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_eritrea.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_tigrinya_ethiopia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_ethiopia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_tigrinya_ethiopia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_ethiopia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ghana.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ghana.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ghana.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ghana.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gilaki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gilaki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gilaki_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gilaki_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gondi_dev.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_dev.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gondi_dev.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_dev.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gondi_gunjala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_gunjala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gondi_gunjala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_gunjala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gondi_tel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_tel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gondi_tel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_tel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/greekclassical.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/greekclassical.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/greekclassical.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/greekclassical.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hanunoo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hanunoo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hanunoo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hanunoo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/haroi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/haroi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/haroi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/haroi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hatran_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hatran_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hatran_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hatran_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hausa_ajami_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_ajami_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hausa_ajami_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_ajami_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hausa_kano.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_kano.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hausa_kano.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_kano.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hcesar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hcesar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hcesar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hcesar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hieroglyphic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hieroglyphic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hieroglyphic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hieroglyphic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/himyarit_musnad.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/himyarit_musnad.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/himyarit_musnad.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/himyarit_musnad.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hindi_modular.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hindi_modular.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hindi_modular.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hindi_modular.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/indigenous_nt.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/indigenous_nt.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/indigenous_nt.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/indigenous_nt.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/indonesia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/indonesia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/indonesian_suku.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesian_suku.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/indonesian_suku.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesian_suku.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/inuktitut_naqittaut.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/inuktitut_naqittaut.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/inuktitut_naqittaut.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/inuktitut_naqittaut.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ishkashimi_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ishkashimi_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ishkashimi_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ishkashimi_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_bengali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_bengali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_bengali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_bengali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_devanagari_hindi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_hindi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_devanagari_hindi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_hindi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_devanagari_sanskrit_vedic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_sanskrit_vedic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_devanagari_sanskrit_vedic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_sanskrit_vedic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_gujarati.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gujarati.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_gujarati.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gujarati.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_gurmukhi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gurmukhi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_gurmukhi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gurmukhi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_odia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_odia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_odia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_odia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_roman.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_roman.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_roman.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_roman.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/jawa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/jawa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/jawa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/jawa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/jorai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/jorai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/jorai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/jorai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/karakalpak_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/karakalpak_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/karakalpak_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/karakalpak_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kayan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kayan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kayan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kayan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kbdsn1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kbdsn1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kbdsn1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kbdsn1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kharoshthi_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kharoshthi_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kharoshthi_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kharoshthi_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/khmer_advanced.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_advanced.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/khmer_advanced.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_advanced.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/khmer_angkor.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_angkor.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/khmer_angkor.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_angkor.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/khojki_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/khojki_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/khojki_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/khojki_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kmhmu_2008.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kmhmu_2008.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kmhmu_2008.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kmhmu_2008.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/koalibrere.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/koalibrere.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/koalibrere.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/koalibrere.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/korean_rr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/korean_rr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/korean_rr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/korean_rr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/koreguaje.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/koreguaje.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/koreguaje.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/koreguaje.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/krung.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/krung.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/krung.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/krung.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lahu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lahu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lahu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lahu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lamkaang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lamkaang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lamkaang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lamkaang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/landuma.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/landuma.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/landuma.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/landuma.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_2008_basic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_basic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_2008_basic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_basic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_2008_rapid.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_rapid.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_2008_rapid.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_rapid.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_pali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_pali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_pali_us.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali_us.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_pali_us.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali_us.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lazuri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lazuri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lazuri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lazuri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/libtralo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/libtralo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/libtralo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/libtralo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/makasar_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/makasar_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/makasar_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/makasar_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_braille.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_braille.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_braille.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_braille.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_malayalam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_malayalam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_malayalam_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_malayalam_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_tirhuta.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_tirhuta.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_tirhuta.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_tirhuta.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/maltese.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/maltese.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/maltese.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/maltese.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mandaic_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mandaic_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mandaic_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mandaic_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/masaram_gondi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/masaram_gondi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/masaram_gondi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/masaram_gondi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/me_en.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/me_en.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/me_en.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/me_en.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/meitei_legacy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/meitei_legacy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/meitei_legacy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/meitei_legacy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/miluk_hanis_siuslaw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/miluk_hanis_siuslaw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/miluk_hanis_siuslaw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/miluk_hanis_siuslaw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/modi_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/modi_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/modi_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/modi_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mon_anonta.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_anonta.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mon_anonta.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_anonta.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mon_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mon_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mongolian_cyrillic_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mongolian_cyrillic_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mongolian_cyrillic_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mongolian_cyrillic_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mozhi_malayalam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mozhi_malayalam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mozhi_malayalam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mozhi_malayalam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mro_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mro_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mro_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mro_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/multani_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/multani_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/multani_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/multani_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/multi_pak_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/multi_pak_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/multi_pak_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/multi_pak_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/munji.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/munji.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/munji.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/munji.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/myancode_san.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/myancode_san.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/myancode_san.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/myancode_san.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nabataean_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nabataean_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nabataean_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nabataean_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/naijatype.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/naijatype.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/naijatype.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/naijatype.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nailangs.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nailangs.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nailangs.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nailangs.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nasa_yuwe.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nasa_yuwe.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nasa_yuwe.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nasa_yuwe.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nepali_traditional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nepali_traditional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nepali_traditional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nepali_traditional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/newa_romanized.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_romanized.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/newa_romanized.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_romanized.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/newa_traditional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_traditional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/newa_traditional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_traditional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nias.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nias.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nias.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nias.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nisenan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nisenan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nisenan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nisenan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nkonya.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nkonya.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nkonya.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nkonya.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_bengali_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_bengali_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_bengali_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_bengali_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_devanagari_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_devanagari_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_devanagari_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_devanagari_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_gujarati_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gujarati_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_gujarati_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gujarati_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_gurmukhi_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gurmukhi_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_gurmukhi_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gurmukhi_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_ipa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_ipa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_ipa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_ipa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_kannada_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_kannada_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_kannada_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_kannada_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_malayalam_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_malayalam_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_malayalam_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_malayalam_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_oriya_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_oriya_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_oriya_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_oriya_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_tamil_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_tamil_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_tamil_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_tamil_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_telugu_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_telugu_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_telugu_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_telugu_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nrc_makah.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nrc_makah.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nrc_makah.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nrc_makah.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ntl_onekey.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ntl_onekey.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ntl_onekey.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ntl_onekey.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/numanggang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/numanggang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/numanggang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/numanggang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nw_iranian_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nw_iranian_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nw_iranian_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nw_iranian_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/o_tissi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/o_tissi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/o_tissi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/o_tissi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/obolo_chwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_chwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/obolo_chwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_chwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/obolo_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/obolo_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/old_hungarian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_hungarian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/old_hungarian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_hungarian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/old_turkic_udw21_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_turkic_udw21_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/old_turkic_udw21_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_turkic_udw21_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/orma.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/orma.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/orma.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/orma.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/osage_nation.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/osage_nation.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/osage_nation_new.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation_new.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/osage_nation_new.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation_new.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/otoe_missouria.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/otoe_missouria.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/otoe_missouria.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/otoe_missouria.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/persian_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/persian_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/persian_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/persian_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/pid_piaroa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/pid_piaroa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/pid_piaroa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/pid_piaroa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/pingelap.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/pingelap.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/pingelap.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/pingelap.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_dualstroke.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_dualstroke.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_dualstroke.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_dualstroke.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_natural.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_natural.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_natural.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_natural.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_us_dualstroke.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_dualstroke.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_us_dualstroke.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_dualstroke.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_us_natural.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_natural.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_us_natural.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_natural.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/pukapuka.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/pukapuka.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/pukapuka.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/pukapuka.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/qom.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/qom.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/qom.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/qom.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/quinault.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/quinault.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/quinault.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/quinault.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/qwerty_farang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/qwerty_farang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/qwerty_farang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/qwerty_farang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_aer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_aer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_aer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_aer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_arabic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_arabic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_arabic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_arabic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_balti.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_balti.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_balti.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_balti.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_brahui.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_brahui.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_brahui_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_brahui_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_burushaski.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_burushaski.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_burushaski.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_burushaski.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_dameli.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dameli.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_dameli.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dameli.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_dhatki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dhatki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_dhatki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dhatki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_dogri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dogri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_dogri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dogri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_gawar_bati.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawar_bati.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_gawar_bati.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawar_bati.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_gawri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_gawri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_hazaragi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hazaragi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_hazaragi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hazaragi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_hindko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hindko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_hindko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hindko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_indus_kohistani.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_indus_kohistani.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_indus_kohistani.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_indus_kohistani.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_kalasha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kalasha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_kalasha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kalasha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_kashmir_shina.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmir_shina.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_kashmir_shina.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmir_shina.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_kashmiri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmiri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_kashmiri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmiri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_khowar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_khowar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_khowar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_khowar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_marwari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_marwari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_marwari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_marwari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_munji.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_munji.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_munji.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_munji.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_oadki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_oadki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_oadki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_oadki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_ormuri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ormuri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_ormuri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ormuri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_pahari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pahari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_pahari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pahari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_palula.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_palula.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_palula.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_palula.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_parkari_koli.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_parkari_koli.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_parkari_koli.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_parkari_koli.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_pashai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_pashai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_pashto.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashto.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_pashto.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashto.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_saraiki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_saraiki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_saraiki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_saraiki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_shina.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_shina.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_shina.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_shina.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_sindhi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_sindhi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_sindhi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_sindhi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_torwali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_torwali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_torwali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_torwali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_urdu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_urdu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_urdu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_urdu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_ushojo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ushojo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_ushojo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ushojo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_uyghur.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_uyghur.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_uyghur.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_uyghur.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_wadiyara.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wadiyara.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_wadiyara.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wadiyara.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_wakhi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wakhi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_wakhi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wakhi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_western_punjabi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_western_punjabi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_western_punjabi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_western_punjabi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_yidgha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_yidgha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_yidgha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_yidgha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rawang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rawang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rawang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rawang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rejang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rejang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rejang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rejang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/remington_gail.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/remington_gail.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/remington_gail.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/remington_gail.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rohingya_arab.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rohingya_arab.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rohingya_arab.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rohingya_arab.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rossel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rossel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rossel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rossel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/runeboard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/runeboard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/runeboard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/runeboard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/russian_mnemonic_r.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/russian_mnemonic_r.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/russian_mnemonic_r.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/russian_mnemonic_r.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sabdalipi_assamese.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sabdalipi_assamese.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sabdalipi_assamese.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sabdalipi_assamese.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sahaptin_umatilla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_umatilla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sahaptin_umatilla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_umatilla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sahaptin_yakima.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_yakima.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sahaptin_yakima.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_yakima.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sanjha_punjabi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sanjha_punjabi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sanjha_punjabi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sanjha_punjabi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/santali_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/santali_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/santali_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/santali_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/saraiki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/saraiki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/saraiki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/saraiki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/satere.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/satere.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/satere.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/satere.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/shahmukhi_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/shahmukhi_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/shahmukhi_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/shahmukhi_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/shan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/shan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/shan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/shan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/shaw_2layer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/shaw_2layer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/shaw_2layer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/shaw_2layer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/siddham_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/siddham_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/siddham_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/siddham_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_akha_act.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_akha_act.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_akha_act.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_akha_act.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_arabic_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_arabic_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_arabic_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_arabic_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_areare.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_areare.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_areare.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_areare.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bengali_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bengali_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bengali_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bengali_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bolivia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bolivia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bolivia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bolivia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_boonkit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_boonkit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_boonkit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_boonkit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_brao.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_brao.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_brao.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_brao.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bru.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bru.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bru.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bru.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_buang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_buang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_buang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_buang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bunong.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bunong.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bunong.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bunong.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_busa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_busa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_busa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_busa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bwe_karen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bwe_karen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bwe_karen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bwe_karen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cameroon_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cameroon_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cameroon_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cameroon_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cherokee_nation.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cherokee_nation.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cherokee_nation.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cherokee_nation.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cheyenne.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cheyenne.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cheyenne.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cheyenne.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cipher_music.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cipher_music.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cipher_music.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cipher_music.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_devanagari_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_devanagari_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_devanagari_romanized.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_romanized.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_devanagari_romanized.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_romanized.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_devanagari_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_devanagari_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_dzongkha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_dzongkha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_dzongkha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_dzongkha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_eastern_congo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_eastern_congo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_eastern_congo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_eastern_congo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_el_ethiopian_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_el_ethiopian_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_el_ethiopian_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_el_ethiopian_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ethiopic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ethiopic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ethiopic_power_g.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic_power_g.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ethiopic_power_g.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic_power_g.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_euro_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_euro_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_euro_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_euro_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_extended_urdu_np.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_extended_urdu_np.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_extended_urdu_np.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_extended_urdu_np.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_greek_polytonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_greek_polytonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_greek_polytonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_greek_polytonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hawaiian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hawaiian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hawaiian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hawaiian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hebr_grek_trans.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebr_grek_trans.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hebr_grek_trans.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebr_grek_trans.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hebrew.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hebrew.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hebrew_legacy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew_legacy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hebrew_legacy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew_legacy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hmd_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hmd_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hmd_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hmd_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_indic_roman.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_indic_roman.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_indic_roman.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_indic_roman.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ipa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ipa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ipa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ipa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_jarai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_jarai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_jarai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_jarai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kayah_kali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_kali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kayah_kali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_kali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kayah_latn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_latn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kayah_latn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_latn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kayah_mymr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_mymr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kayah_mymr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_mymr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_khamti.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khamti.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_khamti.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khamti.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_khmer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khmer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_khmer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khmer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_khowar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khowar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_khowar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khowar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kmhmu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kmhmu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kmhmu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kmhmu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_korda_jamo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_jamo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_korda_jamo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_jamo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_korda_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_korda_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_korean_morse.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korean_morse.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_korean_morse.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korean_morse.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kvl_kayaw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kvl_kayaw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kvl_kayaw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kvl_kayaw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lepcha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lepcha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lepcha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lepcha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_limbu_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_limbu_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_limbu_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_limbu_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lisu_basic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_basic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lisu_basic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_basic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lisu_standard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_standard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lisu_standard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_standard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lpo_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lpo_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lpo_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lpo_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_madi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_madi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_madi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_madi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_makuri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_makuri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_makuri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_makuri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_mali_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_mali_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_mali_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_mali_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_mali_qwertz.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwertz.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_mali_qwertz.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwertz.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_moore.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_moore.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_moore.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_moore.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_myanmar_my3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_my3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_myanmar_my3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_my3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_myanmar_mywinext.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_mywinext.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_myanmar_mywinext.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_mywinext.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nigeria_dot.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_dot.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nigeria_dot.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_dot.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nigeria_odd_vowels.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_odd_vowels.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nigeria_odd_vowels.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_odd_vowels.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nigeria_underline.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_underline.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nigeria_underline.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_underline.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nubian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nubian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nubian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nubian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_pan_africa_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_pan_africa_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_pan_africa_positional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_positional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_pan_africa_positional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_positional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_philippines.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_philippines.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_philippines.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_philippines.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_sahu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sahu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_sahu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sahu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_bsc_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_bsc_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_bsc_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_bsc_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_cou_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_cou_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_cou_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_cou_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_dyo_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_dyo_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_dyo_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_dyo_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_gsl_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_gsl_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_gsl_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_gsl_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_krx_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_krx_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_krx_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_krx_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_ndv_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_ndv_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_ndv_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_ndv_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_sav_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_sav_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_sav_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_sav_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_snf_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_snf_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_snf_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_snf_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_srr_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_srr_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_srr_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_srr_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_wo_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_wo_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_wo_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_wo_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_sgaw_karen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sgaw_karen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_sgaw_karen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sgaw_karen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_shan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_shan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_shan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_shan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam_lao.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_lao.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam_lao.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_lao.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tawallammat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tawallammat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tawallammat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tawallammat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tchad.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tchad.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tchad.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tchad.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tepehuan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tepehuan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tepehuan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tepehuan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_torwali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_torwali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_torwali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_torwali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tunisian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tunisian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tunisian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tunisian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_uganda_tanzania.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_uganda_tanzania.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_uganda_tanzania.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_uganda_tanzania.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_vai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_vai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_vai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_vai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_wayuu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_wayuu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_wayuu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_wayuu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ygp_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ygp_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ygp_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ygp_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yna_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yna_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yna_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yna_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yoruba8.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba8.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yoruba8.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba8.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yoruba_bar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_bar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yoruba_bar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_bar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yoruba_dot.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_dot.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yoruba_dot.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_dot.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic_ru.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic_ru.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic_ru.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic_ru.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ywq_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ywq_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ywq_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ywq_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/slc_saliba.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/slc_saliba.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/slc_saliba.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/slc_saliba.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/soqotri_arabic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/soqotri_arabic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/soqotri_arabic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/soqotri_arabic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/srr_ajami_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/srr_ajami_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/srr_ajami_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/srr_ajami_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sundanese.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sundanese.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sundanese_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sundanese_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/swanalekha_malayalam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/swanalekha_malayalam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/swanalekha_malayalam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/swanalekha_malayalam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sxava.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sxava.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sxava_eo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava_eo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sxava_eo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava_eo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sylheti_nagri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sylheti_nagri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sylheti_nagri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sylheti_nagri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/syriac_arabic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_arabic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/syriac_arabic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_arabic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/syriac_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/syriac_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tagbanwa_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tagbanwa_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tagbanwa_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tagbanwa_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/taigi_poj.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/taigi_poj.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/taigi_poj.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/taigi_poj.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tainua.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tainua.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tainua.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tainua.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tangsa_lakhum.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tangsa_lakhum.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tangsa_lakhum.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tangsa_lakhum.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tawallammat_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tawallammat_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tawallammat_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tawallammat_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/teggargrent_lat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/teggargrent_lat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/teggargrent_lat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/teggargrent_lat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tem_kdh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tem_kdh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tem_kdh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tem_kdh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_anjal_paangu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_anjal_paangu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_anjal_paangu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_anjal_paangu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_bamini.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_bamini.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_bamini.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_bamini.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_new_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_new_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_new_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_new_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_tamil99_ext.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_tamil99_ext.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_tamil99_ext.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_tamil99_ext.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tibetan_direct_input.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_direct_input.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tibetan_direct_input.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_direct_input.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tibetan_ewts.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_ewts.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tibetan_ewts.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_ewts.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tirhuta.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tirhuta.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tirhuta.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tirhuta.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tlahuica.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tlahuica.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tlahuica.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tlahuica.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tsakonian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tsakonian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tsakonian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tsakonian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tuareg_tifinagh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tuareg_tifinagh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tuareg_tifinagh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tuareg_tifinagh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/turkmen_cyrl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/turkmen_cyrl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/turkmen_cyrl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/turkmen_cyrl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/txo_toto.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/txo_toto.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/txo_toto.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/txo_toto.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/udi_keyboard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/udi_keyboard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/udi_keyboard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/udi_keyboard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ukwuani.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ukwuani.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ukwuani.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ukwuani.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/uma_graphic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_graphic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/uma_graphic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_graphic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/uma_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/uma_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/urdu_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/urdu_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/urdu_phonetic_crulp.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic_crulp.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/urdu_phonetic_crulp.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic_crulp.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/venetia_et_histria.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/venetia_et_histria.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/venetia_et_histria.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/venetia_et_histria.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/vm_tamil_modular.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_modular.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/vm_tamil_modular.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_modular.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/vm_tamil_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/vm_tamil_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wakhi_anglicized.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_anglicized.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wakhi_anglicized.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_anglicized.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wakhi_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wakhi_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wakhi_standard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_standard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wakhi_standard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_standard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wancho.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wancho.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wancho.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wancho.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/warang_citi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/warang_citi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/warang_citi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/warang_citi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wolofal.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wolofal.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wolofal.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wolofal.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/xinaliq.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/xinaliq.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/xinaliq.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/xinaliq.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/yiddish_pasekh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/yiddish_pasekh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/yiddish_pasekh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/yiddish_pasekh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/yidgha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/yidgha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/yidgha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/yidgha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/younger_futhark_short_twig.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/younger_futhark_short_twig.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/younger_futhark_short_twig.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/younger_futhark_short_twig.kmx diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index b81429806e..49924f2d78 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -96,7 +96,7 @@ if get_option('full_test') # kbd is going to be an absolute path kbd_obj = output_path / test_basename + '.kmx' - reference_kmx = meson.current_source_dir() / 'fixtures' / test_basename + '.kmx' + reference_kmx = meson.current_source_dir() / 'fixtures/keyboards-repo' / test_basename + '.kmx' if fs.is_file(reference_kmx) test(test_basename, kmcompxtest, args: [kbd, kbd_obj, reference_kmx]) diff --git a/developer/src/kmcmplib/tests/prep.sh b/developer/src/kmcmplib/tests/prep.sh index 058d4a1b9b..8ad9b81171 100755 --- a/developer/src/kmcmplib/tests/prep.sh +++ b/developer/src/kmcmplib/tests/prep.sh @@ -39,30 +39,30 @@ if [[ ! -d "$KEYBOARDS_ROOT/.git" ]] || [[ ! -f "$KEYBOARDS_ROOT/tools/regressio fi if builder_start_action clean; then - rm -rf "$THIS_SCRIPT_PATH/fixtures" + rm -rf "$THIS_SCRIPT_PATH/fixtures/keyboards-repo" rm -f "$THIS_SCRIPT_PATH/keyboards_commit_ref.txt" builder_finish_action success clean fi if builder_start_action build; then # yes, remove existing fixtures before build so we don't end up with stale fixtures - rm -rf "$THIS_SCRIPT_PATH/fixtures" + rm -rf "$THIS_SCRIPT_PATH/fixtures/keyboards-repo" # record the revision of the keyboards repo pushd "$KEYBOARDS_ROOT" > /dev/null git rev-parse head > "$THIS_SCRIPT_PATH/keyboards_commit_ref.txt" popd > /dev/null - "$KEYBOARDS_ROOT/tools/regression.sh" --use-legacy-compiler --local "$KEYMAN_ROOT/developer/bin/" --output "$THIS_SCRIPT_PATH/fixtures/" + "$KEYBOARDS_ROOT/tools/regression.sh" --use-legacy-compiler --local "$KEYMAN_ROOT/developer/bin/" --output "$THIS_SCRIPT_PATH/fixtures/keyboards-repo/" cd "$THIS_SCRIPT_PATH" # We don't need visual keyboards - rm ./fixtures/*.kvk + rm ./fixtures/keyboards-repo/*.kvk # Nor do we need kmw outputs - rm ./fixtures/*.js + rm ./fixtures/keyboards-repo/*.js # Finally, we remove any keyboards over 500kb, as we won't automatically test them for now # TODO: consider if we want to keep those large keyboards in the future - find ./fixtures/ -size +500k -exec rm {} \+ + find ./fixtures/keyboards-repo/ -size +500k -exec rm {} \+ builder_finish_action success build fi \ No newline at end of file -- GitLab From 9a47cbd599a19fab59e8408d4e72bb4227e4b154 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 13:09:30 +0700 Subject: [PATCH 297/386] fix(web): spacebar-text user-test page --- web/src/test/manual/web/spacebar-text/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/manual/web/spacebar-text/index.html b/web/src/test/manual/web/spacebar-text/index.html index 58f8eaf96f..36fb95440e 100644 --- a/web/src/test/manual/web/spacebar-text/index.html +++ b/web/src/test/manual/web/spacebar-text/index.html @@ -53,7 +53,7 @@ }, false); function setST(t) { - keyman.options['spacebarText'] = t; + keyman.util.setOption('spacebarText', t); document.getElementById('ta1').focus(); //keyman.osk.show(true); <-- this is needed if you are not triggering a re-display of // the keyboard, which the focus() call does for us here. -- GitLab From 71df68cd13fd06fcd1cdc5ff52b527a2fd76211d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 May 2023 10:52:05 +0700 Subject: [PATCH 298/386] refactor(developer): move fs for kmn load to caller --- developer/src/kmcmplib/include/kmcmplibapi.h | 4 +- developer/src/kmcmplib/src/Compiler.cpp | 136 +++++++----------- .../src/kmcmplib/src/CompilerInterfaces.cpp | 89 ++++++++---- developer/src/kmcmplib/src/compfile.h | 29 ---- developer/src/kmcmplib/src/kmcmplib.h | 5 +- developer/src/kmcmplib/src/meson.build | 8 +- developer/src/kmcmplib/tests/api-test.cpp | 50 +++++-- developer/src/kmcmplib/tests/kmcompxtest.cpp | 33 ++++- developer/src/kmcmplib/tests/meson.build | 4 +- 9 files changed, 196 insertions(+), 162 deletions(-) diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index 4054d81e81..5c2ae46fda 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -36,7 +36,7 @@ struct KMCMP_COMPILER_RESULT { }; // TODO: parameters in UTF-8 -typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, char* szText, void* context); +typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, const char* szText, void* context); // parameters in UTF-8 // TODO typical usage: @@ -48,7 +48,7 @@ typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, char* szT // delete[] buf; // return error; // } -typedef bool (*kmcmp_LoadFileProc)(char* loadFilename, char* baseFilename, void* buffer, int* bufferSize); +typedef bool (*kmcmp_LoadFileProc)(const char* loadFilename, const char* baseFilename, void* buffer, int* bufferSize, void* context); // Parameters in UTF-8 EXTERN bool kmcmp_CompileKeyboard( diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index b09e36cbc1..8e0f94cb29 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -94,6 +94,9 @@ #include "CasedKeys.h" #include #include +#include +#include + #include "CheckFilenameConsistency.h" #include "UnreachableRules.h" #include "CheckForDuplicates.h" @@ -230,6 +233,8 @@ enum LinePrefixType { lptNone, lptKeymanAndKeymanWeb, lptKeymanWebOnly, lptKeyma /* Compile target */ kmcmp_CompilerMessageProc msgproc = NULL; +kmcmp_LoadFileProc loadfileproc = NULL; + void* msgprocContext = NULL; int kmcmp::currentLine = 0; @@ -3068,7 +3073,7 @@ KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataS return CERR_None; } -KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) +KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess) { KMX_DWORD len; PKMX_WCHAR p; @@ -3076,21 +3081,26 @@ KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) KMX_DWORD n; KMX_WCHAR currentQuotes = 0; KMX_WCHAR str[LINESIZE + 3]; - len = (KMX_DWORD)fread( str , 1 ,LINESIZE * 2,fp_in); - if (ferror(fp_in) ) return CERR_CannotReadInfile; - len /= 2; - str[len] = 0; auto cur = ftell(fp_in); - fseek(fp_in, 0, SEEK_END); - auto fsize = ftell(fp_in); - fseek(fp_in, cur, SEEK_SET); - if (cur == fsize) + if(offset >= sz) { + return CERR_EndOfFile; + } - // \r\n is still added here even though Linux doesn`t use \r. - // This is to ensure to still have a working windows-only-version - u16ncat(str, u"\r\n", _countof(str)); // I3481 // Always a "\r\n" to the EOF, avoids funny bugs + len = offset + LINESIZE*2 > sz ? sz-offset : LINESIZE*2; + memcpy(str, infile+offset, len); + offset += len; + len /= 2; + str[len] = 0; - if (len == 0) return CERR_EndOfFile; + if(offset == sz) { + // \r\n is still added here even though Linux doesn`t use \r. + // This is to ensure to still have a working windows-only-version + u16ncat(str, u"\r\n", _countof(str)); // I3481 // Always a "\r\n" to the EOF, avoids funny bugs + } + + if (len == 0) { + return CERR_EndOfFile; + } // neccessary to add this block for using on non-windows platforms (removes all \r for platforms that use \n instead of \r\n) for (p = str, n = 0; n < len; n++, p++) { @@ -3172,9 +3182,13 @@ KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) return (PreProcess ? CERR_None : CERR_LineTooLong); } - if (*p == L'\n') kmcmp::currentLine++; + kmcmp::currentLine++; - fseek(fp_in, -(int)(len * 2 - (int)(p - str) * 2 - 2), SEEK_CUR); + offset -= (int)(len * 2 - (int)(p - str) * 2 - 2); + if(offset >= sz) { + // If we've appended a \n, we can go past EOF + offset = sz; + } p--; while (p >= str && iswspace(*p)) p--; @@ -3427,81 +3441,39 @@ KMX_BOOL kmcmp::IsValidCallStore(PFILE_STORE fs) return i == 1; } -FILE* CreateTempFile() -{ - return tmpfile(); +/////////////////// + +bool hasPreamble(std::u16string result) { + return result.size() > 0 && result[0] == 0xFEFF; } -/////////////////// +bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) { + if(sz == 0) { + return FALSE; + } -FILE* UTF16TempFromUTF8(FILE* fp_in , KMX_BOOL hasPreamble) -{ - FILE *fp_out = CreateTempFile(); - if(fp_out == NULL) // I3228 // I3510 - { - fclose(fp_in); - return NULL; //return INVALID_HANDLE_VALUE; _S2 can I exchange that? + std::u16string result; + + try { + std::wstring_convert, char16_t> converter; + result = converter.from_bytes((char*)infile, (char*)infile+sz-1); + } catch(std::range_error e) { + std::wstring_convert, char16_t> converter; + result = converter.from_bytes((char*)infile, (char*)infile+sz-1); } - PKMX_BYTE buf, p; - PKMX_WCHAR outbuf, poutbuf; - KMX_DWORD len; - KMX_DWORD len2; - KMX_WCHAR prolog = 0xFEFF; - fwrite(&prolog,2, 1, fp_out); - - fseek(fp_in, 0, SEEK_END); - len = (KMX_DWORD)ftell(fp_in); - fseek(fp_in, 0, SEEK_SET); - if (hasPreamble) { - fseek( fp_in,3,SEEK_SET); // Cut off UTF-8 marker - len -= 3; - } - - buf = new KMX_BYTE[len + 1]; // null terminated - outbuf = new KMX_WCHAR[len + 1]; - - len2= (KMX_DWORD)fread(buf,1,len,fp_in); - if (len2) { - buf[len2] = 0; - p = buf; - poutbuf = outbuf; - if (hasPreamble) { - // We have a preamble, so we attempt to read as UTF-8 and allow conversion errors to be filtered. This is not great for a - // compiler but matches existing behaviour -- in future versions we may not do lenient conversion. - ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], lenientConversion); - fwrite(outbuf, (KMX_DWORD)(poutbuf - outbuf) * 2 , 1, fp_out); - } - else { - // No preamble, so we attempt to read as strict UTF-8 and fall back to ANSI if that fails - ConversionResult cr = ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], strictConversion); - if (cr == sourceIllegal) { - // Not a valid UTF-8 file, so fall back to ANSI - // AddCompileError(CHINT_NonUnicodeFile); - // note, while this message is defined, for now we will not emit it - // because we don't support HINT/INFO messages yet and we don't want - // this to cause a blocking compile at this stage - // do strtowstr only when no invalid characters are found - if( p==0){ - poutbuf = strtowstr((PKMX_STR)buf); - fwrite(poutbuf, (KMX_DWORD)u16len(poutbuf) * 2 , 1, fp_out); - delete[] poutbuf; - } - else - AddCompileError(CERR_InvalidCharacter); - } + if(hasPreamble(result)) { + *sz16 = result.size() * 2 - 1; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str() + 2, *sz16); - else { - fwrite(outbuf, (KMX_DWORD)(poutbuf - outbuf) * 2 , 1, fp_out); - } - } } - fclose( fp_in); - delete[] buf; - delete[] outbuf; - fseek( fp_out,2,SEEK_SET); - return fp_out; + *sz16 = result.size() * 2; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str(), *sz16); + + return TRUE; } PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID) diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index 71e1e895f2..fd297f4332 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -11,14 +11,14 @@ #include "../../../../common/windows/cpp/include/ConvertUTF.h" #include "../../../../common/windows/cpp/include/keymanversion.h" -bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk); +bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); #ifdef __EMSCRIPTEN__ /* WASM interface for compiler message callback */ -EM_JS(int, wasm_msgproc, (int line, int msgcode, char* text, char* context), { +EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context), { const proc = globalThis[UTF8ToString(context)]; if(!proc || typeof proc != 'function') { console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); @@ -28,7 +28,21 @@ EM_JS(int, wasm_msgproc, (int line, int msgcode, char* text, char* context), { } }); -int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, char* szText, void* context) { +EM_JS(bool, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int* bufferSize, char* context), { + const proc = globalThis[UTF8ToString(context)]; + if(!proc || typeof proc != 'function') { + return 0; + } else { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + } +}); + +bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buffer, int* bufferSize, void* context) { + char* msgProc = static_cast(context); + return wasm_loadfileproc(filename, baseFilename, buffer, bufferSize, msgProc); +} + +int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, void* context) { char* msgProc = static_cast(context); return wasm_msgproc(line, dwMsgCode, szText, msgProc); } @@ -61,7 +75,7 @@ WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPI pszInfile.c_str(), options, wasm_CompilerMessageProc, - nullptr, //wasm_LoadFileProc, + wasm_LoadFileProc, intf.messageCallback.c_str(), kr ); @@ -116,8 +130,6 @@ EXTERN bool kmcmp_CompileKeyboard( KMCMP_COMPILER_RESULT& result ) { - FILE* fp_in = NULL; - KMX_CHAR str[260]; FILE_KEYBOARD fk; kmcmp::FSaveDebug = options.saveDebug; // I3681 @@ -126,7 +138,7 @@ EXTERN bool kmcmp_CompileKeyboard( kmcmp::FShouldAddCompilerVersion = options.shouldAddCompilerVersion; kmcmp::CompileTarget = options.target; - if (!messageProc || !pszInfile) { // TODO: add loadFileProc + if (!messageProc || !loadFileProc || !pszInfile) { AddCompileError(CERR_BadCallParams); return FALSE; } @@ -142,43 +154,58 @@ EXTERN bool kmcmp_CompileKeyboard( } msgproc = messageProc; - //TODO: loadfileproc = loadFileProc; + loadfileproc = loadFileProc; msgprocContext = (void*)procContext; kmcmp::currentLine = 0; kmcmp::nErrors = 0; - fp_in = Open_File(pszInfile, "rb"); - - if (fp_in == NULL) { + int sz; + if(!loadFileProc(pszInfile, "", nullptr, &sz, msgprocContext)) { AddCompileError(CERR_InfileNotExist); return FALSE; } - // Transfer the file to a memory stream for processing UTF-8 or ANSI to UTF-16? - // What about really large files? Transfer to a temp file... - if (!fread(str, 1, 3, fp_in)) { - fclose(fp_in); + if(sz < 3) { + // Technically, a 3 byte file can never be a valid .kmn, so we can shortcut + // here and avoid testing outside memory bounds for looking at BOM AddCompileError(CERR_CannotReadInfile); return FALSE; } - fseek(fp_in, 0, SEEK_SET); - if (str[0] == UTF8Sig[0] && str[1] == UTF8Sig[1] && str[2] == UTF8Sig[2]) - fp_in = UTF16TempFromUTF8(fp_in, TRUE); - else if (str[0] == UTF16Sig[0] && str[1] == UTF16Sig[1]) - fseek(fp_in, 2, SEEK_SET); - else - fp_in = UTF16TempFromUTF8(fp_in, FALSE); - if (fp_in == NULL) { - AddCompileError(CERR_CannotCreateTempfile); + KMX_BYTE* infile = new KMX_BYTE[sz]; + if(!infile) { + AddCompileError(CERR_CannotAllocateMemory); return FALSE; } + if(!loadFileProc(pszInfile, "", infile, &sz, msgprocContext)) { + delete[] infile; + AddCompileError(CERR_CannotReadInfile); + return FALSE; + } + + int offset = 0; + if(infile[0] == (KMX_BYTE) UTF16Sig[0] && infile[1] == (KMX_BYTE) UTF16Sig[1]) { + // UTF-16 source file + offset = 2; + } else { + // UTF-8 source file + KMX_BYTE* infile16; + int sz16; + if(!UTF16TempFromUTF8(infile, sz, &infile16, &sz16)) { + delete[] infile; + AddCompileError(CERR_CannotCreateTempfile); + return FALSE; + } + delete[] infile; + infile = infile16; + sz = sz16; + } kmcmp::CodeConstants = new kmcmp::NamedCodeConstants; - bool success = CompileKeyboardHandle(fp_in, &fk); + bool success = CompileKeyboardHandle(infile+offset, sz-offset, &fk); delete kmcmp::CodeConstants; - fclose(fp_in); + delete[] infile; if (kmcmp::nErrors > 0 || !success) { return FALSE; @@ -204,7 +231,7 @@ EXTERN bool kmcmp_CompileKeyboard( return TRUE; } -bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) +bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) { PKMX_WCHAR str, p; @@ -263,8 +290,10 @@ bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) AddStore(fk, TSS_CUSTOMKEYMANEDITION, u"0"); AddStore(fk, TSS_CUSTOMKEYMANEDITIONNAME, u"Keyman"); + int offset = 0; + // must preprocess for group and store names -> this isn't really necessary, but never mind! - while ((msg = ReadLine(fp_in, str, TRUE)) == CERR_None) + while ((msg = ReadLine(infile, sz, offset, str, TRUE)) == CERR_None) { p = str; switch (LineTokenType(&p)) @@ -301,7 +330,7 @@ bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) return FALSE; } - fseek( fp_in,2,SEEK_SET); + offset = 0; kmcmp::currentLine = 0; /* Reindex the list of codeconstants after stores added */ @@ -309,7 +338,7 @@ bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) kmcmp::CodeConstants->reindex(); /* ReadLine will automatically skip over $Keyman lines, and parse wrapped lines */ - while ((msg = ReadLine(fp_in, str, FALSE)) == CERR_None) + while ((msg = ReadLine(infile, sz, offset, str, FALSE)) == CERR_None) { msg = ParseLine(fk, str); if (msg != CERR_None) { diff --git a/developer/src/kmcmplib/src/compfile.h b/developer/src/kmcmplib/src/compfile.h index 595d1fecec..7c262be090 100644 --- a/developer/src/kmcmplib/src/compfile.h +++ b/developer/src/kmcmplib/src/compfile.h @@ -197,33 +197,4 @@ struct COMPILEMESSAGES { typedef COMPILEMESSAGES *PCOMPILEMESSAGES; -/* -struct TVersion -{ - //int MinVersion; // 0x0500 usually - //int CompilerVersion[4]; - //int MinCompilerVersion[4]; - int KeyboardVersion; // 0x0500 usually -}; - -extern TVersion FVersionInfo; -*/ - -/* -#define bstrcpy(c,d) (LPBYTE)strcpy((LPSTR)(c),(LPSTR)(d)) -#define bstrlen(c) strlen((LPSTR)(c)) -#define bstrcmp(c,d) strcmp((LPSTR)(c),(LPSTR)(d)) -#define bstrncmp(c,d,n) strncmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrnicmp(c,d,n) strnicmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstricmp(c,d) stricmp((LPSTR)(c),(LPSTR)(d)) -#define bstrchr(c,ch) (LPBYTE)strchr((LPSTR)(c),(char)ch) -#define bstrncpy(c,d,n) (LPBYTE)strncpy((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrtok(c,d) (LPBYTE)strtok((LPSTR)(c),(LPSTR)(d)) -#define bstrcat(c,d) (LPBYTE)strcat((LPSTR)(c),(LPSTR)(d)) -#define bstrncat(c,d,n) (LPBYTE)strncat((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrrev(c) (LPBYTE)strrev((LPSTR)(c)) -#define batoi(c) atoi((LPSTR)(c)) -#define bstrtol(c,d,n) strtol((LPSTR)(c),(LPSTR *)(d),(n)) -*/ - #endif // _COMPFILE_H diff --git a/developer/src/kmcmplib/src/kmcmplib.h b/developer/src/kmcmplib/src/kmcmplib.h index 78e945ee0d..70718f98bc 100644 --- a/developer/src/kmcmplib/src/kmcmplib.h +++ b/developer/src/kmcmplib/src/kmcmplib.h @@ -24,6 +24,7 @@ namespace kmcmp { } extern kmcmp_CompilerMessageProc msgproc; +extern kmcmp_LoadFileProc loadfileproc; extern void* msgprocContext; extern KMX_BOOL AWarnDeprecatedCode_GLOBAL_LIB; @@ -40,10 +41,10 @@ KMX_BOOL AddCompileError(KMX_DWORD msg); PKMX_WCHAR strtowstr(PKMX_STR in); PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID); -FILE* UTF16TempFromUTF8(FILE* fp_in , KMX_BOOL hasPreamble); +bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16); KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataSize); KMX_DWORD AddStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, const KMX_WCHAR * str, KMX_DWORD *dwStoreID= NULL); -KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess); +KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess); KMX_DWORD ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str); KMX_DWORD ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p); KMX_DWORD ProcessGroupFinish(PFILE_KEYBOARD fk); diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 8264c3d28e..727d6831e6 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -7,6 +7,7 @@ # TODO: is this required? It should be Keyman Core only defns += ['-DKMN_KBP_EXPORTING'] version_res = [] +lib_links = [] if cpp_compiler.get_id() == 'gcc' or cpp_compiler.get_id() == 'clang' warns += [ @@ -24,12 +25,13 @@ endif name_suffix = [] if cpp_compiler.get_id() == 'emscripten' - links += ['-lnodefs.js', '-sMODULARIZE', '-sEXPORT_ES6', '--whole-archive', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] + lib_links = ['--whole-archive', '--bind', '-sMODULARIZE', '-sEXPORT_ES6'] + links += ['-lnodefs.js', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] # tests are building as ES6 so we need to declare the file extension # note that meson currently struggles with the sanitycheckc_cross.exe # program, because it has a hard coded extension (.exe) which is not # valid for node programs in module mode. - name_suffix = '.mjs' + # name_suffix = '.mjs' endif icu = subproject('icu-for-uset', default_options: [ 'default_library=static', 'cpp_std=c++17', 'warning_level=0', 'werror=false']) @@ -63,7 +65,7 @@ lib = library('kmcmplib', version_res, cpp_args: defns + warns + flags, - link_args: links, + link_args: links + lib_links, version: meson.project_version(), include_directories: inc, install: true, diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index 4bb0276a79..b347773220 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -18,13 +18,14 @@ #include #include "../src/compfile.h" #include +#include "../src/filesystem.h" void setup(); -void test_kmcmp_CompileKeyboard(); +void test_kmcmp_CompileKeyboard(char *kmn_file); std::vector error_vec; -int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { error_vec.push_back(dwMsgCode); const char*t = "unknown"; switch(dwMsgCode & 0xF000) { @@ -37,9 +38,42 @@ int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { return 1; } +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { + FILE* fp = Open_File(filename, "rb"); + if(!fp) { + return false; + } + + if(!data) { + // return size + if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + *size = ftell(fp); + if(*size == -1L) { + fclose(fp); + return false; + } + } else { + // return data + if(fread(data, 1, *size, fp) != *size) { + fclose(fp); + return false; + } + } + fclose(fp); + return true; +} + int main(int argc, char *argv[]) { + if(argc < 1) { + puts("Usage: api-test "); + puts("Warning: blank_keyboard will be overwritten"); + return 1; + } setup(); - test_kmcmp_CompileKeyboard(); + test_kmcmp_CompileKeyboard(argv[1]); return 0; } @@ -48,13 +82,9 @@ void setup() { error_vec.clear(); } -void test_kmcmp_CompileKeyboard() { - char kmn_file[L_tmpnam], kmx_file[L_tmpnam]; - tmpnam(kmn_file); - tmpnam(kmx_file); - +void test_kmcmp_CompileKeyboard(char *kmn_file) { // Create an empty file - FILE *fp = fopen(kmn_file, "w"); + FILE *fp = Open_File(kmn_file, "wb"); fclose(fp); // It should fail when a zero-byte file is passed in @@ -65,7 +95,7 @@ void test_kmcmp_CompileKeyboard() { options.warnDeprecatedCode = true; options.shouldAddCompilerVersion = false; options.target = CKF_KEYMAN; - assert(!kmcmp_CompileKeyboard(kmn_file, options, msgproc, nullptr, nullptr, result)); + assert(!kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)); assert(error_vec.size() == 1); assert(error_vec[0] == CERR_CannotReadInfile); diff --git a/developer/src/kmcmplib/tests/kmcompxtest.cpp b/developer/src/kmcmplib/tests/kmcompxtest.cpp index 87d9e53cf7..c5d189798b 100644 --- a/developer/src/kmcmplib/tests/kmcompxtest.cpp +++ b/developer/src/kmcmplib/tests/kmcompxtest.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "../src/filesystem.h" #ifdef _MSC_VER #else @@ -28,7 +29,7 @@ vector < int > error_vec; #define CERR_WARNING 0x00002000 #define CERR_HINT 0x00001000 -int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { error_vec.push_back(dwMsgCode); const char*t = "unknown"; @@ -42,6 +43,34 @@ int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) return 1; } +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { + FILE* fp = Open_File(filename, "rb"); + if(!fp) { + return false; + } + + if(!data) { + // return size + if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + *size = ftell(fp); + if(*size == -1L) { + fclose(fp); + return false; + } + } else { + // return data + if(fread(data, 1, *size, fp) != *size) { + fclose(fp); + return false; + } + } + fclose(fp); + return true; +} + #include "../src/filesystem.h" int main(int argc, char *argv[]) @@ -79,7 +108,7 @@ int main(int argc, char *argv[]) options.shouldAddCompilerVersion = false; options.target = CKF_KEYMAN; - if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, nullptr, nullptr, result)) { + if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)) { char* testname = strrchr( (char*) kmn_file, '/') + 1; if(strncmp(testname, pfirst5, 5) == 0){ return __LINE__; // exit code: CERR_ in Name + no Error found diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index 49924f2d78..2468ac53b1 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -113,10 +113,10 @@ apitest = executable('api-test', 'api-test.cpp', name_suffix: name_suffix, link_args: links + tests_flags, objects: lib.extract_all_objects(), - dependencies: icuuc_dep, + dependencies: icuuc_dep ) -test('api-test', apitest) +test('api-test', apitest, args: [output_path / 'blank_keyboard.kmx']) usetapitest = executable('uset-api-test', 'uset-api-test.cpp', cpp_args: defns, -- GitLab From 3e4e95677c8e962cb0762165737728cb9ae785c1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 13:33:29 +0700 Subject: [PATCH 299/386] change(web): bases all user-test pages off of the build/publish/debug folder --- web/src/test/manual/web/attachment-api/index.html | 7 +++---- web/src/test/manual/web/basic-iframe/index.html | 8 +++----- web/src/test/manual/web/build-visual-keyboard/index.html | 8 +++----- web/src/test/manual/web/caps-lock-layer-3620/index.html | 7 +++---- web/src/test/manual/web/chirality/index.html | 7 +++---- web/src/test/manual/web/ckeditor/index.html | 7 +++---- web/src/test/manual/web/ckeditor/inline.html | 7 +++---- web/src/test/manual/web/desktop-ui/button.html | 7 +++---- web/src/test/manual/web/desktop-ui/float.html | 7 +++---- web/src/test/manual/web/desktop-ui/toggle.html | 7 +++---- web/src/test/manual/web/desktop-ui/toolbar.html | 7 +++---- web/src/test/manual/web/empty-row/index.html | 7 +++---- web/src/test/manual/web/inline-osk/index.html | 7 +++---- web/src/test/manual/web/issue005/index.html | 7 +++---- web/src/test/manual/web/issue103/index.html | 7 +++---- web/src/test/manual/web/issue115/index.html | 7 +++---- web/src/test/manual/web/issue116/index.html | 7 +++---- web/src/test/manual/web/issue1332/index.html | 7 +++---- web/src/test/manual/web/issue160/index.html | 7 +++---- web/src/test/manual/web/issue266/index.html | 7 +++---- web/src/test/manual/web/issue271/index.html | 7 +++---- web/src/test/manual/web/issue29/index.html | 8 +++----- web/src/test/manual/web/issue2924/header.js | 3 +-- web/src/test/manual/web/issue2924/index.html | 4 ++-- web/src/test/manual/web/issue3701/index.html | 7 +++---- web/src/test/manual/web/issue382/index.html | 7 +++---- web/src/test/manual/web/issue53/index.html | 7 +++---- web/src/test/manual/web/issue5455/index.html | 7 +++---- web/src/test/manual/web/issue6005/index.html | 7 +++---- web/src/test/manual/web/issue62/index.html | 7 +++---- web/src/test/manual/web/issue63/index.html | 7 +++---- .../manual/web/issue917-context-and-notany/index.html | 7 +++---- web/src/test/manual/web/issue920/index.html | 7 +++---- web/src/test/manual/web/keyboard-errors/index.html | 7 +++---- web/src/test/manual/web/mnemonic/index.html | 8 +++----- web/src/test/manual/web/options-with-save/index.html | 8 +++----- web/src/test/manual/web/osk-event-buttons/index.html | 7 +++---- web/src/test/manual/web/osk-movement/index.html | 7 +++---- web/src/test/manual/web/platform/index.html | 7 +++---- web/src/test/manual/web/prediction-mtnt/index.html | 7 +++---- web/src/test/manual/web/prediction-ui/index.html | 7 +++---- web/src/test/manual/web/promise-api/index.html | 7 +++---- web/src/test/manual/web/rotation-events/index.html | 7 +++---- web/src/test/manual/web/sentry-integration/index.html | 3 +-- web/src/test/manual/web/spacebar-text/index.html | 7 +++---- web/src/test/manual/web/start-of-sentence-3621/index.html | 7 +++---- web/src/test/manual/web/test-updateLayer/index.html | 7 +++---- web/src/test/manual/web/unminified - manual.html | 5 ++--- web/src/test/manual/web/unminified.html | 5 ++--- web/src/tools/testing/bulk_rendering/index.html | 5 ++--- web/src/tools/testing/recorder/index.html | 5 ++--- 51 files changed, 144 insertions(+), 199 deletions(-) diff --git a/web/src/test/manual/web/attachment-api/index.html b/web/src/test/manual/web/attachment-api/index.html index 45bcfb8db9..f904d25f08 100644 --- a/web/src/test/manual/web/attachment-api/index.html +++ b/web/src/test/manual/web/attachment-api/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/basic-iframe/index.html b/web/src/test/manual/web/basic-iframe/index.html index 22d1b9492a..8a935f2cc6 100644 --- a/web/src/test/manual/web/basic-iframe/index.html +++ b/web/src/test/manual/web/basic-iframe/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/build-visual-keyboard/index.html b/web/src/test/manual/web/build-visual-keyboard/index.html index d2bed2610e..2c3e1d6c0a 100644 --- a/web/src/test/manual/web/build-visual-keyboard/index.html +++ b/web/src/test/manual/web/build-visual-keyboard/index.html @@ -24,7 +24,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/ckeditor/index.html b/web/src/test/manual/web/ckeditor/index.html index 8e326f8bb0..950f8bcb9b 100644 --- a/web/src/test/manual/web/ckeditor/index.html +++ b/web/src/test/manual/web/ckeditor/index.html @@ -17,7 +17,7 @@ - + - + diff --git a/web/src/test/manual/web/ckeditor/inline.html b/web/src/test/manual/web/ckeditor/inline.html index fb53d9d5cf..463ebf2138 100644 --- a/web/src/test/manual/web/ckeditor/inline.html +++ b/web/src/test/manual/web/ckeditor/inline.html @@ -17,7 +17,7 @@ - + - + diff --git a/web/src/test/manual/web/desktop-ui/button.html b/web/src/test/manual/web/desktop-ui/button.html index a52dbfc0c5..6722fe26dc 100644 --- a/web/src/test/manual/web/desktop-ui/button.html +++ b/web/src/test/manual/web/desktop-ui/button.html @@ -23,7 +23,7 @@ - + - + @@ -41,8 +41,7 @@ + - + @@ -41,8 +41,7 @@ + - + @@ -41,8 +41,7 @@ + - + diff --git a/web/src/test/manual/web/empty-row/index.html b/web/src/test/manual/web/empty-row/index.html index 570f54331c..02953493e9 100644 --- a/web/src/test/manual/web/empty-row/index.html +++ b/web/src/test/manual/web/empty-row/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/issue103/index.html b/web/src/test/manual/web/issue103/index.html index 86db130f9a..35b006e347 100644 --- a/web/src/test/manual/web/issue103/index.html +++ b/web/src/test/manual/web/issue103/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue115/index.html b/web/src/test/manual/web/issue115/index.html index dbc777ab37..cd9afcb8fa 100644 --- a/web/src/test/manual/web/issue115/index.html +++ b/web/src/test/manual/web/issue115/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/issue160/index.html b/web/src/test/manual/web/issue160/index.html index ed83776fd8..33219e7fdf 100644 --- a/web/src/test/manual/web/issue160/index.html +++ b/web/src/test/manual/web/issue160/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/issue29/index.html b/web/src/test/manual/web/issue29/index.html index b8476887c5..24a226326c 100644 --- a/web/src/test/manual/web/issue29/index.html +++ b/web/src/test/manual/web/issue29/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue2924/header.js b/web/src/test/manual/web/issue2924/header.js index e7d70b67c8..dcd8083c7d 100644 --- a/web/src/test/manual/web/issue2924/header.js +++ b/web/src/test/manual/web/issue2924/header.js @@ -1,6 +1,5 @@ keyman.init({ - attachType: 'auto', - resources: '../../resources' + attachType: 'auto' }); window.addEventListener('load', function() { diff --git a/web/src/test/manual/web/issue2924/index.html b/web/src/test/manual/web/issue2924/index.html index c8653b9bfe..12396420c2 100644 --- a/web/src/test/manual/web/issue2924/index.html +++ b/web/src/test/manual/web/issue2924/index.html @@ -6,8 +6,8 @@ KeymanWeb Issue 2924 - Variable Stores and Predictive Text - - + + diff --git a/web/src/test/manual/web/issue3701/index.html b/web/src/test/manual/web/issue3701/index.html index c1ebd33c0a..4b621678cc 100644 --- a/web/src/test/manual/web/issue3701/index.html +++ b/web/src/test/manual/web/issue3701/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/issue5455/index.html b/web/src/test/manual/web/issue5455/index.html index ebe2d1c56e..77ea7dbb21 100644 --- a/web/src/test/manual/web/issue5455/index.html +++ b/web/src/test/manual/web/issue5455/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/issue63/index.html b/web/src/test/manual/web/issue63/index.html index 0b5630bd12..55cee63c97 100644 --- a/web/src/test/manual/web/issue63/index.html +++ b/web/src/test/manual/web/issue63/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue917-context-and-notany/index.html b/web/src/test/manual/web/issue917-context-and-notany/index.html index 02ff4307ac..251edb9142 100644 --- a/web/src/test/manual/web/issue917-context-and-notany/index.html +++ b/web/src/test/manual/web/issue917-context-and-notany/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/mnemonic/index.html b/web/src/test/manual/web/mnemonic/index.html index f1f42aebbc..e0b0f0de79 100644 --- a/web/src/test/manual/web/mnemonic/index.html +++ b/web/src/test/manual/web/mnemonic/index.html @@ -20,7 +20,7 @@ - + - + diff --git a/web/src/test/manual/web/options-with-save/index.html b/web/src/test/manual/web/options-with-save/index.html index f88944c3da..9f607fd11f 100644 --- a/web/src/test/manual/web/options-with-save/index.html +++ b/web/src/test/manual/web/options-with-save/index.html @@ -20,7 +20,7 @@ - + - + diff --git a/web/src/test/manual/web/osk-event-buttons/index.html b/web/src/test/manual/web/osk-event-buttons/index.html index 0c2da2bb28..92dafcdb4a 100644 --- a/web/src/test/manual/web/osk-event-buttons/index.html +++ b/web/src/test/manual/web/osk-event-buttons/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/prediction-mtnt/index.html b/web/src/test/manual/web/prediction-mtnt/index.html index 76120694be..29dd746879 100644 --- a/web/src/test/manual/web/prediction-mtnt/index.html +++ b/web/src/test/manual/web/prediction-mtnt/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + @@ -52,8 +52,7 @@ kmw.init({ attachType: 'auto', - useAlerts: alertType, - resources:'../../resources' + useAlerts: alertType }).then(function() { loadKeyboards(); }); diff --git a/web/src/test/manual/web/rotation-events/index.html b/web/src/test/manual/web/rotation-events/index.html index f66ba55c2b..c07b3300c9 100644 --- a/web/src/test/manual/web/rotation-events/index.html +++ b/web/src/test/manual/web/rotation-events/index.html @@ -14,7 +14,7 @@ - + - + diff --git a/web/src/test/manual/web/sentry-integration/index.html b/web/src/test/manual/web/sentry-integration/index.html index f330963109..6453920b15 100644 --- a/web/src/test/manual/web/sentry-integration/index.html +++ b/web/src/test/manual/web/sentry-integration/index.html @@ -40,8 +40,7 @@ + - + + - + + - + diff --git a/web/src/test/manual/web/unminified - manual.html b/web/src/test/manual/web/unminified - manual.html index 230d2df3e7..dbe7cdae48 100644 --- a/web/src/test/manual/web/unminified - manual.html +++ b/web/src/test/manual/web/unminified - manual.html @@ -23,7 +23,7 @@ - + - + + - + + - + + @@ -56,7 +56,6 @@ window.addEventListener("load", function() { keyman.init({ attachType: 'auto', - resources: '../../resources', keyboards: '' }); -- GitLab From 24464328f33aa0bc2bbc7537985f8272a8c7fc85 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 30 May 2023 13:36:46 +0700 Subject: [PATCH 300/386] chore(web): a touch more cleanup --- web/src/test/manual/web/sentry-integration/index.html | 4 ++-- web/src/tools/building/check-build-size.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/test/manual/web/sentry-integration/index.html b/web/src/test/manual/web/sentry-integration/index.html index 6453920b15..51d4635c67 100644 --- a/web/src/test/manual/web/sentry-integration/index.html +++ b/web/src/test/manual/web/sentry-integration/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/tools/building/check-build-size.sh b/web/src/tools/building/check-build-size.sh index 41b233cecd..7a3a47f900 100755 --- a/web/src/tools/building/check-build-size.sh +++ b/web/src/tools/building/check-build-size.sh @@ -84,7 +84,7 @@ parse_params "$@" # Get file size of the latest local minified build # -LOCAL_FILE=web/build/app/browser/release/keymanweb.js +LOCAL_FILE=web/build/publish/release/keymanweb.js LOCAL_FILE_SIZE=`stat --printf="%s" $KEYMAN_ROOT/$LOCAL_FILE` # -- GitLab From 9b4ca1aebd76286f7a223b757d306b5097f9850e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 26 May 2023 19:32:14 +0200 Subject: [PATCH 301/386] chore(linux): Move build steps to build.sh Closes #8712. --- linux/keyman-config/Makefile | 53 +++++----------------- linux/keyman-config/build.sh | 70 ++++++++++++++++++++++++++++-- linux/scripts/install.sh | 16 +++---- linux/scripts/package-build.inc.sh | 5 +-- linux/scripts/reconf.sh | 2 +- 5 files changed, 86 insertions(+), 60 deletions(-) diff --git a/linux/keyman-config/Makefile b/linux/keyman-config/Makefile index 41eaab30ef..f057dcad0c 100644 --- a/linux/keyman-config/Makefile +++ b/linux/keyman-config/Makefile @@ -1,49 +1,19 @@ #!/usr/bin/make -default: clean version man langtags - python3 setup.py build - -langtags: - cd buildtools && python3 ./build-langtags.py +default: + ./build.sh clean build install: - if [ -n "${SUDO_USER}" ]; then \ - make install-sudo; \ - else \ - make install-temp; \ - fi - -install-sudo: # run as sudo - pip3 install qrcode sentry-sdk - # eventually change this to: pip3 install . - python3 setup.py install - # install icons - mkdir -p /usr/local/share/keyman/icons - cp keyman_config/icons/* /usr/local/share/keyman/icons - # install man pages - mkdir -p /usr/local/share/man/man1 - cp ../../debian/man/*.1 /usr/local/share/man/man1 - -install-temp: - mkdir -p /tmp/keyman/$(shell python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)') - # when we no longer have to support old pip version (python > 3.6) change this to: - # pip3 install --prefix /tmp/keyman . - PYTHONUSERBASE=/tmp/keyman python3 setup.py install --user - -uninstall: clean # run as sudo - rm -rf /usr/local/share/keyman/icons - rm -f /usr/local/share/man/man1/km-*.1 - pip3 uninstall keyman_config - rm -f /usr/local/bin/km-config - rm -f /usr/local/bin/km-kvk2ldml - rm -f /usr/local/bin/km-package-get - rm -f /usr/local/bin/km-package-install - rm -f /usr/local/bin/km-package-list-installed - rm -f /usr/local/bin/km-package-uninstall + ./build.sh install + +uninstall: # run as sudo + ./build.sh uninstall clean: - -rm -rf dist make_deb build keyman_config/version.py *.egg-info __pycache__ \ - keyman_config/standards/lang_tags_map.py + ./build.sh clean + +check: + ./build.sh test devdist: version python3 setup.py egg_info -b.`TZ=UTC git log -1 --pretty=format:%cd --date=format-local:%Y%m%d%H%M` sdist @@ -93,6 +63,3 @@ $(MOFILES): %/LC_MESSAGES/keyman-config.mo: %.po msgfmt -v --check --output-file=$@ $^ compile-po: $(MOFILES) - -check: - ./run-tests.sh diff --git a/linux/keyman-config/build.sh b/linux/keyman-config/build.sh index 38cf22e917..aba4ebcd41 100755 --- a/linux/keyman-config/build.sh +++ b/linux/keyman-config/build.sh @@ -24,9 +24,71 @@ cd "$THIS_SCRIPT_PATH" builder_describe_outputs \ build "/linux/keyman-config/keyman_config/standards/lang_tags_map.py" -builder_run_action clean make clean +clean_action() { + rm -rf dist make_deb build keyman_config/version.py ./*.egg-info __pycache__ \ + keyman_config/standards/lang_tags_map.py +} + +build_action() { + builder_echo "Create version.py" + pushd keyman_config + sed \ + -e "s/_VERSION_/${VERSION}/g" \ + -e "s/_VERSIONWITHTAG_/${VERSION_WITH_TAG}/g" \ + -e "s/_VERSIONGITTAG_/${VERSION_GIT_TAG}/g" \ + -e "s/_MAJORVERSION_/${VERSION_MAJOR}/g" \ + -e "s/_RELEASEVERSION_/${VERSION_RELEASE}/g" \ + -e "s/_TIER_/${TIER}/g" \ + -e "s/_ENVIRONMENT_/${VERSION_ENVIRONMENT}/g" \ + -e "s/_UPLOADSENTRY_/${UPLOAD_SENTRY}/g" \ + version.py.in > version.py + popd + pushd buildtools + builder_echo "Create lang_tags_map.py" + python3 ./build-langtags.py + popd + builder_echo "Building man pages" + ./build-help.sh --man --no-reconf + builder_echo "Building keyman-config" + python3 setup.py build +} + +install_action() { + if [ -n "${SUDO_USER:-}" ]; then + pip3 install qrcode sentry-sdk + # eventually change this to: pip3 install . + python3 setup.py install + # install icons + mkdir -p /usr/local/share/keyman/icons + cp keyman_config/icons/* /usr/local/share/keyman/icons + # install man pages + mkdir -p /usr/local/share/man/man1 + cp ../../debian/man/*.1 /usr/local/share/man/man1 + else + mkdir -p "/tmp/keyman/$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)')" + # when we no longer have to support old pip version (python > 3.6) change this to: + # pip3 install --prefix /tmp/keyman . + PYTHONUSERBASE=/tmp/keyman python3 setup.py install --user + fi +} + +uninstall_action() { + # run as sudo + clean_action + rm -rf /usr/local/share/keyman/icons + rm -f /usr/local/share/man/man1/km-*.1 + pip3 uninstall keyman_config + rm -f /usr/local/bin/km-config + rm -f /usr/local/bin/km-kvk2ldml + rm -f /usr/local/bin/km-package-get + rm -f /usr/local/bin/km-package-install + rm -f /usr/local/bin/km-package-list-installed + rm -f /usr/local/bin/km-package-uninstall +} + +builder_run_action clean clean_action builder_run_action configure # nothing to do -builder_run_action build make +builder_run_action build build_action builder_run_action test ./run-tests.sh -builder_run_action install make install -builder_run_action uninstall make uninstall +builder_run_action install install_action +builder_run_action uninstall uninstall_action diff --git a/linux/scripts/install.sh b/linux/scripts/install.sh index 40f2eb8f5b..55074f4848 100755 --- a/linux/scripts/install.sh +++ b/linux/scripts/install.sh @@ -15,7 +15,7 @@ INSTALLDIR=${INSTALLDIR:-"/usr/local"} if [[ "${SUDOINSTALL}" != "no" ]]; then if [ "$EUID" -ne 0 ] then - echo "Please run 'make un/install' with sudo" + echo "Please run 'build.sh un/install' with sudo" exit fi fi @@ -24,7 +24,7 @@ if [ -f "/usr/share/ibus/component/keyman.xml" ] && [ "${SUDOINSTALL}" == "yes" if grep -Fq "/usr/lib/ibus" /usr/share/ibus/component/keyman.xml then echo "component file is in ibus-keyman package version so move it" - echo "run 'sudo make uninstall' to put it back" + echo "run 'sudo build.sh uninstall' to put it back" mv /usr/share/ibus/component/keyman.xml /usr/share/doc/ibus-keyman/ else echo "component file is local one so overwrite it" @@ -37,10 +37,10 @@ cd "$BASEDIR" cd ibus-keyman if [[ "${SUDOINSTALL}" == "uninstall" ]]; then - echo "doing make uninstall of ibus-keyman" + echo "doing build.sh uninstall of ibus-keyman" ./build.sh uninstall else - echo "doing make install of ibus-keyman" + echo "doing build.sh install of ibus-keyman" ./build.sh install fi cd "$BASEDIR" @@ -49,20 +49,20 @@ cd keyman-config echo "SUDOINSTALL: ${SUDOINSTALL}" if [[ "${SUDOINSTALL}" == "yes" ]]; then if [ ! -d build ]; then - echo "keyman-config must be built before it is installed. Run 'make configure' if needed then 'make'" + echo "keyman-config must be built before it is installed. Run 'build.sh configure build'." exit 1 fi echo "doing sudo glib-compile-schemas for keyman-config" cp com.keyman.gschema.xml /usr/share/glib-2.0/schemas/ glib-compile-schemas /usr/share/glib-2.0/schemas/ echo "doing sudo install of keyman-config" - make install + ./build.sh install elif [[ "${SUDOINSTALL}" == "uninstall" ]]; then echo "doing sudo uninstall of keyman-config" - make uninstall + ./build.sh uninstall else echo "doing /tmp install of keyman-config" - make install-temp + ./build.sh install fi cd "$BASEDIR" diff --git a/linux/scripts/package-build.inc.sh b/linux/scripts/package-build.inc.sh index 39ea70f7d1..6c249891c1 100644 --- a/linux/scripts/package-build.inc.sh +++ b/linux/scripts/package-build.inc.sh @@ -28,10 +28,7 @@ function downloadSource() { if [ "${proj:=}" == "keyman" ]; then cd "${BASEDIR}" || exit - fi - - if [ "${proj}" == "keyman" ]; then - make clean + ./build.sh clean fi # Update tier in Debian watch files (replacing any previously set tier) and remove comment diff --git a/linux/scripts/reconf.sh b/linux/scripts/reconf.sh index cd74571a4a..4e4f5c1b11 100755 --- a/linux/scripts/reconf.sh +++ b/linux/scripts/reconf.sh @@ -21,7 +21,7 @@ cd "$BASEDIR/ibus-keyman" ./build.sh clean configure cd "$BASEDIR/keyman-config" -make clean +./build.sh clean cd keyman_config sed \ -- GitLab From fd562019dd18f577cb3a38d82b4765b2da773e7d Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 30 May 2023 11:35:24 +0200 Subject: [PATCH 302/386] chore(linux): Use build.sh for Debian packaging --- linux/debian/rules | 12 ++++++------ linux/keyman-config/build.sh | 10 +++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/linux/debian/rules b/linux/debian/rules index ab4ad409e4..db1f925017 100755 --- a/linux/debian/rules +++ b/linux/debian/rules @@ -9,7 +9,9 @@ export PYBUILD_INSTALL_ARGS=--install-scripts=/usr/share/keyman-config/ export KEYMAN_PKG_BUILD=1 +export HOME=$(shell mktemp -d) export XDG_DATA_HOME=$(shell mktemp -d) +export XDG_CONFIG_HOME=$(shell mktemp -d) # xenial needs this to be explicit export LC_ALL=C.UTF-8 @@ -32,26 +34,23 @@ override_dh_auto_configure: --wrap-mode=nodownload --prefix=/usr --sysconfdir=/etc --localstatedir=/var linux/keyman-system-service/build.sh configure -- \ --wrap-mode=nodownload --prefix=/usr --sysconfdir=/etc --localstatedir=/var - # keyman-config + linux/keyman-config/build.sh configure override_dh_auto_build: core/build.sh --no-tests build:arch linux/ibus-keyman/build.sh build linux/keyman-system-service/build.sh build - # keyman-config + linux/keyman-config/build.sh build cd linux/keyman-config && \ - make man && \ sed -i -e "s/^__pkgversion__ = \"[^\"]*\"/__pkgversion__ = \"$(DEB_VERSION)\"/g" keyman_config/version.py && \ make compile-po - dh_auto_build --sourcedir=linux/keyman-config --buildsystem=pybuild $@ override_dh_auto_test: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) core/build.sh --no-tests test:arch linux/ibus-keyman/build.sh test linux/keyman-system-service/build.sh test - # keyman-config - dh_auto_test --sourcedir=linux/keyman-config --buildsystem=pybuild $@ + linux/keyman-config/build.sh test endif override_dh_auto_install: @@ -63,6 +62,7 @@ override_dh_auto_install: install -d $(CURDIR)/debian/keyman/usr/share/ cp -r linux/keyman-config/locale/ $(CURDIR)/debian/keyman/usr/share/ rm $(CURDIR)/debian/keyman/usr/share/locale/*.po* + # Don't call `build.sh install` - dh_auto_install does some extra smarts dh_auto_install --sourcedir=linux/keyman-config --buildsystem=pybuild $@ # Unfortunately bash-completion 2.10 (focal) doesn't yet provide dh-sequence-bash-completion, # which we could add as build-dependency, so we'll have to explicitly call diff --git a/linux/keyman-config/build.sh b/linux/keyman-config/build.sh index aba4ebcd41..23d138b28e 100755 --- a/linux/keyman-config/build.sh +++ b/linux/keyman-config/build.sh @@ -44,8 +44,12 @@ build_action() { version.py.in > version.py popd pushd buildtools - builder_echo "Create lang_tags_map.py" - python3 ./build-langtags.py + if [ -f build-langtags.py ]; then + builder_echo "Create lang_tags_map.py" + python3 ./build-langtags.py + else + builder_echo "Skip building lang_tags_map.py during package build" + fi popd builder_echo "Building man pages" ./build-help.sh --man --no-reconf @@ -68,7 +72,7 @@ install_action() { mkdir -p "/tmp/keyman/$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)')" # when we no longer have to support old pip version (python > 3.6) change this to: # pip3 install --prefix /tmp/keyman . - PYTHONUSERBASE=/tmp/keyman python3 setup.py install --user + PYTHONUSERBASE=${DESTDIR:-/tmp/keyman} python3 setup.py install --user fi } -- GitLab From ccba767e2e17e8f6128a6a987c40b34ac15544f6 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 30 May 2023 17:18:12 +0200 Subject: [PATCH 303/386] chore(linux): Allow to run tests when package building with sbuild --- linux/keyman-config/run-tests.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/linux/keyman-config/run-tests.sh b/linux/keyman-config/run-tests.sh index 3b8664512a..b390ce613b 100755 --- a/linux/keyman-config/run-tests.sh +++ b/linux/keyman-config/run-tests.sh @@ -1,6 +1,9 @@ #!/bin/bash PYTHONPATH=.:$PYTHONPATH +XDG_CONFIG_HOME=$(mktemp --directory) +export XDG_CONFIG_HOME + if [ -n "$TEAMCITY_VERSION" ]; then if ! pip3 list --format=columns | grep -q teamcity-messages; then pip3 install teamcity-messages @@ -9,3 +12,5 @@ if [ -n "$TEAMCITY_VERSION" ]; then else python3 -m unittest discover -v -s tests -p test_*.py fi + +rm -rf "$XDG_CONFIG_HOME" -- GitLab From c6f4541c839b54bfc66c5309fe9b85e8e1dbedf3 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 26 May 2023 18:36:29 +0200 Subject: [PATCH 304/386] feat(linux): Fix unit tests --- .../tests/test_gnome_keyboards_util.py | 4 +- linux/keyman-config/tests/test_install_kmp.py | 34 +++++----- .../tests/test_package_install_completion.py | 67 ++++++++++--------- 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/linux/keyman-config/tests/test_gnome_keyboards_util.py b/linux/keyman-config/tests/test_gnome_keyboards_util.py index b5a0cac842..a53908cdd3 100644 --- a/linux/keyman-config/tests/test_gnome_keyboards_util.py +++ b/linux/keyman-config/tests/test_gnome_keyboards_util.py @@ -9,14 +9,14 @@ class GnomeKeyboardsUtilTests(unittest.TestCase): def setUp(self): _reset_gnome_shell() - @patch('keyman_config.os.system') + @patch('os.system') def test_IsGnomeShell_RunningGnomeShell(self, mockSystem): # Setup mockSystem.return_value = 0 # Execute/Verify self.assertEqual(is_gnome_shell(), True) - @patch('keyman_config.os.system') + @patch('os.system') def test_IsGnomeShell_NotRunningGnomeShell(self, mockSystem): # Setup mockSystem.return_value = 1 diff --git a/linux/keyman-config/tests/test_install_kmp.py b/linux/keyman-config/tests/test_install_kmp.py index 2f250550cd..d1e10bd645 100644 --- a/linux/keyman-config/tests/test_install_kmp.py +++ b/linux/keyman-config/tests/test_install_kmp.py @@ -262,13 +262,11 @@ class InstallKmpTests(unittest.TestCase): def test_InstallKmp_FutureKeymanVersion(self): # Setup - workdir = tempfile.TemporaryDirectory().name - os.makedirs(workdir) - packagedir = tempfile.TemporaryDirectory().name - os.makedirs(packagedir) - self.mockGetKeyboardDir.return_value = packagedir - kmpfile = self._createEmptyKmp(workdir) - self._createKmpJson(packagedir, ', "fileVersion": "99.0"') + workdir = tempfile.TemporaryDirectory() + packagedir = tempfile.TemporaryDirectory() + self.mockGetKeyboardDir.return_value = packagedir.name + kmpfile = self._createEmptyKmp(workdir.name) + self._createKmpJson(packagedir.name, ', "fileVersion": "99.0"') # Execute with self.assertRaises(InstallError) as context: @@ -278,22 +276,24 @@ class InstallKmpTests(unittest.TestCase): self.assertTrue('foo.kmp requires Keyman 99.0 or higher' in context.exception.message) self.mockInstallToIbus.assert_not_called() + # Teardown + workdir.cleanup() + packagedir.cleanup() + def test_InstallKmp(self): for testcase in [ {'name': 'PreviousKeymanVersion', 'fileVersion': ', "fileVersion": "7.0"'}, - {'name': 'SameKeymanVersion', 'fileVersion': ', "fileVersion": "' + __version__ + '"'}, + {'name': 'SameKeymanVersion', 'fileVersion': f', "fileVersion": "{__version__}"'}, {'name': 'NoFileVersion', 'fileVersion': ''} ]: with self.subTest(msg=testcase['name'], data=testcase['fileVersion']): # Setup self.mockInstallToIbus.reset_mock() - workdir = tempfile.TemporaryDirectory().name - os.makedirs(workdir) - packagedir = tempfile.TemporaryDirectory().name - os.makedirs(packagedir) - self.mockGetKeyboardDir.return_value = packagedir - kmpfile = self._createEmptyKmp(workdir) - self._createKmpJson(packagedir, testcase['fileVersion']) + workdir = tempfile.TemporaryDirectory() + packagedir = tempfile.TemporaryDirectory() + self.mockGetKeyboardDir.return_value = packagedir.name + kmpfile = self._createEmptyKmp(workdir.name) + self._createKmpJson(packagedir.name, testcase['fileVersion']) # Execute InstallKmp()._install_kmp(kmpfile, 'km', InstallLocation.User) @@ -301,6 +301,10 @@ class InstallKmpTests(unittest.TestCase): # Verify self.mockInstallToIbus.assert_called_once() + # Teardown + workdir.cleanup() + packagedir.cleanup() + if __name__ == '__main__': unittest.main() diff --git a/linux/keyman-config/tests/test_package_install_completion.py b/linux/keyman-config/tests/test_package_install_completion.py index 97238e3dc2..61374a6c66 100644 --- a/linux/keyman-config/tests/test_package_install_completion.py +++ b/linux/keyman-config/tests/test_package_install_completion.py @@ -2,7 +2,7 @@ import os import tempfile import unittest -from unittest.mock import patch, ANY +from unittest.mock import patch from importlib.machinery import SourceFileLoader from importlib.util import module_from_spec, spec_from_loader @@ -10,17 +10,11 @@ from importlib.util import module_from_spec, spec_from_loader class PackageInstallCompletionTests(unittest.TestCase): def setUp(self): - patcher1 = patch('keyman_config.install_kmp.extract_kmp') - self.mockExtractKmp = patcher1.start() - self.addCleanup(patcher1.stop) - patcher2 = patch('keyman_config.kmpmetadata.get_metadata') - self.mockGetMetadata = patcher2.start() - self.addCleanup(patcher2.stop) - + self.mockExtractKmp = self._setupMock('keyman_config.install_kmp.extract_kmp') + self.mockGetMetadata = self._setupMock('keyman_config.kmpmetadata.get_metadata') loader = SourceFileLoader('km_package_install', os.path.join(os.path.dirname( os.path.abspath(__file__)), '../km-package-install')) - spec = spec_from_loader(loader.name, loader) - if spec: + if spec := spec_from_loader(loader.name, loader): self.mod = module_from_spec(spec) loader.exec_module(self.mod) @@ -29,32 +23,43 @@ class PackageInstallCompletionTests(unittest.TestCase): self.cacheDir = os.path.join(self.tempDir.name, 'keyman') os.makedirs(self.cacheDir) + def tearDown(self): + self.tempDir.cleanup() + + def _setupMock(self, arg0): + patcher = patch(arg0) + result = patcher.start() + self.addCleanup(patcher.stop) + return result + def _list_languages_for_keyboard_impl(self, packageId): return self.mod._list_languages_for_keyboard_impl(packageId, 'someDir') def test_PackageCompletionNoLanguage(self): - open(os.path.join(self.cacheDir, 'foo'), 'w') - self.mockGetMetadata.return_value = (None, None, None, [{}], None) - result = self._list_languages_for_keyboard_impl('foo') - self.assertEqual(result, "") + with open(os.path.join(self.cacheDir, 'foo'), 'w'): + self.mockGetMetadata.return_value = (None, None, None, [{}], None) + result = self._list_languages_for_keyboard_impl('foo') + self.assertEqual(result, "") def test_PackageCompletionOneLanguage(self): - open(os.path.join(self.cacheDir, 'khmer_angkor'), 'w') - self.mockGetMetadata.return_value = ( - None, None, None, - [{'languages': [{'name': 'Central Khmer (Khmer, Cambodia)', 'id': 'km'}]}], - None) - result = self._list_languages_for_keyboard_impl('khmer_angkor') - self.assertEqual(result, "km") + with open(os.path.join(self.cacheDir, 'khmer_angkor'), 'w'): + self.mockGetMetadata.return_value = ( + None, None, None, + [{'languages': [{'name': 'Central Khmer (Khmer, Cambodia)', 'id': 'km'}]}], + None + ) + result = self._list_languages_for_keyboard_impl('khmer_angkor') + self.assertEqual(result, "km") def test_PackageCompletionMultipleLanguages(self): - open(os.path.join(self.cacheDir, 'sil_euro_latin'), 'w') - self.mockGetMetadata.return_value = ( - None, None, None, - [{'languages': [ - {'name': 'English', 'id': 'en'}, - {'name': 'French', 'id': 'fr'}, - {'name': 'German', 'id': 'de'}]}], - None) - result = self._list_languages_for_keyboard_impl('sil_euro_latin') - self.assertEqual(result, 'en\nfr\nde') + with open(os.path.join(self.cacheDir, 'sil_euro_latin'), 'w'): + self.mockGetMetadata.return_value = ( + None, None, None, + [{'languages': [ + {'name': 'English', 'id': 'en'}, + {'name': 'French', 'id': 'fr'}, + {'name': 'German', 'id': 'de'}]}], + None + ) + result = self._list_languages_for_keyboard_impl('sil_euro_latin') + self.assertEqual(result, 'en\nfr\nde') -- GitLab From 4a0f061d4dec8523ad5f5f1edc4dcec7b4d310ac Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 6 Jan 2023 18:48:32 +0100 Subject: [PATCH 305/386] feat(linux): Use StackSidebar for km-config This change displays tabs at the left. It also adds a dummy "Options" page which isn't implemented yet. --- .../keyman_config/view_installed.py | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index 01b1a59b89..ddb13239b0 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -94,7 +94,7 @@ class ViewInstalledWindowBase(Gtk.Window): self.close() def run(self): - self.resize(576, 324) + self.resize(776, 424) self.connect("destroy", Gtk.main_quit) self.show_all() Gtk.main() @@ -191,23 +191,41 @@ class ViewInstalledWindow(ViewInstalledWindowBase): bbox_top.add(self.options_button) vbox.pack_start(bbox_top, False, False, 12) + hbox.pack_start(vbox, False, False, 12) - bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.VERTICAL) - bbox_bottom.set_layout(Gtk.ButtonBoxStyle.END) + outerHbox = Gtk.HBox() + sidebar = Gtk.StackSidebar() + stack = Gtk.Stack() - button = Gtk.Button.new_with_mnemonic(_("_Refresh")) - button.set_tooltip_text(_("Refresh keyboard list")) - button.connect("clicked", self.on_refresh_clicked) + outerHbox.pack_start(sidebar, False, False, 0) + outerHbox.pack_end(Gtk.Separator(), False, False, 0) + outerHbox.pack_end(stack, True, True, 0) + stack.set_hexpand(True) + stack.set_vexpand(True) + sidebar.set_stack(stack) + + stack.add_titled(hbox, "KeyboardLayouts", _("Keyboard Layouts")) + stack.add_titled(Gtk.Label("TODO - Options"), "Options", _("Options")) + + outmostVBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + outmostVBox.pack_start(outerHbox, True, True, 0) + + bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.HORIZONTAL) + bbox_bottom.set_layout(Gtk.ButtonBoxStyle.START) + + button = Gtk.Button.new_with_mnemonic(_("_Install keyboard...")) + button.set_tooltip_text(_("Install a keyboard from a file")) + button.connect("clicked", self.on_installfile_clicked) bbox_bottom.add(button) - button = Gtk.Button.new_with_mnemonic(_("_Download")) + button = Gtk.Button.new_with_mnemonic(_("_Download keyboard...")) button.set_tooltip_text(_("Download and install a keyboard from the Keyman website")) button.connect("clicked", self.on_download_clicked) bbox_bottom.add(button) - button = Gtk.Button.new_with_mnemonic(_("_Install")) - button.set_tooltip_text(_("Install a keyboard from a file")) - button.connect("clicked", self.on_installfile_clicked) + button = Gtk.Button.new_with_mnemonic(_("_Refresh")) + button.set_tooltip_text(_("Refresh keyboard list")) + button.connect("clicked", self.on_refresh_clicked) bbox_bottom.add(button) button = Gtk.Button.new_with_mnemonic(_("_Close")) @@ -217,10 +235,10 @@ class ViewInstalledWindow(ViewInstalledWindowBase): bind_accelerator(self.accelerators, button, 'w') bbox_bottom.add(button) - vbox.pack_end(bbox_bottom, False, False, 12) - - hbox.pack_start(vbox, False, False, 12) - self.add(hbox) + bbox_hbox = Gtk.HBox(spacing=12) + bbox_hbox.pack_start(bbox_bottom, True, True, 12) + outmostVBox.pack_end(bbox_hbox, False, True, 12) + self.add(outmostVBox) def addlistitems(self, installed_kmp, store, install_area): for kmp in sorted(installed_kmp): -- GitLab From 7f9ef0d35b386ab6f75a73b4cdd3600485f01900 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 26 May 2023 17:39:44 +0200 Subject: [PATCH 306/386] feat(linux): Add option to disable Sentry Closes #5027. --- linux/keyman-config/com.keyman.gschema.xml | 6 +- linux/keyman-config/keyman_config/__init__.py | 96 ++--------- .../keyman-config/keyman_config/dconf_util.py | 14 +- .../keyman_config/sentry_handling.py | 152 ++++++++++++++++++ .../keyman_config/view_installed.py | 141 ++++++++-------- 5 files changed, 256 insertions(+), 153 deletions(-) create mode 100644 linux/keyman-config/keyman_config/sentry_handling.py diff --git a/linux/keyman-config/com.keyman.gschema.xml b/linux/keyman-config/com.keyman.gschema.xml index 8f05a42bf9..df50060d8c 100644 --- a/linux/keyman-config/com.keyman.gschema.xml +++ b/linux/keyman-config/com.keyman.gschema.xml @@ -1,8 +1,12 @@ - + + true +

Automatic error reporting + true to enable automatic reporting of errors to Sentry + diff --git a/linux/keyman-config/keyman_config/__init__.py b/linux/keyman-config/keyman_config/__init__.py index e5cecf4cc8..04098ade00 100644 --- a/linux/keyman-config/keyman_config/__init__.py +++ b/linux/keyman-config/keyman_config/__init__.py @@ -1,20 +1,18 @@ -import getpass import gettext -import importlib -import logging -import os -import platform -import sys -from .version import __version__ -from .version import __versionwithtag__ -from .version import __versiongittag__ -from .version import __majorversion__ -from .version import __releaseversion__ -from .version import __tier__ -from .version import __pkgversion__ -from .version import __environment__ -from .version import __uploadsentry__ +from keyman_config.sentry_handling import SentryErrorHandling + +from keyman_config.version import ( + __version__, + __versionwithtag__, + __versiongittag__, + __majorversion__, + __releaseversion__, + __tier__, + __pkgversion__, + __environment__, + __uploadsentry__ +) def _(txt): @@ -24,7 +22,7 @@ def _(txt): return translation -def secure_lookup(data, key1, key2 = None): +def secure_lookup(data, key1, key2=None): """ Return data[key1][key2] while dealing with data being None or key1 or key2 not existing """ @@ -38,18 +36,10 @@ def secure_lookup(data, key1, key2 = None): return None -def before_send(event, hint): - if 'exc_info' in hint: - exc_type, exc_value, tb = hint['exc_info'] - if isinstance(exc_value, KeyboardInterrupt): - # Ignore KeyboardInterrupt exception - return None - return event - - gettext.bindtextdomain('keyman-config', '/usr/share/locale') gettext.textdomain('keyman-config') + #if __tier__ == 'alpha' or __tier__ == 'beta': // #7227 disabling: # Alpha and beta versions will work against the staging server so that they # can access new APIs etc that will only be available there. The staging @@ -60,60 +50,8 @@ gettext.textdomain('keyman-config') KeymanComUrl = 'https://keyman.com' KeymanApiUrl = 'https://api.keyman.com' + # There's no staging site for downloads KeymanDownloadsUrl = 'https://downloads.keyman.com' -if 'unittest' in sys.modules.keys(): - print('Not reporting to Sentry', file=sys.stderr) -elif os.environ.get('KEYMAN_NOSENTRY'): - print('Not reporting to Sentry because KEYMAN_NOSENTRY environment variable set', file=sys.stderr) -elif not __uploadsentry__: - print('Not reporting to Sentry because UPLOAD_SENTRY is false (%s)' % __environment__, file=sys.stderr) -else: - try: - # Try new sentry-sdk first - sentry_sdk = importlib.import_module('sentry_sdk') - from sentry_sdk import configure_scope, set_user - from sentry_sdk.integrations.logging import LoggingIntegration - HaveSentryNewSdk = True - - sentry_logging = LoggingIntegration( - level=logging.INFO, # Capture info and above as breadcrumbs - event_level=logging.CRITICAL # Send critical errors as events - ) - SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357@o1005580.ingest.sentry.io/5983525" - sentry_sdk.init( - dsn=SentryUrl, - environment=__environment__, - release=__versiongittag__, - integrations=[sentry_logging], - before_send=before_send - ) - set_user({'id': hash(getpass.getuser())}) - with configure_scope() as scope: - scope.set_tag("app", os.path.basename(sys.argv[0])) - scope.set_tag("pkgversion", __pkgversion__) - scope.set_tag("platform", platform.platform()) - scope.set_tag("system", platform.system()) - scope.set_tag("tier", __tier__) - except ImportError: - try: - # sentry-sdk is not available, so use older raven - raven = importlib.import_module('raven') - from raven import Client - HaveSentryNewSdk = False - - # Note, legacy raven API requires secret (https://github.com/keymanapp/keyman/pull/5787#discussion_r721457909) - SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357:e6d5a81ee6944fc79bd9f0cbb1f2c2a4@o1005580.ingest.sentry.io/5983525" - client = Client(SentryUrl, environment=__environment__, release=__versiongittag__) - client.user_context({'id': hash(getpass.getuser())}) - client.tags_context({ - 'app': os.path.basename(sys.argv[0]), - 'pkgversion': __pkgversion__, - 'platform': platform.platform(), - 'system': platform.system(), - 'tier': __tier__, - }) - except ImportError: - # even raven is not available. This is the case on Ubuntu 16.04. Just ignore. - print(_('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.'), file=sys.stderr) +SentryErrorHandling().initialize_sentry() diff --git a/linux/keyman-config/keyman_config/dconf_util.py b/linux/keyman-config/keyman_config/dconf_util.py index 09b95d1815..c28ec3fd19 100644 --- a/linux/keyman-config/keyman_config/dconf_util.py +++ b/linux/keyman-config/keyman_config/dconf_util.py @@ -2,25 +2,25 @@ from gi.repository import Gio -# DConf path destkop/ibus/keyman/options -DCONF_BASE = "com.keyman.options" +# GSettings path destkop/ibus/keyman/options +GSETTINGS_BASE = "com.keyman.options" -# Utilities to get and set Keyman options in DConf: +# Utilities to get and set Keyman options in GSettings: # /desktop/ibus/keyman/options/packageID/keyboardID/options def get_child_schema(info): - settings = Gio.Settings.new(DCONF_BASE) + settings = Gio.Settings.new(GSETTINGS_BASE) path = settings.get_property('path') if not path.endswith('/'): path += '/' path += info['packageID'] + '/' + info['keyboardID'] + '/' - return Gio.Settings(DCONF_BASE + '.child', path) + return Gio.Settings(GSETTINGS_BASE + '.child', path) def get_option(info): """ - Get the Keyman keyboard options from DConf + Get the Keyman keyboard options from GSettings Convert from list of comma-separated strings into dictionary Args: @@ -41,7 +41,7 @@ def get_option(info): def set_option(info, options): """ - Store the Keyman keyboard options in DConf as a list of strings + Store the Keyman keyboard options in GSettings as a list of strings Args: info: dictionary diff --git a/linux/keyman-config/keyman_config/sentry_handling.py b/linux/keyman-config/keyman_config/sentry_handling.py new file mode 100644 index 0000000000..f2b2f8adc3 --- /dev/null +++ b/linux/keyman-config/keyman_config/sentry_handling.py @@ -0,0 +1,152 @@ +#!/usr/bin/python3 +import getpass +import importlib +import logging +import os +import platform +import sys +from keyman_config.gsettings import GSettings +from keyman_config.version import ( + __version__, + __versionwithtag__, + __versiongittag__, + __majorversion__, + __releaseversion__, + __tier__, + __pkgversion__, + __environment__, + __uploadsentry__ +) + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gio, Gtk + + +class SentryErrorHandling: + def __init__(self) -> None: + self.settings = Gio.Settings.new('com.keyman.options') + + def initialize_sentry(self): + (enabled, reason) = self.is_sentry_enabled() + if not enabled: + print(reason, file=sys.stderr) + logging.info(reason) + return (enabled, reason) + else: + try: + self._sentry_sdk_initialize() + except ImportError: + try: + self._raven_initialize() + except ImportError: + # even raven is not available. This is the case on Ubuntu 16.04. Just ignore. + print(_('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.'), + file=sys.stderr) + logging.info('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.') + return (False, _('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.')) + return (True, '') + + def is_sentry_enabled(self): + if 'unittest' in sys.modules.keys(): + return (False, 'Running unit tests, not reporting to Sentry') + elif self._get_environ_nosentry(): + return (False, 'Not reporting to Sentry because KEYMAN_NOSENTRY environment variable set') + elif not __uploadsentry__: + return (False, f'Not reporting to Sentry because UPLOAD_SENTRY is false ({__environment__})') + elif not self._get_setting(): + return (False, 'Not reporting to Sentry because disabled in GSettings') + return (True, 'Reporting to Sentry') + + def is_sentry_disabled_by_variable(self): + return self._get_environ_nosentry() or not __uploadsentry__ + + def bind_checkbutton(self, button: Gtk.CheckButton): + self.settings.bind("error-reporting", button, "active", Gio.SettingsBindFlags.NO_SENSITIVITY) + self.settings.connect("changed::error-reporting", self._on_sentry_reporting_toggled) + + def set_enabled(self, enabled): + assert not self.is_sentry_disabled_by_variable() + was_enabled = self.is_sentry_enabled() + self._save_setting(enabled) + if enabled != was_enabled: + self._handle_enabled(enabled) + + def _get_environ_nosentry(self): + keyman_nosentry = os.environ.get('KEYMAN_NOSENTRY') + return keyman_nosentry and (int(keyman_nosentry) == 1) + + def _handle_enabled(self, enabled): + if enabled: + self.initialize_sentry() + else: + self._close_sentry() + + def _save_setting(self, enabled: bool): + self.settings.set_boolean('error-reporting', enabled) + + def _get_setting(self) -> bool: + return self.settings.get_boolean('error-reporting') + + def _on_sentry_reporting_toggled(self, settings, key): + self._handle_enabled(self.settings.get_boolean('error-reporting')) + + def _close_sentry(self): + from sentry_sdk import Hub + logging.info("Shutting down Sentry error reporting") + client = Hub.current.client + if client is not None: + client.close(timeout=2.0) + + def _sentry_sdk_initialize(self): + # Try new sentry-sdk first + sentry_sdk = importlib.import_module('sentry_sdk') + from sentry_sdk import configure_scope, set_user + from sentry_sdk.integrations.logging import LoggingIntegration + + sentry_logging = LoggingIntegration( + level=logging.INFO, # Capture info and above as breadcrumbs + event_level=logging.CRITICAL # Send critical errors as events + ) + SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357@o1005580.ingest.sentry.io/5983525" + sentry_sdk.init( + dsn=SentryUrl, + environment=__environment__, + release=__versiongittag__, + integrations=[sentry_logging], + before_send=self._before_send + ) + set_user({'id': hash(getpass.getuser())}) + with configure_scope() as scope: + scope.set_tag("app", os.path.basename(sys.argv[0])) + scope.set_tag("pkgversion", __pkgversion__) + scope.set_tag("platform", platform.platform()) + scope.set_tag("system", platform.system()) + scope.set_tag("tier", __tier__) + logging.info("Initialized Sentry error reporting") + + def _raven_initialize(self): + # sentry-sdk is not available, so use older raven + raven = importlib.import_module('raven') + from raven import Client + + # Note, legacy raven API requires secret (https://github.com/keymanapp/keyman/pull/5787#discussion_r721457909) + SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357:e6d5a81ee6944fc79bd9f0cbb1f2c2a4@o1005580.ingest.sentry.io/5983525" + client = Client(SentryUrl, environment=__environment__, release=__versiongittag__) + client.user_context({'id': hash(getpass.getuser())}) + client.tags_context({ + 'app': os.path.basename(sys.argv[0]), + 'pkgversion': __pkgversion__, + 'platform': platform.platform(), + 'system': platform.system(), + 'tier': __tier__, + }) + logging.info("Initialized Sentry error reporting (raven)") + + def _before_send(self, event, hint): + if 'exc_info' in hint: + exc_type, exc_value, tb = hint['exc_info'] + if isinstance(exc_value, KeyboardInterrupt): + # Ignore KeyboardInterrupt exception + return None + return event diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index ddb13239b0..1a378a106d 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -25,6 +25,7 @@ from keyman_config.keyboard_details import KeyboardDetailsView from keyman_config.kmpmetadata import get_fonts, parsemetadata from keyman_config.list_installed_kmp import get_installed_kmp from keyman_config.options import OptionsView +from keyman_config.sentry_handling import SentryErrorHandling from keyman_config.uninstall_kmp import uninstall_kmp from keyman_config.welcome import WelcomeView @@ -104,26 +105,60 @@ class ViewInstalledWindow(ViewInstalledWindowBase): def __init__(self): ViewInstalledWindowBase.__init__(self) -# window is split left/right hbox -# right is ButtonBox -# possibly 2 ButtonBox in a vbox -# top one with _Remove, _About, ?_Welcome? or ?Read_Me?, _Options -# bottom one with _Download, _Install, Re_fresh, _Close -# left is GtkTreeView - does it need to be inside anything else apart from the hbox? -# with liststore which defines columns -# GdkPixbuf icon -# gchararray name -# gchararray version -# gchararray packageID (hidden) -# enum? area (user, shared, system) (icon or hidden?) -# gchararray welcomefile (hidden) (or just use area and packageID?) -# changing selected item in treeview changes what buttons are activated -# on selected_item_changed signal set the data that the buttons will use in their callbacks -# see https://developer.gnome.org/gtk3/stable/TreeWidget.html#TreeWidget + self.sentry = SentryErrorHandling() + outerHbox = Gtk.HBox() + sidebar = Gtk.StackSidebar() + stack = Gtk.Stack() + + outerHbox.pack_start(sidebar, False, False, 0) + outerHbox.pack_end(Gtk.Separator(), False, False, 0) + outerHbox.pack_end(stack, True, True, 0) + stack.set_hexpand(True) + stack.set_vexpand(True) + sidebar.set_stack(stack) + + stack.add_titled(self.add_keyboard_layouts_widget(), "KeyboardLayouts", _("Keyboard Layouts")) + stack.add_titled(self.add_options_widget(), "Options", _("Options")) + + outmostVBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + outmostVBox.pack_start(outerHbox, True, True, 0) + + bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.HORIZONTAL) + bbox_bottom.set_layout(Gtk.ButtonBoxStyle.START) + + button = Gtk.Button.new_with_mnemonic(_("_Install keyboard...")) + button.set_tooltip_text(_("Install a keyboard from a file")) + button.connect("clicked", self.on_installfile_clicked) + bbox_bottom.add(button) + + button = Gtk.Button.new_with_mnemonic(_("_Download keyboard...")) + button.set_tooltip_text(_("Download and install a keyboard from the Keyman website")) + button.connect("clicked", self.on_download_clicked) + bbox_bottom.add(button) + + button = Gtk.Button.new_with_mnemonic(_("_Refresh")) + button.set_tooltip_text(_("Refresh keyboard list")) + button.connect("clicked", self.on_refresh_clicked) + bbox_bottom.add(button) + + button = Gtk.Button.new_with_mnemonic(_("_Close")) + button.set_tooltip_text(_("Close window")) + button.connect("clicked", self.on_close_clicked) + bind_accelerator(self.accelerators, button, 'q') + bind_accelerator(self.accelerators, button, 'w') + bbox_bottom.add(button) + + bbox_hbox = Gtk.HBox(spacing=12) + bbox_hbox.pack_start(bbox_bottom, True, True, 12) + outmostVBox.pack_end(bbox_hbox, False, True, 12) + self.add(outmostVBox) + + def add_keyboard_layouts_widget(self): hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - s = Gtk.ScrolledWindow() - hbox.pack_start(s, True, True, 0) + + scrolledWindow = Gtk.ScrolledWindow() + hbox.pack_start(scrolledWindow, True, True, 0) self.store = Gtk.ListStore( GdkPixbuf.Pixbuf, # icon @@ -134,12 +169,6 @@ class ViewInstalledWindow(ViewInstalledWindowBase): str, # path to welcome file if it exists or None str) # path to options file if it exists or None - # add installed keyboards to the the store e.g. - # treeiter = store.append([GdkPixbuf.Pixbuf.new_from_file_at_size( - # "/usr/local/share/keyman/libtralo/libtralo.ico.png", 16, 16), \ - # "LIBTRALO", "1.6.1", \ - # "libtralo", KmpArea.SHARED, True]) - self.refresh_installed_kmp() self.tree = Gtk.TreeView(self.store) @@ -159,7 +188,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): select = self.tree.get_selection() select.connect("changed", self.on_tree_selection_changed) - s.add(self.tree) + scrolledWindow.add(self.tree) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) @@ -193,52 +222,32 @@ class ViewInstalledWindow(ViewInstalledWindowBase): vbox.pack_start(bbox_top, False, False, 12) hbox.pack_start(vbox, False, False, 12) - outerHbox = Gtk.HBox() - sidebar = Gtk.StackSidebar() - stack = Gtk.Stack() - - outerHbox.pack_start(sidebar, False, False, 0) - outerHbox.pack_end(Gtk.Separator(), False, False, 0) - outerHbox.pack_end(stack, True, True, 0) - stack.set_hexpand(True) - stack.set_vexpand(True) - sidebar.set_stack(stack) + return hbox - stack.add_titled(hbox, "KeyboardLayouts", _("Keyboard Layouts")) - stack.add_titled(Gtk.Label("TODO - Options"), "Options", _("Options")) + def add_options_widget(self): + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + label = Gtk.Label(_("General")) + label.set_padding(5, 5) + label.set_halign(Gtk.Align.START) + vbox.pack_start(label, False, False, 10) - outmostVBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - outmostVBox.pack_start(outerHbox, True, True, 0) + (enabled, reason) = self.sentry.is_sentry_enabled() + disabledByVariable = self.sentry.is_sentry_disabled_by_variable() - bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.HORIZONTAL) - bbox_bottom.set_layout(Gtk.ButtonBoxStyle.START) + self.errorReportingButton = Gtk.CheckButton(_("Automatically report errors to keyman.com")) + self.errorReportingButton.set_active(enabled) + self.errorReportingButton.set_sensitive(not disabledByVariable) + self.sentry.bind_checkbutton(self.errorReportingButton) + vbox.pack_start(self.errorReportingButton, False, False, 0) - button = Gtk.Button.new_with_mnemonic(_("_Install keyboard...")) - button.set_tooltip_text(_("Install a keyboard from a file")) - button.connect("clicked", self.on_installfile_clicked) - bbox_bottom.add(button) + if disabledByVariable: + label = Gtk.Label(reason) + label.set_halign(Gtk.Align.START) + label.set_padding(25, 0) + label.set_sensitive(False) + vbox.pack_start(label, False, False, 0) - button = Gtk.Button.new_with_mnemonic(_("_Download keyboard...")) - button.set_tooltip_text(_("Download and install a keyboard from the Keyman website")) - button.connect("clicked", self.on_download_clicked) - bbox_bottom.add(button) - - button = Gtk.Button.new_with_mnemonic(_("_Refresh")) - button.set_tooltip_text(_("Refresh keyboard list")) - button.connect("clicked", self.on_refresh_clicked) - bbox_bottom.add(button) - - button = Gtk.Button.new_with_mnemonic(_("_Close")) - button.set_tooltip_text(_("Close window")) - button.connect("clicked", self.on_close_clicked) - bind_accelerator(self.accelerators, button, 'q') - bind_accelerator(self.accelerators, button, 'w') - bbox_bottom.add(button) - - bbox_hbox = Gtk.HBox(spacing=12) - bbox_hbox.pack_start(bbox_bottom, True, True, 12) - outmostVBox.pack_end(bbox_hbox, False, True, 12) - self.add(outmostVBox) + return vbox def addlistitems(self, installed_kmp, store, install_area): for kmp in sorted(installed_kmp): -- GitLab From a6c4c0763be416b1e86ddb339e45da24e7eadc66 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 30 May 2023 19:01:10 +0200 Subject: [PATCH 307/386] chore(linux): Properly set variables in GHA --- .github/workflows/deb-packaging.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 8c745bef30..8495f5577a 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -119,8 +119,8 @@ jobs: platform: "${{ matrix.arch }}" source_dir: "artifacts/keyman-srcpkg" sourcepackage: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1.dsc" - deb_fullname: $DEBFULLNAME - deb_email: $DEBEMAIL + deb_fullname: ${{env.DEBFULLNAME}} + deb_email: ${{env.DEBEMAIL}} prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }} - name: Output resulting .deb files -- GitLab From b6c90c67b0ce69e4431cec383b8e64d23e3f009a Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 30 May 2023 19:21:41 +0200 Subject: [PATCH 308/386] chore(linux): Temporarily install schema during creation of man pages --- linux/keyman-config/build.sh | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/linux/keyman-config/build.sh b/linux/keyman-config/build.sh index 23d138b28e..7f63eaff1e 100755 --- a/linux/keyman-config/build.sh +++ b/linux/keyman-config/build.sh @@ -29,6 +29,21 @@ clean_action() { keyman_config/standards/lang_tags_map.py } +build_man_pages() { + local TEMP_DATA_DIR SCHEMA_DIR + TEMP_DATA_DIR=$(mktemp -d) + SCHEMA_DIR=$TEMP_DATA_DIR/glib-2.0/schemas + export XDG_DATA_DIRS=$TEMP_DATA_DIR:${XDG_DATA_DIRS-} + export GSETTINGS_SCHEMA_DIR=${SCHEMA_DIR} + mkdir -p "$SCHEMA_DIR" + cp ./com.keyman.gschema.xml "$SCHEMA_DIR"/ + glib-compile-schemas "$SCHEMA_DIR" + ./build-help.sh --man --no-reconf + export XDG_DATA_DIRS=${XDG_DATA_DIRS#*:} + unset GSETTINGS_SCHEMA_DIR + rm -rf $TEMP_DATA_DIR +} + build_action() { builder_echo "Create version.py" pushd keyman_config @@ -52,7 +67,7 @@ build_action() { fi popd builder_echo "Building man pages" - ./build-help.sh --man --no-reconf + build_man_pages builder_echo "Building keyman-config" python3 setup.py build } -- GitLab From 77da56e7487ee70d8acdee4492362a7ab248e149 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 07:44:35 +0700 Subject: [PATCH 309/386] refactor(developer): complete fs move out of kmcmplib * Moves filesystem access out of kmcmplib into kmc-kmn * Adds filesystem access callback to kmcmplib unit tests * Cleans up callback interface through wasm * Adds unit tests for various file load scenarios * Removes nodefs dependency from kmcmplib wasm build, and removes corresponding path mappings which were previously required for wasm builds; note that these are still present for the unit tests for kmcmplib. --- .../src/kmc-kmn/src/compiler/compiler.ts | 55 ++++++++-- .../kmcmplib/src/CheckFilenameConsistency.cpp | 47 ++------ .../kmcmplib/src/CheckFilenameConsistency.h | 2 - developer/src/kmcmplib/src/Compiler.cpp | 102 ++++++++---------- .../src/kmcmplib/src/CompilerInterfaces.cpp | 44 ++++---- .../src/kmcmplib/src/NamedCodeConstants.cpp | 97 ++++++----------- .../src/kmcmplib/src/NamedCodeConstants.h | 5 +- developer/src/kmcmplib/src/compfile.h | 3 +- developer/src/kmcmplib/src/kmcmplib.h | 1 - developer/src/kmcmplib/src/meson.build | 9 +- developer/src/kmcmplib/src/pch.h | 2 - developer/src/kmcmplib/tests/api-test.cpp | 53 ++------- .../valid-keyboards/compile_legacy.bat | 4 + .../fixtures/valid-keyboards/k001_utf16.kmn | Bin 0 -> 452 bytes .../fixtures/valid-keyboards/k001_utf16.kmx | Bin 0 -> 356 bytes .../valid-keyboards/k002_utf8_without_bom.kmn | 11 ++ .../valid-keyboards/k002_utf8_without_bom.kmx | Bin 0 -> 378 bytes .../valid-keyboards/k003_utf8_with_bom.kmn | 11 ++ .../valid-keyboards/k003_utf8_with_bom.kmx | Bin 0 -> 372 bytes .../fixtures/valid-keyboards/k004_ansi.kmn | 13 +++ .../fixtures/valid-keyboards/k004_ansi.kmx | Bin 0 -> 350 bytes .../fixtures/valid-keyboards/k005_bitmap.bmp | Bin 0 -> 246 bytes .../fixtures/valid-keyboards/k005_bitmap.kmn | 12 +++ .../fixtures/valid-keyboards/k005_bitmap.kmx | Bin 0 -> 656 bytes .../fixtures/valid-keyboards/k006_icon.ico | Bin 0 -> 318 bytes .../fixtures/valid-keyboards/k006_icon.kmn | 12 +++ .../fixtures/valid-keyboards/k006_icon.kmx | Bin 0 -> 728 bytes .../valid-keyboards/k007_includecodes_r_n.kmn | 12 +++ .../valid-keyboards/k007_includecodes_r_n.kmx | Bin 0 -> 462 bytes .../valid-keyboards/k007_includecodes_r_n.txt | 2 + .../valid-keyboards/k008_includecodes_n.kmn | 12 +++ .../valid-keyboards/k008_includecodes_n.kmx | Bin 0 -> 454 bytes .../valid-keyboards/k008_includecodes_n.txt | 2 + developer/src/kmcmplib/tests/kmcompxtest.cpp | 49 +-------- developer/src/kmcmplib/tests/meson.build | 41 +++++-- .../src/kmcmplib/tests/util_callbacks.cpp | 59 ++++++++++ developer/src/kmcmplib/tests/util_callbacks.h | 8 ++ .../util_filesystem.cpp} | 41 ++++++- .../filesystem.h => tests/util_filesystem.h} | 5 +- 39 files changed, 406 insertions(+), 308 deletions(-) create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.ico create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.txt create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt create mode 100644 developer/src/kmcmplib/tests/util_callbacks.cpp create mode 100644 developer/src/kmcmplib/tests/util_callbacks.h rename developer/src/kmcmplib/{src/filesystem.cpp => tests/util_filesystem.cpp} (84%) rename developer/src/kmcmplib/{src/filesystem.h => tests/util_filesystem.h} (82%) diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 606bec638c..b27440fd06 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -42,13 +42,16 @@ const baseOptions: CompilerOptions = { */ let callbackProcIdentifier = 0; +const + callbackPrefix = 'kmnCompilerCallbacks_'; + export class KmnCompiler { private Module: any; - callbackName: string; + callbackID: string; // a unique numeric id added to globals with prefixed names callbacks: CompilerCallbacks; constructor() { - this.callbackName = 'kmnCompilerCallback' + callbackProcIdentifier; + this.callbackID = callbackPrefix + callbackProcIdentifier.toString(); callbackProcIdentifier++; } @@ -58,6 +61,7 @@ export class KmnCompiler { try { this.Module = await loadWasmHost(); } catch(e: any) { + /* c8 ignore next 3 */ this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({e})); return false; } @@ -74,7 +78,9 @@ export class KmnCompiler { // Can't report a message here. throw Error('Must call Compiler.init(callbacks) before proceeding'); } - if(!this.Module) { // fail if wasm not loaded or function not found + if(!this.Module) { + /* c8 ignore next 4 */ + // fail if wasm not loaded or function not found this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({})); return false; } @@ -83,15 +89,17 @@ export class KmnCompiler { public run(infile: string, outfile: string, options?: CompilerOptions): boolean { if(!this.verifyInitialized()) { + /* c8 ignore next 2 */ return false; } options = {...baseOptions, ...options}; - (globalThis as any)[this.callbackName] = this.compilerMessageCallback; - // TODO: use callbacks for file access -- so kmc-kmn is entirely fs agnostic + (globalThis as any)[this.callbackID] = { + message: this.compilerMessageCallback, + loadFile: this.loadFileCallback + }; let result = this.runCompiler(infile, outfile, options); - delete (globalThis as any)[this.callbackName]; - //TODO: write the file out! + delete (globalThis as any)[this.callbackID]; if(result) { if(result.kmx) { this.callbacks.fs.writeFileSync(result.kmx.filename, result.kmx.data); @@ -111,6 +119,30 @@ export class KmnCompiler { return 1; } + private loadFileCallback = (filename: string, baseFilename: string, buffer: number, bufferSize: number): number => { + // TODO: we can optimize this in future by avoiding loading the file twice + let resolvedFilename = this.callbacks.resolveFilename(baseFilename, filename); + let data = this.callbacks.loadFile(resolvedFilename); + if(!data) { + return 0; + } + + if(buffer == 0) { + /* We need to return buffer size required */ + return data.byteLength; + } + + if(bufferSize != data.byteLength) { + // TODO: consider chucking a wobbly because this is a bug + /* c8 ignore next 2 */ + return 0; + } + + this.Module.HEAP8.set(data, buffer); + + return 1; + } + private runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { let result: CompilerResult = {}; let wasm_interface = new this.Module.CompilerInterface(); @@ -122,8 +154,7 @@ export class KmnCompiler { wasm_options.warnDeprecatedCode = options.warnDeprecatedCode; wasm_options.shouldAddCompilerVersion = options.shouldAddCompilerVersion; wasm_options.target = 0; //CKF_KEYMAN; TODO, support KMW - wasm_interface.messageCallback = this.callbackName; - wasm_interface.loadFileCallback = this.callbackName; // TODO: this is wrong, needs to be a new callback; not yet used though + wasm_interface.callbacksKey = this.callbackID; // key of object on globalThis wasm_result = this.Module.kmcmp_compile(infile, wasm_options, wasm_interface); if(!wasm_result.result) { return null; @@ -143,6 +174,7 @@ export class KmnCompiler { return result; } catch(e) { + /* c8 ignore next 3 */ this.callbacks.reportMessage(CompilerMessages.Fatal_UnexpectedException({e:e})); return null; } finally { @@ -163,6 +195,7 @@ export class KmnCompiler { reader.validate(kvks, this.callbacks.loadSchema('kvks')); } catch(e) { console.log(e); + // TODO: also unit test // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } @@ -170,6 +203,7 @@ export class KmnCompiler { let vk = reader.transform(kvks, errors); if(!vk || errors.length) { console.dir(errors); + // TODO: also unit test // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } @@ -188,10 +222,12 @@ export class KmnCompiler { */ public parseUnicodeSet(pattern: string, bufferSize: number) : UnicodeSet | null { if(!this.verifyInitialized()) { + /* c8 ignore next 2 */ return null; } if (!bufferSize) { + /* c8 ignore next 2 */ bufferSize = 100; // TODO-LDML: Preflight mode? Reuse buffer? } const buf = this.Module.asm.malloc(bufferSize * 2 * this.Module.HEAPU32.BYTES_PER_ELEMENT); @@ -239,6 +275,7 @@ function getUnicodeSetError(rc: number) : CompilerEvent { case KMCMP_FATAL_OUT_OF_RANGE: return CompilerMessages.Fatal_UnicodeSetOutOfRange(); default: + /* c8 ignore next */ return CompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}); } } diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp index 627298bf37..3a185bc403 100644 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp +++ b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp @@ -7,52 +7,12 @@ #include #include "CheckFilenameConsistency.h" #include "kmx_u16.h" -#include "filesystem.h" #ifdef _MSC_VER #include #endif -namespace kmcmp { - extern KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer -} -bool IsRelativePath(KMX_CHAR const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if ((*p == '\\') || (*p == '/')) return FALSE; - if (*p && *(p + 1) == ':') return FALSE; - - return TRUE; -} - -bool IsRelativePath(KMX_WCHAR const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if ((*p == u'\\') || (*p == u'/'))return FALSE; - if (*p && *(p + 1) == u':') return FALSE; - - return TRUE; -} KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissingFile) { PKMX_WCHAR WFilename = strtowstr(( KMX_CHAR *)Filename); @@ -62,6 +22,12 @@ KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissin } KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile) { + // TODO: we no longer have filesystem access here. We could move this check to + // kmc itself, and make it consistent across all compilers that use the same + // loader callback + return CERR_None; + +#if 0 // not ready yet: needs more attention-> common includes for non-Windows platforms KMX_WCHAR Name[260]; // TODO: fixed buffer sizes bad @@ -115,6 +81,7 @@ KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissin #endif return CERR_None; +#endif } KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk) { diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.h b/developer/src/kmcmplib/src/CheckFilenameConsistency.h index 39f86db391..eba8f2d8bf 100644 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.h +++ b/developer/src/kmcmplib/src/CheckFilenameConsistency.h @@ -7,5 +7,3 @@ KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk); KMX_DWORD CheckFilenameConsistency(KMX_CHAR const * Filename, bool ReportMissingFile); KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile); -bool IsRelativePath(KMX_CHAR const * p); -bool IsRelativePath(KMX_WCHAR const * p); diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 8e0f94cb29..91e02e14e7 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -101,7 +101,6 @@ #include "UnreachableRules.h" #include "CheckForDuplicates.h" #include "kmx_u16.h" -#include "filesystem.h" #include /* These macros are adapted from winnt.h and legacy use only */ @@ -125,7 +124,6 @@ namespace kmcmp{ KMX_BOOL FMnemonicLayout = FALSE; KMX_BOOL FOldCharPosMatching = FALSE; int CompileTarget; - KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer int BeginLine[4]; KMX_BOOL IsValidCallStore(PFILE_STORE fs); @@ -867,7 +865,6 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE int i, j; KMX_DWORD msg; PKMX_WCHAR p, q; - KMX_CHAR *pp; if (!pssBuf) pssBuf = new KMX_WCHAR[GLOBAL_BUFSIZE]; PKMX_WCHAR buf = pssBuf; @@ -917,13 +914,9 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE case TSS_INCLUDECODES: VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_NamedCodes); - pp = wstrtostr(sp->dpString); - if (!kmcmp::CodeConstants->LoadFile(pp)) - { - delete[] pp; + if (!kmcmp::CodeConstants->LoadFile(fk, sp->dpString)) { return CERR_CannotLoadIncludeFile; } - delete[] pp; kmcmp::CodeConstants->reindex(); // I4982 break; @@ -3234,58 +3227,32 @@ KMX_BOOL IsSameToken(PKMX_WCHAR *p, KMX_WCHAR const * token) return FALSE; } -KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +static bool endsWith(const std::string& str, const std::string& suffix) { - FILE *fp; - KMX_WCHAR szNewName[260]; - - if (IsRelativePath(szName)) - { - PKMX_WCHAR WCompileDir = strtowstr(kmcmp::CompileDir); - u16ncpy(szNewName, WCompileDir, _countof(szNewName)); // I3481 - u16ncat(szNewName,szName, _countof(szNewName )); // I3481 - } - else - u16ncpy(szNewName, szName, _countof(szNewName)); // I3481 - - fp=Open_File(szNewName, u"rb"); - - if ( fp == NULL) - { - // else if filename.bmp is not in the folder -> attempt to open filename.bmp.bmp ! - if ( u16cmp(szNewName+u16len(szNewName)-4, u".bmp") ) - u16ncat(szNewName, u".bmp", _countof(szNewName)); // I3481 + return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix); +} - fp= Open_File(szNewName, u"rb"); +KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +{ + auto szNameUtf8 = string_from_u16string(szName); - if ( fp == NULL) + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, (int*) FileSize, msgprocContext)) { + // Append .bmp and try again + if(endsWith(szNameUtf8, ".bmp")) { return CERR_CannotReadBitmapFile; + } + szNameUtf8.append(".bmp"); + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, (int*) FileSize, msgprocContext)) { + return CERR_CannotReadBitmapFile; + } } - KMX_DWORD msg; - if ((msg = CheckFilenameConsistency(szNewName, FALSE)) != CERR_None) { - return msg; - } - - fseek(fp, 0, SEEK_END); - *FileSize = (KMX_DWORD)ftell(fp); - fseek(fp ,0,SEEK_SET); - if (*FileSize < 0) { - fclose(fp); - return CERR_CannotReadBitmapFile; - } - - if (*FileSize < 2) return CERR_CannotReadBitmapFile; *Buf = new KMX_BYTE[*FileSize]; - - if (fread(*Buf, 1, *FileSize, fp) < (size_t) *FileSize) { - delete[] * Buf; - *Buf = NULL; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), *Buf, (int*) FileSize, msgprocContext)) { + delete[] *Buf; return CERR_CannotReadBitmapFile; } - fclose(fp); - /* Test for version 7.0 icon support */ if (*((PKMX_CHAR)*Buf) != 'B' && *(((PKMX_CHAR)*Buf) + 1) != 'M') { VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); @@ -3447,6 +3414,8 @@ bool hasPreamble(std::u16string result) { return result.size() > 0 && result[0] == 0xFEFF; } +#include "unicode/ucnv.h" + bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) { if(sz == 0) { return FALSE; @@ -3456,23 +3425,36 @@ bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) try { std::wstring_convert, char16_t> converter; - result = converter.from_bytes((char*)infile, (char*)infile+sz-1); + result = converter.from_bytes((char*)infile, (char*)infile+sz); } catch(std::range_error e) { - std::wstring_convert, char16_t> converter; - result = converter.from_bytes((char*)infile, (char*)infile+sz-1); + UErrorCode status = U_ZERO_ERROR; + // TODO: we need ICU data files here @srl295 plz help! + UConverter* conv = ucnv_open("windows-1252", &status); + if(U_FAILURE(status)) { + return FALSE; + } + + char16_t* dest = new char16_t[sz*2]; + ucnv_toUChars(conv, dest, sz*2, (char*)infile, sz, &status); + if(U_FAILURE(status)) { + delete[] dest; + return FALSE; + } + + result = dest; + delete[] dest; } if(hasPreamble(result)) { - *sz16 = result.size() * 2 - 1; + *sz16 = result.size() * 2 - 2; *tempfile = new KMX_BYTE[*sz16]; - memcpy(*tempfile, result.c_str() + 2, *sz16); - + memcpy(*tempfile, result.c_str() + 1, *sz16); + } else { + *sz16 = result.size() * 2; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str(), *sz16); } - *sz16 = result.size() * 2; - *tempfile = new KMX_BYTE[*sz16]; - memcpy(*tempfile, result.c_str(), *sz16); - return TRUE; } diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index fd297f4332..1a4e77f8e8 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -3,7 +3,6 @@ #include #include #include "kmcmplib.h" -#include "filesystem.h" #include "CheckFilenameConsistency.h" #include "CheckNCapsConsistency.h" #include "DeprecationChecks.h" @@ -19,7 +18,7 @@ bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); WASM interface for compiler message callback */ EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context), { - const proc = globalThis[UTF8ToString(context)]; + const proc = globalThis[UTF8ToString(context)].message; if(!proc || typeof proc != 'function') { console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); return 0; @@ -28,18 +27,27 @@ EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context } }); -EM_JS(bool, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int* bufferSize, char* context), { - const proc = globalThis[UTF8ToString(context)]; +EM_JS(int, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int bufferSize, char* context), { + const proc = globalThis[UTF8ToString(context)].loadFile; if(!proc || typeof proc != 'function') { return 0; } else { - return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + if(buffer == 0) { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), 0, 0); + } else { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + } } }); bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buffer, int* bufferSize, void* context) { char* msgProc = static_cast(context); - return wasm_loadfileproc(filename, baseFilename, buffer, bufferSize, msgProc); + if(buffer == nullptr) { + *bufferSize = wasm_loadfileproc(filename, baseFilename, 0, 0, msgProc); + return *bufferSize != 0; + } else { + return wasm_loadfileproc(filename, baseFilename, buffer, *bufferSize, msgProc) == 1; + } } int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, void* context) { @@ -48,8 +56,7 @@ int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, v } struct WASM_COMPILER_INTERFACE { - std::string messageCallback; // int line, uint32_t dwMsgCode, char* szText - std::string loadFileCallback; // TODO: char* filename, char* baseFilename --> buffer + std::string callbacksKey; // key of callbacks object on globalThis }; struct WASM_COMPILER_RESULT { @@ -76,7 +83,7 @@ WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPI options, wasm_CompilerMessageProc, wasm_LoadFileProc, - intf.messageCallback.c_str(), + intf.callbacksKey.c_str(), kr ); @@ -103,8 +110,7 @@ EMSCRIPTEN_BINDINGS(compiler_interface) { emscripten::class_("CompilerInterface") .constructor<>() - .property("messageCallback", &WASM_COMPILER_INTERFACE::messageCallback) - .property("loadFileCallback", &WASM_COMPILER_INTERFACE::loadFileCallback) + .property("callbacksKey", &WASM_COMPILER_INTERFACE::callbacksKey) ; emscripten::class_("CompilerResult") @@ -131,6 +137,8 @@ EXTERN bool kmcmp_CompileKeyboard( ) { FILE_KEYBOARD fk; + fk.extra = new FILE_KEYBOARD_EXTRA; + fk.extra->kmnFilename = pszInfile; kmcmp::FSaveDebug = options.saveDebug; // I3681 kmcmp::FCompilerWarningsAsErrors = options.compilerWarningsAsErrors; // I4865 @@ -143,16 +151,6 @@ EXTERN bool kmcmp_CompileKeyboard( return FALSE; } - PKMX_STR p; - - if ((p = strrchr_slash((char*)pszInfile)) != nullptr) { - strncpy(kmcmp::CompileDir, pszInfile, (int)(p - pszInfile + 1)); // I3481 - kmcmp::CompileDir[(int)(p - pszInfile + 1)] = 0; - } - else { - kmcmp::CompileDir[0] = 0; - } - msgproc = messageProc; loadfileproc = loadFileProc; msgprocContext = (void*)procContext; @@ -172,7 +170,7 @@ EXTERN bool kmcmp_CompileKeyboard( return FALSE; } - KMX_BYTE* infile = new KMX_BYTE[sz]; + KMX_BYTE* infile = new KMX_BYTE[sz+1]; if(!infile) { AddCompileError(CERR_CannotAllocateMemory); return FALSE; @@ -182,6 +180,7 @@ EXTERN bool kmcmp_CompileKeyboard( AddCompileError(CERR_CannotReadInfile); return FALSE; } + infile[sz] = 0; // zero-terminate for safety, not technically needed but helps avoid memory bugs int offset = 0; if(infile[0] == (KMX_BYTE) UTF16Sig[0] && infile[1] == (KMX_BYTE) UTF16Sig[1]) { @@ -266,7 +265,6 @@ bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) fk->dpDeadKeyArray = NULL; fk->cxVKDictionary = 0; // I3438 fk->dpVKDictionary = NULL; // I3438 - fk->extra = new FILE_KEYBOARD_EXTRA; fk->extra->kvksFilename = u""; /* fk->szMessage[0] = 0; fk->szLanguageName[0] = 0;*/ diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.cpp b/developer/src/kmcmplib/src/NamedCodeConstants.cpp index e6b1ae6d70..30091b7cb8 100644 --- a/developer/src/kmcmplib/src/NamedCodeConstants.cpp +++ b/developer/src/kmcmplib/src/NamedCodeConstants.cpp @@ -27,15 +27,12 @@ #include "CheckFilenameConsistency.h" #include #include "kmcompx.h" -#include "filesystem.h" using namespace kmcmp; int IsHangulSyllable(const KMX_WCHAR *codename, int *code); namespace kmcmp { - extern KMX_CHAR CompileDir[]; - int __cdecl sort_entries(const void *elem1, const void *elem2) { return u16icmp( @@ -117,85 +114,61 @@ char *kmc_strupr(char *s) { return s; } -KMX_BOOL NamedCodeConstants::IntLoadFile(const KMX_CHAR *filename) -{ +KMX_BOOL NamedCodeConstants::LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filename) { const int str_size = 256; - FILE *fp = NULL; if (CheckFilenameConsistency(filename, FALSE) != 0) { return FALSE; } - fp = Open_File(filename, "rt"); - if(fp == NULL) { - return FALSE; // I3481 + auto szNameUtf8 = string_from_u16string(filename); + + int FileSize; + KMX_BYTE* Buf; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, &FileSize, msgprocContext)) { + return FALSE; } - KMX_CHAR str[str_size], *p, *q, *context = NULL; - KMX_BOOL isEol , first = TRUE; + Buf = new KMX_BYTE[FileSize+1]; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), Buf, &FileSize, msgprocContext)) { + delete[] Buf; + return FALSE; + } + Buf[FileSize] = 0; // zero-terminate for strtok - while(fgets(str, str_size, fp)) - { - isEol = *(strchr(str, 0) - 1) == '\n'; - p = strtok_r(str, ";", &context); // I3481 - q = strtok_r(NULL, ";\n", &context); - if(p && q) - { - if(first && *p == (KMX_CHAR)0xEF && *(p+1) == (KMX_CHAR)0xBB && *(p+2) == (KMX_CHAR)0xBF) p += 3; // I3056 UTF-8 // I3512 - first = FALSE; + char* filetok; + char* filecontext; + filetok = strtok_r((char*)Buf, "\n", &filecontext); + + if(*filetok == (KMX_CHAR)0xEF && *(filetok+1) == (KMX_CHAR)0xBB && *(filetok+2) == (KMX_CHAR)0xBF) filetok += 3; // I3056 UTF-8 // I3512 + + while(filetok) { + KMX_CHAR str[str_size], *p, *q, *context = NULL; + + if(strlen(filetok) >= str_size) { + delete[] Buf; + // TODO chuck a wobbly + return FALSE; + } + strcpy(str, filetok); + p = strtok_r(str, ";\r", &context); // I3481 + q = strtok_r(nullptr, ";\r", &context); + if(p && q) { kmc_strupr(q); // I3481 // I3641 - long n = strtol(p, NULL, 16); + long n = strtol(p, nullptr, 16); if (*q != '<') { PKMX_WCHAR q0 = strtowstr(q); AddCode_IncludedCodes((int)n, q0); delete[] q0; } } - if(!isEol ) - { - while(fgets(str, str_size, fp)) if(*(strchr(str, 0)-1) == '\n') break; - } + filetok = strtok_r(nullptr, "\n", &filecontext); } - fclose(fp); - - return TRUE; -} - -KMX_BOOL NamedCodeConstants::LoadFile(const KMX_CHAR *filename) -{ - const int buf_size = 260; - KMX_CHAR buf[buf_size]; - // Look in current directory first -- REMOVED AS DANGEROUS - /* strncpy(buf, filename, (buf_size-1)); buf[buf_size-1] = 0; // I3481 - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - */ - // Then look in keyboard file directory (CompileDir) - strncpy(buf, CompileDir, (buf_size-1)); buf[buf_size-1] = 0; // I3481 - strncat(buf, filename, (buf_size-1)-strlen(CompileDir)); buf[buf_size-1] = 0; - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - - //TODO: sort out how to find common includes in non-Windows platforms: - #ifdef _WINDOWS_ - // Finally look in kmcmpdll.dll directory - GetModuleFileName(0, buf, buf_size); - - KMX_CHAR *p = strrchr_slash(buf); - if(p) - p++; - else - p = buf; - *p = 0; - strncat_s(buf, _countof(buf), filename, (buf_size-1)-strlen(buf)); buf[buf_size-1] = 0; // I3481 // I3641 - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - #endif + delete[] Buf; reindex(); - - return FALSE; + return TRUE; } void NamedCodeConstants::reindex() diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.h b/developer/src/kmcmplib/src/NamedCodeConstants.h index 83d707d092..c537a73361 100644 --- a/developer/src/kmcmplib/src/NamedCodeConstants.h +++ b/developer/src/kmcmplib/src/NamedCodeConstants.h @@ -2,6 +2,8 @@ #ifndef NAMEDCODECONSTANTS_H #define NAMEDCODECONSTANTS_H +#include "compfile.h" + #define MAX_ENAME 128 #define ALLOC_SIZE 256 @@ -23,14 +25,13 @@ namespace kmcmp{ int GetCode_IncludedCodes(const KMX_WCHAR *codename); void AddCode_IncludedCodes(int n, const KMX_WCHAR *p); - KMX_BOOL IntLoadFile(const KMX_CHAR *filename); public: NamedCodeConstants(); ~NamedCodeConstants(); void reindex(); void AddCode(int n, const KMX_WCHAR *p, KMX_DWORD storeIndex); - KMX_BOOL LoadFile(const KMX_CHAR *filename); + KMX_BOOL LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filename); int GetCode(const KMX_WCHAR *codename, KMX_DWORD *storeIndex); }; } diff --git a/developer/src/kmcmplib/src/compfile.h b/developer/src/kmcmplib/src/compfile.h index 7c262be090..c54663aba7 100644 --- a/developer/src/kmcmplib/src/compfile.h +++ b/developer/src/kmcmplib/src/compfile.h @@ -123,7 +123,8 @@ typedef FILE_VKDICTIONARY *PFILE_VKDICTIONARY; * Extra metadata for API consumers */ struct FILE_KEYBOARD_EXTRA { - std::u16string kvksFilename; // original TSS_VISUALKEYBOARD value + std::string kmnFilename; // utf-8 + std::u16string kvksFilename; // utf-16, original TSS_VISUALKEYBOARD value }; typedef struct FILE_KEYBOARD_EXTRA* PFILE_KEYBOARD_EXTRA; diff --git a/developer/src/kmcmplib/src/kmcmplib.h b/developer/src/kmcmplib/src/kmcmplib.h index 70718f98bc..f22c836bae 100644 --- a/developer/src/kmcmplib/src/kmcmplib.h +++ b/developer/src/kmcmplib/src/kmcmplib.h @@ -14,7 +14,6 @@ namespace kmcmp { extern KMX_BOOL FMnemonicLayout; extern KMX_BOOL FOldCharPosMatching; extern int CompileTarget; - extern KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer extern int BeginLine[4]; extern int currentLine; extern NamedCodeConstants *CodeConstants; diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 727d6831e6..a81efac562 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -25,8 +25,10 @@ endif name_suffix = [] if cpp_compiler.get_id() == 'emscripten' - lib_links = ['--whole-archive', '--bind', '-sMODULARIZE', '-sEXPORT_ES6'] - links += ['-lnodefs.js', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] + # wasm-exceptions supported in Node 18+, Chrome 95+, Firefox 100+, Safari 15.2+ + flags += ['-fwasm-exceptions'] + lib_links = ['--whole-archive', '-sMODULARIZE', '-sEXPORT_ES6'] + links += ['-fwasm-exceptions', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] # tests are building as ES6 so we need to declare the file extension # note that meson currently struggles with the sanitycheckc_cross.exe # program, because it has a hard coded extension (.exe) which is not @@ -44,7 +46,6 @@ lib = library('kmcmplib', 'CompilerInterfaces.cpp', 'DeprecationChecks.cpp', 'Edition.cpp', - 'filesystem.cpp', 'NamedCodeConstants.cpp', 'versioning.cpp', 'virtualcharkeys.cpp', @@ -79,7 +80,7 @@ if cpp_compiler.get_id() == 'emscripten' host = executable('wasm-host', #'wasm-host.cpp', cpp_args: defns, include_directories: inc, - link_args: links, + link_args: links + lib_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep) endif diff --git a/developer/src/kmcmplib/src/pch.h b/developer/src/kmcmplib/src/pch.h index f878ad11be..358e28998a 100644 --- a/developer/src/kmcmplib/src/pch.h +++ b/developer/src/kmcmplib/src/pch.h @@ -10,6 +10,4 @@ #include "../../../../common/windows/cpp/include/crc32.h" #include -#include - #include diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index b347773220..cdbabea4d5 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -18,54 +18,12 @@ #include #include "../src/compfile.h" #include -#include "../src/filesystem.h" +#include "util_filesystem.h" +#include "util_callbacks.h" void setup(); void test_kmcmp_CompileKeyboard(char *kmn_file); -std::vector error_vec; - -int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { - error_vec.push_back(dwMsgCode); - const char*t = "unknown"; - switch(dwMsgCode & 0xF000) { - case CERR_HINT: t=" hint"; break; - case CERR_WARNING: t="warning"; break; - case CERR_ERROR: t=" error"; break; - case CERR_FATAL: t=" fatal"; break; - } - printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); - return 1; -} - -bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { - FILE* fp = Open_File(filename, "rb"); - if(!fp) { - return false; - } - - if(!data) { - // return size - if(fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - return false; - } - *size = ftell(fp); - if(*size == -1L) { - fclose(fp); - return false; - } - } else { - // return data - if(fread(data, 1, *size, fp) != *size) { - fclose(fp); - return false; - } - } - fclose(fp); - return true; -} - int main(int argc, char *argv[]) { if(argc < 1) { puts("Usage: api-test "); @@ -82,6 +40,13 @@ void setup() { error_vec.clear(); } +/* + TODO: tests to run: + 4. ANSI (no BOM of course) + 8. file without blank last line (cannot compare with fixture due to bug in kmcmpdll...) + Hint to add: k004_ansi.kmn: Hint: 10A6 Keyman Developer has detected that the file has ANSI encoding. Consider converting this file to UTF-8 +*/ + void test_kmcmp_CompileKeyboard(char *kmn_file) { // Create an empty file FILE *fp = Open_File(kmn_file, "wb"); diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat new file mode 100644 index 0000000000..e346418eb4 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat @@ -0,0 +1,4 @@ +@echo off +echo Compiles the keyboards using the legacy kmcomp.exe +echo to use as baseline comparisons for kmcmplib +for %%d in (*.kmn) do kmcomp -no-compiler-version -d %%d \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn new file mode 100644 index 0000000000000000000000000000000000000000..2b3180c36ab4b473e74b1b3487fc8f0a61d623f6 GIT binary patch literal 452 zcmZvX%}T>S6otRF;Ci;RG8IwmMz9N^r4>Z2P-{0*q)pQpYC6SO#HDW`K8}yz1F2_b zEO8;>PVStad(V7-rC7RTRHQsou;NLlV@XTQNQ_74DzC9(@0sYdEGXqE<#-S~6_Scs zhQAAVAtuv(qPk(oDf=`z;)0$KKQreYOjH}hfM HO;rB?W(q}E literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx new file mode 100644 index 0000000000000000000000000000000000000000..13ea2a686e09c16db68ff7bb355e114b9125d76d GIT binary patch literal 356 zcmX|-y-EW?6h@CC{(@LJjfK^g5*smH5)hGKgd~cM=wcK?h&EbS+F6LTi1-vfKuBe6 zY2hPS_yT(Fx_IF%-}l|QGdt5f>~@%g>e1pi)hKINm-ap;#+1e%<2!;qYd!Vk use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx new file mode 100644 index 0000000000000000000000000000000000000000..0b9c9294182d9387165c98e1ffc84c8fe45452e3 GIT binary patch literal 378 zcmX|-zb}JP6owxn6pNv86O*ZdP*T+eoFYo)Fd(S=j8hhJK#^I(|T)`TOA8S;iuF8z!+fx45AU`;5awoSE5eup-GIf`;l(pDb^*mi- zgBt5Bs~4F|6HDysw`j7)rhS|MtM+EoD_MGH-mK1!I^)1;9k+mrTP?|74J)FJ+x_0F i=_X>k3LRTrscR?m>{x#(?dZ7 use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx new file mode 100644 index 0000000000000000000000000000000000000000..ee1cf4225502e3c7944b58f301e5be649af4ec00 GIT binary patch literal 372 zcmX|-y-EW?6h@B`{B116X{}O;1cXJqBp?!k5sZqJk{HD$Xi`MP(k_Kq2MIojmG}T+ zXuY}scO@6Efi@` zVV7<77VFupB1igtn$*~{k9%Rq-fA{K%}z+nsvN3Q&Yafqd|2~NN&bIY3KDI+*JNKs gw;=I(PHeTMs$I;pj=nFg=(!IcVH94&lMfV=KM7PUb^rhX literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn new file mode 100644 index 0000000000..50a1b31ea1 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn @@ -0,0 +1,13 @@ +c Description: Verifies that kmcmplib can compile an ANSI file +c This has some high-ascii letters in cp1252 to ensure that +c it fails to load as 'utf8 without bom' + +store(&NAME) 'k004_ansi' +store(&VERSION) '9.0' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ÀÐ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx new file mode 100644 index 0000000000000000000000000000000000000000..6c41da979cb2a3ec20aadf6de0ca778fb8ce5ec1 GIT binary patch literal 350 zcmX|-y-EW?6h@CCYQTV1Y%Ef3DG6A_&MX88!36RnVxa_+Vi8S|DsPZN@&q9dkp~bf zYfGyKTe*wxmCUXD) literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp new file mode 100644 index 0000000000000000000000000000000000000000..509b89ae6df7f09a442ae60f215a6c7b3211b34f GIT binary patch literal 246 zcmZvUF%Ez*2t_e5>FO~&hr4&_cK5)zF7R>OXdqB#E&96yg&T8zj G@{Su^DQ%(v literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn new file mode 100644 index 0000000000..926ccdeeaf --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load a bitmap without file extension + +store(&NAME) 'k005_bitmap' +store(&VERSION) '9.0' +store(&BITMAP) 'k005_bitmap' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx new file mode 100644 index 0000000000000000000000000000000000000000..bc9bc9b671724050e169e59fce976a18d54a030e GIT binary patch literal 656 zcmZ{hyGjF55QhJV5JkZh;`acRmwz7&=*hh@R?A}o^(Fw$>p)BB*B zffhV~2T#xjoA1IijKLecfV&^}PM(8l@|;2L6X&2nE|qnpYn`g0GgqI`FK9t~+E=Ix zr$i`ut*fMpR>+q$@22zGrmm`{HEp0v|4#>;sWV^Frn^B}<&If*f9SrwNI%dPd8h-O z>yVd8RbILaLmMHn(q5d*m8d36E=qSuO$aC_)80MxBQ&hmt@rA+ysp>gH(DpC*#9-V z0w3%j85xb@rP%qu9Eyo?oCG{6#Oic92&~e!$x2D|BF%`g<4g%dtuD`P6eIRAp=916x*GXhr`_H use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx new file mode 100644 index 0000000000000000000000000000000000000000..f994a1dcac9e5ae797eb88eed9cee4b0e6f85b00 GIT binary patch literal 728 zcmbu6JxhX76vzKYU{S*}M9=CF;v`N*4WU9rNH3^GLlE{dNK~#(-JqeGBgC;O`Vb+F zjedqcfJ@|NzjNS>L1T*-NfuF<9K>=T?=vWszRYPY!J|SPwy!O@9q0W6a zs|}U3t`#~KmG_fb?GRU0)0#G^OMfTx$qp&2GJSP+l(glyNUQA0_>-Idlm+r-ZR3ZN zkGPps<)(Wuk}(_$?Z#oQL^NgPqI6B^2FirfN$!sL0U8$TD(=PY_%-hEj3JX^Ulc{? zasM{<);X}Z%7EofoYz5F%?RQ`(K+8^8W)_p-7YPBsr@Qn>kZm|*}mHT`SFvrMGq4+QP0yrw1lmGw# literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn new file mode 100644 index 0000000000..71d72d5120 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load an includecodes file with \r\n line endings + +store(&NAME) 'k007_includecodes_r_n' +store(&VERSION) '9.0' +store(&includecodes) 'k007_includecodes_r_n.txt' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > $LOWER_A +$LOWER_A + [K_B] > $LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx new file mode 100644 index 0000000000000000000000000000000000000000..f8b4eac1b0d9103c7767e487b5109a06aed8e1ec GIT binary patch literal 462 zcma)%J4%C55JrzSDq4tzwy9Ev-%9L~fQZBwiHe=Z#3J|zmm`-ru-1)wlxv$dkL4`TUG*`bcreZN>By?W7ilL#L#M^jq*~&ofWJMn2 zUSj{dJj$$m%7-jT;@)rbqU@`;C3M|ihyS0LqfJlZ use(main) + +group(main) using keys + ++ [K_A] > $LOWER_A +$LOWER_A + [K_B] > $LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx new file mode 100644 index 0000000000000000000000000000000000000000..aa158cdd546cdde915e4a55c1db521e57d4abb04 GIT binary patch literal 454 zcma)%zb=De6o;Q$N*Khz%F>vcZrIv}AYW9XNQWlUmZ3onNVox$!C+{51>y<}(!t;Y zT!2O58f-l0Q{9?4$@8Ay@0|1gOKQ7SCSc(<`M}7;Vq_(BQ96omLphAs!QiZwuH4I< zJV;Ms|C>C@xO~dH%*dj9zs%FJpf~T9ja`yZ6E%3TAmB8Bu*W5Yup0s-ZP)RHs)YS%IvAz p!xzGhzq^ya7*m9eU;geZ=_aFHgeP0iQr1olYJL5SG{V!AP(Sg;I(+~D literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt new file mode 100644 index 0000000000..4cc3fe4351 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt @@ -0,0 +1,2 @@ +0061;LOWER_A +0062;LOWER_B diff --git a/developer/src/kmcmplib/tests/kmcompxtest.cpp b/developer/src/kmcmplib/tests/kmcompxtest.cpp index c5d189798b..5dbaee88bc 100644 --- a/developer/src/kmcmplib/tests/kmcompxtest.cpp +++ b/developer/src/kmcmplib/tests/kmcompxtest.cpp @@ -13,7 +13,8 @@ #include #include #include -#include "../src/filesystem.h" +#include "util_filesystem.h" +#include "util_callbacks.h" #ifdef _MSC_VER #else @@ -22,57 +23,11 @@ using namespace std; -vector < int > error_vec; - #define CERR_FATAL 0x00008000 #define CERR_ERROR 0x00004000 #define CERR_WARNING 0x00002000 #define CERR_HINT 0x00001000 -int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) -{ - error_vec.push_back(dwMsgCode); - const char*t = "unknown"; - switch(dwMsgCode & 0xF000) { - case CERR_HINT: t=" hint"; break; - case CERR_WARNING: t="warning"; break; - case CERR_ERROR: t=" error"; break; - case CERR_FATAL: t=" fatal"; break; - } - printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); - return 1; -} - -bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { - FILE* fp = Open_File(filename, "rb"); - if(!fp) { - return false; - } - - if(!data) { - // return size - if(fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - return false; - } - *size = ftell(fp); - if(*size == -1L) { - fclose(fp); - return false; - } - } else { - // return data - if(fread(data, 1, *size, fp) != *size) { - fclose(fp); - return false; - } - } - fclose(fp); - return true; -} - -#include "../src/filesystem.h" - int main(int argc, char *argv[]) { if(argc < 4) { diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index 2468ac53b1..cc51189a63 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -6,16 +6,20 @@ fs = import('fs') -tests_flags = [] +tests_links = [] + +if cpp_compiler.get_id() == 'emscripten' + tests_links += ['-lnodefs.js'] +endif input_path = meson.current_source_dir() / '../../../../common/test/keyboards/baseline' output_path = meson.current_build_dir() -kmcompxtest = executable('kmcompxtest', 'kmcompxtest.cpp', - cpp_args: defns, +kmcompxtest = executable('kmcompxtest', ['kmcompxtest.cpp','util_filesystem.cpp','util_callbacks.cpp'], + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep, ) @@ -76,6 +80,25 @@ foreach kbd : tests test(kbd, kmcompxtest, args: [kbd_src, kbd_obj, join_paths(input_path, kbd) + '.kmx']) endforeach +valid_keyboard_tests = [ + 'k001_utf16', + 'k002_utf8_without_bom', + 'k003_utf8_with_bom', + # 'k004_ansi', # TODO: enable ansi test when we have the icu datafiles + 'k005_bitmap', + 'k006_icon', + 'k007_includecodes_r_n', + 'k008_includecodes_n' +] + +fixtures_path = meson.current_source_dir() / 'fixtures/valid-keyboards' + +foreach kbd : valid_keyboard_tests + kbd_src = join_paths(fixtures_path, kbd) + '.kmn' + kbd_obj = join_paths(output_path, kbd) + '.kmx' + test(kbd, kmcompxtest, args: [kbd_src, kbd_obj, join_paths(fixtures_path, kbd) + '.kmx']) +endforeach + # Test fixtures that come from keyboards repo -- but only for a "full" test, # which typically we run on CI no more than once a day, because it's expensive. @@ -107,11 +130,11 @@ endif # Test the API endpoints -apitest = executable('api-test', 'api-test.cpp', - cpp_args: defns, +apitest = executable('api-test', ['api-test.cpp','util_filesystem.cpp','util_callbacks.cpp'], + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep ) @@ -119,10 +142,10 @@ apitest = executable('api-test', 'api-test.cpp', test('api-test', apitest, args: [output_path / 'blank_keyboard.kmx']) usetapitest = executable('uset-api-test', 'uset-api-test.cpp', - cpp_args: defns, + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep, ) diff --git a/developer/src/kmcmplib/tests/util_callbacks.cpp b/developer/src/kmcmplib/tests/util_callbacks.cpp new file mode 100644 index 0000000000..e610a54c09 --- /dev/null +++ b/developer/src/kmcmplib/tests/util_callbacks.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include "util_filesystem.h" +#include "../src/compfile.h" +#include + +std::vector error_vec; + +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { + error_vec.push_back(dwMsgCode); + const char*t = "unknown"; + switch(dwMsgCode & 0xF000) { + case CERR_HINT: t=" hint"; break; + case CERR_WARNING: t="warning"; break; + case CERR_ERROR: t=" error"; break; + case CERR_FATAL: t=" fatal"; break; + } + printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); + return 1; +} + +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { + std::string resolvedFilename = filename; + if(baseFilename && *baseFilename && IsRelativePath(filename)) { + char* p; + if ((p = strrchr_slash((char*)baseFilename)) != nullptr) { + std::string basePath = std::string(baseFilename, (int)(p - baseFilename + 1)); + resolvedFilename = basePath; + resolvedFilename.append(filename); + } + } + + FILE* fp = Open_File(resolvedFilename.c_str(), "rb"); + if(!fp) { + return false; + } + + if(!data) { + // return size + if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + *size = ftell(fp); + if(*size == -1L) { + fclose(fp); + return false; + } + } else { + // return data + if(fread(data, 1, *size, fp) != *size) { + fclose(fp); + return false; + } + } + fclose(fp); + return true; +} \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/util_callbacks.h b/developer/src/kmcmplib/tests/util_callbacks.h new file mode 100644 index 0000000000..345de64b9f --- /dev/null +++ b/developer/src/kmcmplib/tests/util_callbacks.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context); +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context); + +extern std::vector error_vec; \ No newline at end of file diff --git a/developer/src/kmcmplib/src/filesystem.cpp b/developer/src/kmcmplib/tests/util_filesystem.cpp similarity index 84% rename from developer/src/kmcmplib/src/filesystem.cpp rename to developer/src/kmcmplib/tests/util_filesystem.cpp index a343641acc..7f9edd6b36 100644 --- a/developer/src/kmcmplib/src/filesystem.cpp +++ b/developer/src/kmcmplib/tests/util_filesystem.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "filesystem.h" +#include "util_filesystem.h" #ifdef _MSC_VER #include @@ -186,3 +186,42 @@ KMX_BOOL kmcmp_FileExists(const KMX_WCHAR* filename) { return FALSE; }; + + +bool IsRelativePath(KMX_CHAR const * p) { + // Relative path (returns TRUE): + // ..\...\BITMAP.BMP + // PATH\BITMAP.BMP + // BITMAP.BMP + + // Semi-absolute path (returns FALSE): + // \...\BITMAP.BMP + + // Absolute path (returns FALSE): + // C:\...\BITMAP.BMP + // \\SERVER\SHARE\...\BITMAP.BMP + + if ((*p == '\\') || (*p == '/')) return FALSE; + if (*p && *(p + 1) == ':') return FALSE; + + return TRUE; +} + +bool IsRelativePath(KMX_WCHAR const * p) { + // Relative path (returns TRUE): + // ..\...\BITMAP.BMP + // PATH\BITMAP.BMP + // BITMAP.BMP + + // Semi-absolute path (returns FALSE): + // \...\BITMAP.BMP + + // Absolute path (returns FALSE): + // C:\...\BITMAP.BMP + // \\SERVER\SHARE\...\BITMAP.BMP + + if ((*p == u'\\') || (*p == u'/'))return FALSE; + if (*p && *(p + 1) == u':') return FALSE; + + return TRUE; +} \ No newline at end of file diff --git a/developer/src/kmcmplib/src/filesystem.h b/developer/src/kmcmplib/tests/util_filesystem.h similarity index 82% rename from developer/src/kmcmplib/src/filesystem.h rename to developer/src/kmcmplib/tests/util_filesystem.h index 786b7268ac..4c94dae6bf 100644 --- a/developer/src/kmcmplib/src/filesystem.h +++ b/developer/src/kmcmplib/tests/util_filesystem.h @@ -1,7 +1,7 @@ #pragma once #include -#include "kmx_u16.h" +#include "../src/kmx_u16.h" // Opens files on windows and non-windows platforms. Datatypes for Filename and mode must be the same. // returns FILE* if file could be opened; FILE needs to be closed in calling function @@ -10,3 +10,6 @@ FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode); FILE* Open_File(const KMX_WCHAR* Filename, const KMX_WCHAR* mode); KMX_BOOL kmcmp_FileExists(const KMX_CHAR *filename); KMX_BOOL kmcmp_FileExists(const KMX_WCHAR *filename); + +bool IsRelativePath(KMX_CHAR const * p); +bool IsRelativePath(KMX_WCHAR const * p); -- GitLab From c95e232f3c51073b292c4706a84c14be9898da6b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 12:13:59 +1000 Subject: [PATCH 310/386] chore(developer): Add TODO issue numbers to PR --- developer/src/kmc-kmn/src/compiler/compiler.ts | 8 ++++---- developer/src/kmcmplib/include/kmcmplibapi.h | 2 +- developer/src/kmcmplib/src/CheckFilenameConsistency.cpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmcmplib/src/CompilerInterfaces.cpp | 3 +++ developer/src/kmcmplib/src/meson.build | 5 ----- developer/src/kmcmplib/tests/api-test.cpp | 2 +- developer/src/kmcmplib/tests/meson.build | 2 +- 8 files changed, 12 insertions(+), 14 deletions(-) diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index b27440fd06..3c6df80e7e 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -120,7 +120,7 @@ export class KmnCompiler { } private loadFileCallback = (filename: string, baseFilename: string, buffer: number, bufferSize: number): number => { - // TODO: we can optimize this in future by avoiding loading the file twice + // TODO: we can optimize this in future by avoiding loading the file twice #8885 let resolvedFilename = this.callbacks.resolveFilename(baseFilename, filename); let data = this.callbacks.loadFile(resolvedFilename); if(!data) { @@ -133,7 +133,7 @@ export class KmnCompiler { } if(bufferSize != data.byteLength) { - // TODO: consider chucking a wobbly because this is a bug + // TODO: consider chucking a wobbly because this is a bug #8885 /* c8 ignore next 2 */ return 0; } @@ -195,7 +195,7 @@ export class KmnCompiler { reader.validate(kvks, this.callbacks.loadSchema('kvks')); } catch(e) { console.log(e); - // TODO: also unit test + // TODO: also unit test #8886 // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } @@ -203,7 +203,7 @@ export class KmnCompiler { let vk = reader.transform(kvks, errors); if(!vk || errors.length) { console.dir(errors); - // TODO: also unit test + // TODO: also unit test #8886 // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index 5c2ae46fda..0b41af6990 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -35,7 +35,7 @@ struct KMCMP_COMPILER_RESULT { std::string kvksFilename; }; -// TODO: parameters in UTF-8 +// TODO: parameters in UTF-8 #8887 typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, const char* szText, void* context); // parameters in UTF-8 diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp index 3a185bc403..6d113d116b 100644 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp +++ b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp @@ -24,7 +24,7 @@ KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissin KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile) { // TODO: we no longer have filesystem access here. We could move this check to // kmc itself, and make it consistent across all compilers that use the same - // loader callback + // loader callback -- see #8883 return CERR_None; #if 0 diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 91e02e14e7..49484b1acc 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -3428,7 +3428,7 @@ bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) result = converter.from_bytes((char*)infile, (char*)infile+sz); } catch(std::range_error e) { UErrorCode status = U_ZERO_ERROR; - // TODO: we need ICU data files here @srl295 plz help! + // TODO: we need ICU data files here #8884 UConverter* conv = ucnv_open("windows-1252", &status); if(U_FAILURE(status)) { return FALSE; diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index 1a4e77f8e8..16b5b98654 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -13,6 +13,9 @@ bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); #ifdef __EMSCRIPTEN__ +// TODO: move emscripten wrappers into their own .cpp. Also move CompileKeyboardHandle +// into its own .cpp, so CompilerInterfaces.cpp has only C public API functions listed. +// #8889 /* WASM interface for compiler message callback diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index a81efac562..e697a0dced 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -29,11 +29,6 @@ if cpp_compiler.get_id() == 'emscripten' flags += ['-fwasm-exceptions'] lib_links = ['--whole-archive', '-sMODULARIZE', '-sEXPORT_ES6'] links += ['-fwasm-exceptions', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] - # tests are building as ES6 so we need to declare the file extension - # note that meson currently struggles with the sanitycheckc_cross.exe - # program, because it has a hard coded extension (.exe) which is not - # valid for node programs in module mode. - # name_suffix = '.mjs' endif icu = subproject('icu-for-uset', default_options: [ 'default_library=static', 'cpp_std=c++17', 'warning_level=0', 'werror=false']) diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index cdbabea4d5..cb0337caef 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -42,7 +42,7 @@ void setup() { /* TODO: tests to run: - 4. ANSI (no BOM of course) + 4. ANSI (no BOM of course) #8884 8. file without blank last line (cannot compare with fixture due to bug in kmcmpdll...) Hint to add: k004_ansi.kmn: Hint: 10A6 Keyman Developer has detected that the file has ANSI encoding. Consider converting this file to UTF-8 */ diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index cc51189a63..0d4c078ed3 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -84,7 +84,7 @@ valid_keyboard_tests = [ 'k001_utf16', 'k002_utf8_without_bom', 'k003_utf8_with_bom', - # 'k004_ansi', # TODO: enable ansi test when we have the icu datafiles + # 'k004_ansi', # TODO: enable ansi test when we have the icu datafiles #8884 'k005_bitmap', 'k006_icon', 'k007_includecodes_r_n', -- GitLab From edfa31e7c8e034cedf438a36cd8978ec4f66329e Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 31 May 2023 10:09:28 +0700 Subject: [PATCH 311/386] fix(web): stops creation of empty subdir --- web/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/ci.sh b/web/ci.sh index e3b494f8db..957735d717 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -116,7 +116,7 @@ if builder_start_action prepare:s.keyman.com; then # The main build products are expected to reside at the root of this folder. BASE_PUBLISH_FOLDER="$S_KEYMAN_COM/kmw/engine/$VERSION" echo "FOLDER: $BASE_PUBLISH_FOLDER" - mkdir -p "$BASE_PUBLISH_FOLDER/resources" + mkdir -p "$BASE_PUBLISH_FOLDER" # s.keyman.com - release-config only. It's notably smaller, thus far more favorable # for distribution via cloud service. -- GitLab From 7ccd8bed4f016da38c9efd8d4fd8ac685e08372c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 10:23:42 +0700 Subject: [PATCH 312/386] chore(developer): fix npm pack for kmc-ldml --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index 20d7ce98c4..75d9e1abef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1030,6 +1030,7 @@ } }, "developer/src/kmc-ldml": { + "name": "@keymanapp/kmc-ldml", "license": "MIT", "dependencies": { "@keymanapp/keyman-version": "*", -- GitLab From 1fb81907266fc139823fd121f6c7c45aa290632e Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 24 May 2023 15:44:14 +0700 Subject: [PATCH 313/386] fix(web): restores bulk_rendering tool --- .../testing/bulk_rendering/build-bundler.js | 25 + web/src/tools/testing/bulk_rendering/build.sh | 5 +- .../testing/bulk_rendering/renderer_core.ts | 477 +++++++++--------- .../testing/bulk_rendering/tsconfig.json | 19 +- 4 files changed, 280 insertions(+), 246 deletions(-) create mode 100644 web/src/tools/testing/bulk_rendering/build-bundler.js diff --git a/web/src/tools/testing/bulk_rendering/build-bundler.js b/web/src/tools/testing/bulk_rendering/build-bundler.js new file mode 100644 index 0000000000..b7a10a0223 --- /dev/null +++ b/web/src/tools/testing/bulk_rendering/build-bundler.js @@ -0,0 +1,25 @@ +/* + * Note: while this file is not meant to exist long-term, it provides a nice + * low-level proof-of-concept for esbuild bundling of the various Web submodules. + * + * Add some extra code at the end of src/index.ts and run it to verify successful bundling! + */ + +import esbuild from 'esbuild'; +import { spawn } from 'child_process'; + +await esbuild.build({ + bundle: true, + sourcemap: true, + format: "iife", + nodePaths: [ + '../../../../build/tools/testing/bulk_rendering/obj' + ], + entryPoints: { + 'index': '../../../../build/tools/testing/bulk_rendering/obj/renderer_core.js', + }, + outfile: '../../../../build/tools/testing/bulk_rendering/lib/bulk_render.js', + tsconfig: './tsconfig.json', + target: "es5", + treeShaking: true +}); diff --git a/web/src/tools/testing/bulk_rendering/build.sh b/web/src/tools/testing/bulk_rendering/build.sh index 951eaeb56c..3ef69ff63b 100755 --- a/web/src/tools/testing/bulk_rendering/build.sh +++ b/web/src/tools/testing/bulk_rendering/build.sh @@ -20,6 +20,8 @@ cd "$THIS_SCRIPT_PATH" ################################ Main script ################################ builder_describe \ + "@/web/src/app/browser build" \ + "@/web/src/app/ui build" \ "Build the bulk renderer project. The bulk renderer loads all the cloud keyboards from api.keyman.com and renders each of them to a document." \ "clean" \ "configure runs 'npm ci' on root folder" \ @@ -27,7 +29,7 @@ builder_describe \ builder_describe_outputs \ configure /node_modules \ - build /web/build/tools/testing/bulk_rendering/bulk_render.js + build /web/build/tools/testing/bulk_rendering/lib/bulk_render.js builder_parse "$@" @@ -43,5 +45,6 @@ fi if builder_start_action build; then tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" $builder_verbose + node build-bundler.js builder_finish_action success build fi diff --git a/web/src/tools/testing/bulk_rendering/renderer_core.ts b/web/src/tools/testing/bulk_rendering/renderer_core.ts index adcbc45069..d663717f17 100644 --- a/web/src/tools/testing/bulk_rendering/renderer_core.ts +++ b/web/src/tools/testing/bulk_rendering/renderer_core.ts @@ -1,296 +1,297 @@ // Includes KeymanWeb's Device class, as it's quite a useful resource for KMW-related projects. +import { Device } from 'keyman/engine/device-detect'; -type KeyboardMap = {[id: string]: any}; +import type { KeymanEngine } from 'keyman/app/browser'; +import type { FloatingOSKView } from 'keyman/engine/osk'; -namespace com.keyman.renderer { - export class BatchRenderer { - static divMaster: HTMLDivElement; - static dummy: HTMLInputElement; - static captureStream: any; - static video: any; - static boundingRect: DOMRect; - static allLayers = false; - static keyboardMatch: RegExp; - - async startCapture() { - let captureStream = null; - - try { - captureStream = await (navigator.mediaDevices as any).getDisplayMedia({ - audio: false, - video: { - width: screen.width, - height: screen.height, - frameRate: 60, - } - }); - } catch(err) { - console.error("Error: " + err); - } +declare var keyman: KeymanEngine; - const video = document.createElement("video"); - video.srcObject = captureStream; - video.play(); +type KeyboardMap = {[id: string]: any}; - const result = await new Promise((resolve, reject) => { - video.onloadedmetadata = function () { - resolve(); +export class BatchRenderer { + static divMaster: HTMLDivElement; + static dummy: HTMLInputElement; + static captureStream: any; + static video: any; + static boundingRect: DOMRect; + static allLayers = false; + static keyboardMatch: RegExp; + + async startCapture() { + let captureStream = null; + + try { + captureStream = await (navigator.mediaDevices as any).getDisplayMedia({ + audio: false, + video: { + width: screen.width, + height: screen.height, + frameRate: 60, } }); - - return {captureStream: captureStream, video: video}; + } catch(err) { + console.error("Error: " + err); } - // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. - private filterKeyboards(): KeyboardMap { - let keyman = window['keyman']; + const video = document.createElement("video"); + video.srcObject = captureStream; + video.play(); + + const result = await new Promise((resolve, reject) => { + video.onloadedmetadata = function () { + resolve(); + } + }); - let kbds = keyman['getKeyboards'](); + return {captureStream: captureStream, video: video}; + } - let keyboardMap = []; + // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. + private filterKeyboards(): KeyboardMap { + let kbds = keyman.getKeyboards(); - for(var i = 0; i < kbds.length; i++) { - let id: string = kbds[i]['InternalName']; - if(!id.match(BatchRenderer.keyboardMatch)) continue; - if(id.match(/^Keyboard_bod/)) continue; // bod keyboards currently don't load on Chrome and break the test run; they need rebuild. - if(keyboardMap[id]) { - continue; - } else { - keyboardMap[id] = kbds[i]; - } - } + let keyboardMap = []; - return keyboardMap; + for(var i = 0; i < kbds.length; i++) { + let id: string = kbds[i].InternalName; + if(!id.match(BatchRenderer.keyboardMatch)) continue; + if(id.match(/^Keyboard_bod/)) continue; // bod keyboards currently don't load on Chrome and break the test run; they need rebuild. + if(keyboardMap[id]) { + continue; + } else { + keyboardMap[id] = kbds[i]; + } } - private render(ele: HTMLElement, isMobile?: boolean): Promise { - const capture = async () => { - const result = await new Promise((resolve, reject) => { - // from: https://github.com/kasprownik/electron-screencapture/blob/master/index.js - const canvas = document.createElement('canvas'); - canvas.width = BatchRenderer.boundingRect.width; - canvas.height = BatchRenderer.boundingRect.height; - const context = canvas.getContext('2d'); - // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement - context.drawImage(BatchRenderer.video, - BatchRenderer.boundingRect.left, - BatchRenderer.boundingRect.top, - canvas.width, canvas.height, - 0, 0, canvas.width, canvas.height); - - const frame = canvas.toDataURL("image/png"); - let imgOut = document.createElement('img'); - imgOut.src = frame; - - resolve(imgOut); - }); + return keyboardMap; + } - return result; - } + private render(ele: HTMLElement, isMobile?: boolean): Promise { + const capture = async () => { + const result = await new Promise((resolve, reject) => { + // from: https://github.com/kasprownik/electron-screencapture/blob/master/index.js + const canvas = document.createElement('canvas'); + canvas.width = BatchRenderer.boundingRect.width; + canvas.height = BatchRenderer.boundingRect.height; + const context = canvas.getContext('2d'); + // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement + context.drawImage(BatchRenderer.video, + BatchRenderer.boundingRect.left, + BatchRenderer.boundingRect.top, + canvas.width, canvas.height, + 0, 0, canvas.width, canvas.height); + + const frame = canvas.toDataURL("image/png"); + let imgOut = document.createElement('img'); + imgOut.src = frame; + + resolve(imgOut); + }); - return capture(); + return result; } - createKeyboardHeader(kbd, loaded: boolean): HTMLDivElement { - let divHeader = document.createElement('div'); - let eleName = document.createElement('h2'); - - eleName.textContent = 'ID: ' + kbd['InternalName']; - divHeader.appendChild(eleName); + return capture(); + } - let eleDescription = document.createElement('p'); + createKeyboardHeader(kbd, loaded: boolean): HTMLDivElement { + let divHeader = document.createElement('div'); + let eleName = document.createElement('h2'); - if(loaded) { + eleName.textContent = 'ID: ' + kbd['InternalName']; + divHeader.appendChild(eleName); - eleDescription.appendChild(document.createTextNode('Name: ' + kbd['Name'])); - eleDescription.appendChild(document.createElement('br')); - eleDescription.appendChild(document.createTextNode('Font: ' + window['keyman'].core.activeKeyboard._legacyLayoutSpec.F)); + let eleDescription = document.createElement('p'); - } else { - eleDescription.appendChild(document.createTextNode('Unable to load this keyboard!')); - } + if(loaded) { - divHeader.appendChild(eleDescription); + eleDescription.appendChild(document.createTextNode('Name: ' + kbd['Name'])); + eleDescription.appendChild(document.createElement('br')); + eleDescription.appendChild(document.createTextNode('Font: ' + window['keyman'].core.activeKeyboard._legacyLayoutSpec.F)); - return divHeader; + } else { + eleDescription.appendChild(document.createTextNode('Unable to load this keyboard!')); } - private processKeyboard(kbd) { - let keyman = window['keyman']; - let p: Promise = keyman.setActiveKeyboard(kbd['InternalName']); - let isMobile = keyman.util.device.formFactor != 'desktop'; + divHeader.appendChild(eleDescription); - // Establish common keyboard header info. - let divSummary = document.createElement('div'); - // Establishes a linkable target for this keyboard's data. - divSummary.id = "summary-" + kbd['InternalName']; + return divHeader; + } - BatchRenderer.divMaster.insertAdjacentElement('afterbegin', divSummary); + private processKeyboard(kbd) { + let p: Promise = keyman.setActiveKeyboard(kbd['InternalName']); + let isMobile = keyman.config.hostDevice.formFactor != 'desktop'; - // A nice, closure-friendly reference for use in our callbacks. - let renderer = this; + // Establish common keyboard header info. + let divSummary = document.createElement('div'); + // Establishes a linkable target for this keyboard's data. + divSummary.id = "summary-" + kbd['InternalName']; - // Once the keyboard's loaded, we can really get started. - return p.then(function() { - let box: HTMLDivElement = keyman.osk._Box; + BatchRenderer.divMaster.insertAdjacentElement('afterbegin', divSummary); - BatchRenderer.boundingRect = box.getBoundingClientRect(); + // A nice, closure-friendly reference for use in our callbacks. + let renderer = this; - // Appromixate handling for non-fullscreen mode runs. - // Adjusts for the browser window's title bars, address bars, etc. - const screenOffsetY = window.outerHeight - window.innerHeight; - BatchRenderer.boundingRect.y += screenOffsetY; + // Once the keyboard's loaded, we can really get started. + return p.then(function() { + let box: HTMLDivElement = keyman.osk._Box; - divSummary.appendChild(renderer.createKeyboardHeader(kbd, true)); + BatchRenderer.boundingRect = box.getBoundingClientRect(); - let divRenders = document.createElement('div'); - divSummary.appendChild(divRenders); + // Appromixate handling for non-fullscreen mode runs. + // Adjusts for the browser window's title bars, address bars, etc. + const screenOffsetY = window.outerHeight - window.innerHeight; + BatchRenderer.boundingRect.y += screenOffsetY; - // Uses 'private' APIs that may be subject to change in the future. Keep it updated! - var layers; - if(isMobile) { - layers = keyman.osk.vkbd.layerGroup.layers; - } else { - // The desktop OSK will be overpopulated, with a number of blank layers to display in most cases. - // We instead rely upon the KLS definition to ensure we keep the renders sparse. - layers = keyman.core.activeKeyboard._legacyLayoutSpec.KLS; - } + divSummary.appendChild(renderer.createKeyboardHeader(kbd, true)); - let renderLayer = function(i: number) { - return new Promise(function(resolve) { - // (Private API) Directly sets the keyboard layer within KMW, then uses .show to force-display it. - if(keyman.osk.vkbd) { - keyman.core.keyboardProcessor.layerId = Object.keys(layers)[i]; - } else { - console.error("Error - keyman.osk.vkbd is undefined!"); - } - // Make sure the active element's still set! - renderer.setActiveDummy(); - keyman.osk.show(true); - - (document as any).fonts.ready.then(function() { - window.setTimeout(function() { - renderer.render(box, isMobile).then(function(imgEle: HTMLImageElement) { - let eleLayer = document.createElement('div'); - let eleLayerId = document.createElement('p'); - eleLayerId.textContent = 'Layer ID: ' + Object.keys(layers)[i]; - - eleLayer.appendChild(eleLayerId); - eleLayer.appendChild(imgEle); - eleLayer.appendChild(document.createElement('br')); - - divRenders.appendChild(eleLayer); - resolve(i); - }); - }, 100); - }); + let divRenders = document.createElement('div'); + divSummary.appendChild(divRenders); + + // Uses 'private' APIs that may be subject to change in the future. Keep it updated! + var layers; + if(isMobile) { + layers = keyman.osk.vkbd.layerGroup.layers; + } else { + // The desktop OSK will be overpopulated, with a number of blank layers to display in most cases. + // We instead rely upon the KLS definition to ensure we keep the renders sparse. + // + // _legacyLayoutSpec is technically private, but it's what we've been using, so... yeah. + layers = keyman.core.activeKeyboard['_legacyLayoutSpec'].KLS; + } + + let renderLayer = function(i: number) { + return new Promise(function(resolve) { + // (Private API) Directly sets the keyboard layer within KMW, then uses .show to force-display it. + if(keyman.osk.vkbd) { + keyman.core.keyboardProcessor.layerId = Object.keys(layers)[i]; + } else { + console.error("Error - keyman.osk.vkbd is undefined!"); + } + // Make sure the active element's still set! + renderer.setActiveDummy(); + keyman.osk.show(true); + + (document as any).fonts.ready.then(function() { + window.setTimeout(function() { + renderer.render(box, isMobile).then(function(imgEle: HTMLImageElement) { + let eleLayer = document.createElement('div'); + let eleLayerId = document.createElement('p'); + eleLayerId.textContent = 'Layer ID: ' + Object.keys(layers)[i]; + + eleLayer.appendChild(eleLayerId); + eleLayer.appendChild(imgEle); + eleLayer.appendChild(document.createElement('br')); + + divRenders.appendChild(eleLayer); + resolve(i); + }); + }, 100); }); - }; + }); + }; + + // The resulting Promise will only call it's `.then()` once all of this keyboard's renders have been completed. + return renderer.arrayPromiseIteration(renderLayer, Object.keys(layers).length); + }).catch(function() { + console.log("Failed to load the \"" + kbd['InternalName'] + "\" keyboard for rendering!"); + divSummary.appendChild(renderer.createKeyboardHeader(kbd, false)); + return Promise.resolve(); + }); + } - // The resulting Promise will only call it's `.then()` once all of this keyboard's renders have been completed. - return renderer.arrayPromiseIteration(renderLayer, Object.keys(layers).length); - }).catch(function() { - console.log("Failed to load the \"" + kbd['InternalName'] + "\" keyboard for rendering!"); - divSummary.appendChild(renderer.createKeyboardHeader(kbd, false)); + // Synchronously performs asynchronous operations across a loop, one at a time. + // Necessary due to the nature of KMW OSK rendering. + private arrayPromiseIteration(promiseGenerator: (i: number) => Promise, length: number): Promise { + let iteration = function(index: number): Promise { + if(index < length) { + var promise = promiseGenerator(index); + return promise.then(function(index: number) { + return iteration(++index); + }) + } else { return Promise.resolve(); - }); - } - - // Synchronously performs asynchronous operations across a loop, one at a time. - // Necessary due to the nature of KMW OSK rendering. - private arrayPromiseIteration(promiseGenerator: (i: number) => Promise, length: number): Promise { - let iteration = function(index: number): Promise { - if(index < length) { - var promise = promiseGenerator(index); - return promise.then(function(index: number) { - return iteration(++index); - }) - } else { - return Promise.resolve(); - } } - - return iteration(0); } - fillDeviceNotes() { - let description = document.createElement('p'); - let device = new com.keyman.Device(); - device.detect(); + return iteration(0); + } - description.appendChild(document.createTextNode('Browser: ' + device.browser)); - description.appendChild(document.createElement('br')); - description.appendChild(document.createTextNode('OS: ' + device.OS)); - description.appendChild(document.createElement('br')); - description.appendChild(document.createTextNode('Form factor: ' + device.formFactor)); - description.appendChild(document.createElement('br')); - description.appendChild(document.createTextNode('Touchable: ' + device.touchable)); + fillDeviceNotes() { + let description = document.createElement('p'); + let device = new Device(); + device.detect(); - document.getElementById('deviceNotes').appendChild(description); - } + description.appendChild(document.createTextNode('Browser: ' + device.browser)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('OS: ' + device.OS)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('Form factor: ' + device.formFactor)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('Touchable: ' + device.touchable)); - setActiveDummy() { - window['keyman'].domManager.activeElement = BatchRenderer.dummy; - } + document.getElementById('deviceNotes').appendChild(description); + } - async run(allLayers, filter) { - BatchRenderer.allLayers = allLayers; - BatchRenderer.keyboardMatch = new RegExp('^Keyboard_('+filter+')', 'i'); - if(window['keyman']) { - let keyman = window['keyman']; - - let cc = await this.startCapture(); - BatchRenderer.captureStream = cc.captureStream; - BatchRenderer.video = cc.video; - - // Establish a 'dummy' element to bypass the 'nothing's active' check KMW usually uses. - BatchRenderer.dummy = document.createElement('input'); - window['keyman'].attachToControl(BatchRenderer.dummy); - this.setActiveDummy(); - - BatchRenderer.divMaster = document.getElementById('renderList'); - if(BatchRenderer.divMaster.childElementCount > 0) { - console.log("Prior bulk-renderer run detected. Terminating execution."); - return; - } + setActiveDummy() { + keyman.setActiveElement(BatchRenderer.dummy, true); + } - // We want the renderer to control where the keyboard is displayed. - // Also bypasses another 'fun' OSK complication. - if(keyman.util.device.formFactor == 'desktop') { - keyman.osk.userPositioned = true; - } + async run(allLayers, filter) { + BatchRenderer.allLayers = allLayers; + BatchRenderer.keyboardMatch = new RegExp('^Keyboard_('+filter+')', 'i'); + if(window['keyman']) { + let cc = await this.startCapture(); + BatchRenderer.captureStream = cc.captureStream; + BatchRenderer.video = cc.video; + + // Establish a 'dummy' element to bypass the 'nothing's active' check KMW usually uses. + BatchRenderer.dummy = document.createElement('input'); + keyman.attachToControl(BatchRenderer.dummy); + this.setActiveDummy(); + + BatchRenderer.divMaster = document.getElementById('renderList'); + if(BatchRenderer.divMaster.childElementCount > 0) { + console.log("Prior bulk-renderer run detected. Terminating execution."); + return; + } - // Assumes that the keyboards have been preloaded for us. - let kbds = this.filterKeyboards(); + // We want the renderer to control where the keyboard is displayed. + // Also bypasses another 'fun' OSK complication. + if(keyman.config.hostDevice.formFactor == 'desktop') { + (keyman.osk as FloatingOSKView).userPositioned = true; + } - console.log("Unique keyboard ids detected: " + Object.keys(kbds).length); + // Assumes that the keyboards have been preloaded for us. + let kbds = this.filterKeyboards(); - let renderer = this; + console.log("Unique keyboard ids detected: " + Object.keys(kbds).length); - let keyboardIterator = function(i) { - return new Promise(function(resolve) { - renderer.processKeyboard(kbds[Object.keys(kbds)[i]]).then(function () { - //console.log("Keyboard " + i + " processed!"); - resolve(i); - }); + let renderer = this; + + let keyboardIterator = function(i) { + return new Promise(function(resolve) { + renderer.processKeyboard(kbds[Object.keys(kbds)[i]]).then(function () { + //console.log("Keyboard " + i + " processed!"); + resolve(i); }); - }; + }); + }; - this.arrayPromiseIteration(keyboardIterator, Object.keys(kbds).length).then(function() { - // Once all renders are done, we can now tidy the page up and prep it for final display + potential file-saving. + this.arrayPromiseIteration(keyboardIterator, Object.keys(kbds).length).then(function() { + // Once all renders are done, we can now tidy the page up and prep it for final display + potential file-saving. - // This will go at the top of the page when finished, but not when actively rendering. - // We want to leave as much space visible as possible when actively rendering keyboards - // so that auto-scrolling isn't an issue. - renderer.fillDeviceNotes(); - }); - } else { - console.error("KeymanWeb not detected!"); - } + // This will go at the top of the page when finished, but not when actively rendering. + // We want to leave as much space visible as possible when actively rendering keyboards + // so that auto-scrolling isn't an issue. + renderer.fillDeviceNotes(); + }); + } else { + console.error("KeymanWeb not detected!"); } } +} - (function(){ - window['kmw_renderer'] = new com.keyman.renderer.BatchRenderer(); - })(); -} \ No newline at end of file +(function(){ + window['kmw_renderer'] = new BatchRenderer(); +})(); \ No newline at end of file diff --git a/web/src/tools/testing/bulk_rendering/tsconfig.json b/web/src/tools/testing/bulk_rendering/tsconfig.json index b200d751f0..12d77f5389 100644 --- a/web/src/tools/testing/bulk_rendering/tsconfig.json +++ b/web/src/tools/testing/bulk_rendering/tsconfig.json @@ -3,11 +3,18 @@ "compilerOptions": { "allowJs": true, + "module": "es6", + "moduleResolution": "Node16", + "allowSyntheticDefaultImports": true, "inlineSources": true, - "module": "none", - "outFile": "../../../../build/tools/testing/bulk_rendering/bulk_render.js", "sourceMap": true, - "target": "es5" + "sourceRoot": "keyman", + "baseUrl": "./", + "outDir": "../../../../build/tools/testing/bulk_rendering/obj/", + "tsBuildInfoFile": "../../../../build/tools/testing/bulk_rendering/obj/tsconfig.tsbuildinfo", + "target": "es5", + "rootDir": ".", + "composite": true }, "files": [ @@ -15,9 +22,7 @@ ], "references": [ - { "path": "../../../../../common/web/utils", "prepend": true }, - { "path": "../../../../../common/web/keyman-version", "prepend": true }, - { "path": "../../../../../common/web/lm-message-types" }, - { "path": "../../../engine/device-detect", "prepend": true} + { "path": "../../../app/browser" }, + { "path": "../../../engine/device-detect" } ] } -- GitLab From d706c5c46aa431b780e22ff82f1e1afca320d79e Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 08:57:15 +0700 Subject: [PATCH 314/386] feat(web): adds filesize profiling to CI build logs --- web/ci.sh | 4 +++- web/common.inc.sh | 3 ++- web/src/app/browser/build-bundler.js | 31 ++++++++++++++++++++++++++-- web/src/app/browser/build.sh | 6 +++++- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index e3b494f8db..e457e9ef99 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -49,7 +49,9 @@ if builder_start_action build; then # - clean: make extra-sure that no prior build products exist. # - also useful when validating this script on a local dev machine! # - build: then do the ACTUAL build. - ./build.sh configure clean build + # one option: + # - --ci: For app/browser, outputs 'release' config filesize profiling logs + ./build.sh configure clean build --ci builder_finish_action success build fi diff --git a/web/common.inc.sh b/web/common.inc.sh index 6e5ad3c3f0..96a3c61bbd 100644 --- a/web/common.inc.sh +++ b/web/common.inc.sh @@ -20,11 +20,12 @@ compile ( ) { fi local COMPILE_TARGET="$1" + local BUNDLE_FLAG="${2:-}" tsc -b "${KEYMAN_ROOT}/web/src/$COMPILE_TARGET" -v if [ -f "./build-bundler.js" ]; then - node "./build-bundler.js" + node "./build-bundler.js" "$BUNDLE_FLAG" # So... tsc does declaration-bundling on its own pretty well, at least for local development. tsc --emitDeclarationOnly --outFile "${KEYMAN_ROOT}/web/build/$COMPILE_TARGET/lib/index.d.ts" -p "${KEYMAN_ROOT}/web/src/$COMPILE_TARGET" diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 3d855f92ed..19236a44e0 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -9,6 +9,26 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; import fs from 'fs'; +let EMIT_FILESIZE_PROFILE = false; + +if(process.argv.length > 2) { + for(let i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + + switch(arg) { + case '': + break; + case '--ci': + EMIT_FILESIZE_PROFILE=true + break; + // May add other options if desired in the future. + default: + console.error("Invalid command-line option set for script; only --ci is permitted."); + process.exit(1); + } + } +} + /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - * the `@class` emit comment-annotation is designed to facilitate tree-shaking for ES5-targeted @@ -45,7 +65,7 @@ await esbuild.build({ tsconfig: './tsconfig.json' }); -await esbuild.build({ +let result = await esbuild.build({ bundle: true, sourcemap: true, minifyWhitespace: true, @@ -60,9 +80,16 @@ await esbuild.build({ plugins: [ es5ClassAnnotationAsPurePlugin ], target: "es5", treeShaking: true, - tsconfig: './tsconfig.json' + tsconfig: './tsconfig.json', + // Enables source-file output size profiling! + metafile: EMIT_FILESIZE_PROFILE }); +if(EMIT_FILESIZE_PROFILE) { + // Profiles the sourcecode! + console.log(await esbuild.analyzeMetafile(result.metafile, { verbose: true })); +} + await esbuild.build({ bundle: true, sourcemap: true, diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index a578917dc9..63c24f2df8 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -41,7 +41,11 @@ builder_describe_outputs \ #### Build action definitions #### compile_and_copy() { - compile $SUBPROJECT_NAME + local COMPILE_FLAGS= + if builder_has_option --ci; then + COMPILE_FLAGS=--ci + fi + compile $SUBPROJECT_NAME $COMPILE_FLAGS mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk" cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/" -- GitLab From c7be7a1bcbcc406e400521e762931a906d58f9f1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 08:58:52 +0700 Subject: [PATCH 315/386] feat(common/models): profile for lm-worker pre-polyfill --- common/web/lm-worker/build-bundler.js | 74 +++++++++++++++++++++++++-- common/web/lm-worker/build.sh | 6 ++- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/common/web/lm-worker/build-bundler.js b/common/web/lm-worker/build-bundler.js index 44c03ec2f1..599bd4585a 100644 --- a/common/web/lm-worker/build-bundler.js +++ b/common/web/lm-worker/build-bundler.js @@ -7,6 +7,48 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; +import fs from 'fs'; + +/* + * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - + * the `@class` emit comment-annotation is designed to facilitate tree-shaking for ES5-targeted + * down-level emits. `esbuild` doesn't look for it by default... but we can override that with + * this plugin. + */ +let es5ClassAnnotationAsPurePlugin = { + name: '@class -> __PURE__', + setup(build) { + build.onLoad({filter: /\.js$/ }, async (args) => { + let source = await fs.promises.readFile(args.path, 'utf8'); + return { + // Marks any classes compiled by TS (as per the /** @class */ annotation) + // as __PURE__ in order to facilitate tree-shaking. + contents: source.replace('/** @class */', '/* @__PURE__ */ /** @class */'), + loader: 'js' + } + }); + } +} + +let EMIT_FILESIZE_PROFILE = false; + +if(process.argv.length > 2) { + for(let i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + + switch(arg) { + case '': + break; + case '--ci': + EMIT_FILESIZE_PROFILE=true + break; + // May add other options if desired in the future. + default: + console.error("Invalid command-line option set for script; only --ci is permitted."); + process.exit(1); + } + } +} await esbuild.build({ bundle: true, @@ -28,12 +70,13 @@ await esbuild.build({ }, outdir: 'build/lib', outExtension: { '.js': '.mjs' }, + plugins: [ es5ClassAnnotationAsPurePlugin ], tsconfig: 'tsconfig.json', - target: "es5" + target: "es5", }); // Bundled CommonJS (classic Node) module version -esbuild.buildSync({ +await esbuild.build({ bundle: true, sourcemap: true, format: "cjs", @@ -44,12 +87,12 @@ esbuild.buildSync({ }, outdir: 'build/lib', outExtension: { '.js': '.cjs' }, + plugins: [ es5ClassAnnotationAsPurePlugin ], tsconfig: 'tsconfig.json', target: "es5" }); -// Direct-use version -esbuild.buildSync({ +const embeddedWorkerBuildOptions = { bundle: true, sourcemap: true, format: "iife", @@ -58,6 +101,27 @@ esbuild.buildSync({ 'worker-main': 'build/obj/worker-main.js' }, outdir: 'build/lib', + plugins: [ es5ClassAnnotationAsPurePlugin ], tsconfig: 'tsconfig.json', target: "es5" -}); +} + +// Direct-use version +await esbuild.build(embeddedWorkerBuildOptions); + +if(EMIT_FILESIZE_PROFILE) { + // We want a specialized bundle build here instead; no output, but minified like + // the actual worker. + const minifiedProfilingOptions = { + ...embeddedWorkerBuildOptions, + minify: true, + metafile: true, + write: false // don't actually write the file. + } + + let result = await esbuild.build(minifiedProfilingOptions); + + console.log("Minified, pre-polyfill worker filesize profile:"); + // Profiles the sourcecode! + console.log(await esbuild.analyzeMetafile(result.metafile, { verbose: true })); +} diff --git a/common/web/lm-worker/build.sh b/common/web/lm-worker/build.sh index 91322527e6..9e544501cb 100755 --- a/common/web/lm-worker/build.sh +++ b/common/web/lm-worker/build.sh @@ -56,9 +56,13 @@ if builder_start_action build; then # Build worker with tsc first tsc -b $builder_verbose || builder_die "Could not build worker." + EXT_FLAGS= + if builder_has_option --ci; then + EXT_FLAGS=--ci + fi echo "Bundling worker modules" - node build-bundler.js + node build-bundler.js "$EXT_FLAGS" # Declaration bundling. npm run tsc -- --emitDeclarationOnly --outFile ./build/lib/index.d.ts -- GitLab From 5e93bb018920dffa80f19554fdb2825446e6f705 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 31 May 2023 15:51:50 +0700 Subject: [PATCH 316/386] feat(web): writes profile logs to file, outputs within new /web/build/profiling folder --- common/web/lm-worker/build-bundler.js | 27 ++++++++++++++++----------- web/src/app/browser/build-bundler.js | 9 +++++++-- web/src/app/browser/build.sh | 5 +++++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/common/web/lm-worker/build-bundler.js b/common/web/lm-worker/build-bundler.js index 599bd4585a..a1ec363cd9 100644 --- a/common/web/lm-worker/build-bundler.js +++ b/common/web/lm-worker/build-bundler.js @@ -109,19 +109,24 @@ const embeddedWorkerBuildOptions = { // Direct-use version await esbuild.build(embeddedWorkerBuildOptions); -if(EMIT_FILESIZE_PROFILE) { - // We want a specialized bundle build here instead; no output, but minified like - // the actual worker. - const minifiedProfilingOptions = { - ...embeddedWorkerBuildOptions, - minify: true, - metafile: true, - write: false // don't actually write the file. - } +// We want a specialized bundle build here instead; no output, but minified like +// the actual release worker. +const minifiedProfilingOptions = { + ...embeddedWorkerBuildOptions, + minify: true, + metafile: true, + write: false // don't actually write the file. +} - let result = await esbuild.build(minifiedProfilingOptions); +let result = await esbuild.build(minifiedProfilingOptions); +let filesizeProfile = await esbuild.analyzeMetafile(result.metafile, { verbose: true }); +fs.writeFileSync('build/filesize-profile.log', ` +// Minified Worker filesize profile, before polyfilling +${filesizeProfile} +`); +if(EMIT_FILESIZE_PROFILE) { console.log("Minified, pre-polyfill worker filesize profile:"); // Profiles the sourcecode! - console.log(await esbuild.analyzeMetafile(result.metafile, { verbose: true })); + console.log(filesizeProfile); } diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 19236a44e0..17a495e02a 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -82,12 +82,17 @@ let result = await esbuild.build({ treeShaking: true, tsconfig: './tsconfig.json', // Enables source-file output size profiling! - metafile: EMIT_FILESIZE_PROFILE + metafile: true }); +let filesizeProfile = await esbuild.analyzeMetafile(result.metafile, { verbose: true }); +fs.writeFileSync('../../../build/app/browser/filesize-profile.log', ` +// Minified Keyman Engine for Web ('app/browser' target), filesize profile +${filesizeProfile} +`); if(EMIT_FILESIZE_PROFILE) { // Profiles the sourcecode! - console.log(await esbuild.analyzeMetafile(result.metafile, { verbose: true })); + console.log(filesizeProfile); } await esbuild.build({ diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index 63c24f2df8..2cbc51fae5 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -52,6 +52,11 @@ compile_and_copy() { # Update the build/publish copy of our build artifacts prepare + + local PROFILE_DEST="$KEYMAN_ROOT/web/build/profiling/" + mkdir -p "$PROFILE_DEST" + cp "$KEYMAN_ROOT/web/build/app/browser/filesize-profile.log" "$PROFILE_DEST/web-engine-filesize.log" + cp "$KEYMAN_ROOT/common/web/lm-worker/build/filesize-profile.log" "$PROFILE_DEST/lm-worker-filesize.log" } builder_run_action configure verify_npm_setup -- GitLab From fa1d7f723c0a25175061979ee7cc881e688acca2 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 31 May 2023 15:54:12 +0700 Subject: [PATCH 317/386] docs(web): better comments in lm-worker build-bundler --- common/web/lm-worker/build-bundler.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/web/lm-worker/build-bundler.js b/common/web/lm-worker/build-bundler.js index a1ec363cd9..605ff3cd40 100644 --- a/common/web/lm-worker/build-bundler.js +++ b/common/web/lm-worker/build-bundler.js @@ -109,6 +109,9 @@ const embeddedWorkerBuildOptions = { // Direct-use version await esbuild.build(embeddedWorkerBuildOptions); +// ------------------------ + +// Now to generate a filesize profile for the minified version of the worker. // We want a specialized bundle build here instead; no output, but minified like // the actual release worker. const minifiedProfilingOptions = { -- GitLab From bb675730a2065eb11fbc7a7b3943a6ade06ef037 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 15:59:56 +0700 Subject: [PATCH 318/386] fix(common): tweak pack/publish support for npm 9.5.1 and node 18.16.0 The npm version command started failing when we updated to node 18.16.0, and it looks like it is related to the version numbers. This makes the pack file mangling idempotent and applies it to all package.json files in the repository. --- resources/build/build-utils-ci.inc.sh | 56 ++++++++++++++------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/resources/build/build-utils-ci.inc.sh b/resources/build/build-utils-ci.inc.sh index de36ae9e9b..fa72fd907b 100644 --- a/resources/build/build-utils-ci.inc.sh +++ b/resources/build/build-utils-ci.inc.sh @@ -100,18 +100,7 @@ function _builder_publish_npm_package() { dry_run=--dry-run fi - # We use --no-git-tag-version because our CI system controls version numbering - # and already tags releases. We also want to have the version of this match - # the release of Keyman Developer -- these two versions should be in sync. - # Because this is a large repo with multiple projects and build systems, it's - # better for us that individual build systems don't take too much ownership of - # git tagging. :) - npm version --allow-same-version --no-git-tag-version --no-commit-hooks "$VERSION_WITH_TAG" - - # Update all @keymanapp/* [*]dependencies in package.json to the current - # version-with-tag, so that the published version has precise dependencies, - # and we don't accidentally end up with either older or newer deps here - _builder_npm_set_dependency_version + _builder_write_npm_version # Note: In either case, npm publish MUST be given --access public to publish a # package in the @keymanapp scope on the public npm package index. @@ -128,18 +117,33 @@ function _builder_publish_npm_package() { fi } -# Updates all @keymanapp/* [*]dependencies in package.json to the current -# version-with-tag, so that the published version has precise dependencies, and -# we don't accidentally end up with either older or newer deps. This overwrites -# the local package.json, so it does need to be restored afterwards -function _builder_npm_set_dependency_version() { - cat package.json | "$JQ" --arg VERSION_WITH_TAG "$VERSION_WITH_TAG" \ - ' - . + - (try { dependencies: (.dependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + - (try { devDependencies: (.devDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + - (try { bundleDependencies: (.bundleDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + - (try { optionalDependencies: (.optionalDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) - ' > package1.json - mv -f package1.json package.json +function _builder_write_npm_version() { + # We use --no-git-tag-version because our CI system controls version numbering + # and already tags releases. We also want to have the version of this match + # the release of Keyman Developer -- these two versions should be in sync. + # Because this is a large repo with multiple projects and build systems, it's + # better for us that individual build systems don't take too much ownership of + # git tagging. :) + if ! "$JQ" -e '.version' package.json > /dev/null; then + pushd "$KEYMAN_ROOT" > /dev/null + npm version --allow-same-version --no-git-tag-version --no-commit-hooks --workspaces "$VERSION_WITH_TAG" + popd > /dev/null + fi + + # Updates all @keymanapp/* [*]dependencies in all package.jsons to the current + # version-with-tag, so that the published version has precise dependencies, and + # we don't accidentally end up with either older or newer deps. This overwrites + # the local package.json files, so they do need to be restored afterwards + find /c/Projects/keyman/app -name "package.json" -not -path '*/node_modules/*' -print0 | \ + while IFS= read -r -d '' line; do + cat "$line" | "$JQ" --arg VERSION_WITH_TAG "$VERSION_WITH_TAG" \ + ' + . + + (try { dependencies: (.dependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + + (try { devDependencies: (.devDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + + (try { bundleDependencies: (.bundleDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + + (try { optionalDependencies: (.optionalDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + ' > "${line}_" + mv -f "${line}_" "$line" + done } \ No newline at end of file -- GitLab From 4b0f4e9c51121f626b93ac90e652bcb80de56a12 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 19:12:20 +1000 Subject: [PATCH 319/386] chore: Update resources/build/build-utils-ci.inc.sh --- resources/build/build-utils-ci.inc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/build-utils-ci.inc.sh b/resources/build/build-utils-ci.inc.sh index fa72fd907b..cab449a463 100644 --- a/resources/build/build-utils-ci.inc.sh +++ b/resources/build/build-utils-ci.inc.sh @@ -134,7 +134,7 @@ function _builder_write_npm_version() { # version-with-tag, so that the published version has precise dependencies, and # we don't accidentally end up with either older or newer deps. This overwrites # the local package.json files, so they do need to be restored afterwards - find /c/Projects/keyman/app -name "package.json" -not -path '*/node_modules/*' -print0 | \ + find "$KEYMAN_ROOT" -name "package.json" -not -path '*/node_modules/*' -print0 | \ while IFS= read -r -d '' line; do cat "$line" | "$JQ" --arg VERSION_WITH_TAG "$VERSION_WITH_TAG" \ ' -- GitLab From fa66b4241022a71f6f45c361715d099433fe3058 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 31 May 2023 17:40:35 +0200 Subject: [PATCH 320/386] feat(linux): Add column for installation location Closes #8631. --- linux/keyman-config/keyman_config/view_installed.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index 1a378a106d..aaa5196fbd 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -166,6 +166,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): str, # version str, # packageID int, # enum InstallLocation (KmpArea is GObject version) + str, # InstallLocation path str, # path to welcome file if it exists or None str) # path to options file if it exists or None @@ -184,6 +185,9 @@ class ViewInstalledWindow(ViewInstalledWindowBase): # i18n: column header in table displaying installed keyboards column = Gtk.TreeViewColumn(_("Version"), renderer, text=2) self.tree.append_column(column) + # i18n: column header in table displaying installed keyboards + column = Gtk.TreeViewColumn(_("Location"), renderer, text=5) + self.tree.append_column(column) select = self.tree.get_selection() select.connect("changed", self.on_tree_selection_changed) @@ -282,6 +286,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): kmpdata['version'], kmpdata['packageID'], install_area, + get_keyman_dir(install_area).replace(os.path.expanduser('~'), '~'), welcome_file, options_file]) -- GitLab From bbdcb1d5df47d38bc286cda6ebdd6abc3607613b Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 31 May 2023 14:02:54 -0400 Subject: [PATCH 321/386] auto: increment master version to 17.0.115 --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index c05c241eff..ff0cc6fbbe 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 17.0.114 alpha 2023-05-31 + +* chore(developer): replace cwrap wasm bindings (#8857) +* chore(developer): refactor kmcmplib interfaces (#8870) +* chore(developer): move keyboard repo fixtures (#8874) +* chore(linux): Move build steps to build.sh (#8864) + ## 17.0.113 alpha 2023-05-26 * docs(android): Update documentation for building Android on Linux (#8860) diff --git a/VERSION.md b/VERSION.md index a3a3dc809e..38d1ece2d6 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.114 \ No newline at end of file +17.0.115 \ No newline at end of file -- GitLab From 980b893644536d322f4a5196b35425de0581d5f5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 30 May 2023 10:52:05 +0700 Subject: [PATCH 322/386] refactor(developer): move fs for kmn load to caller --- developer/src/kmcmplib/include/kmcmplibapi.h | 4 +- developer/src/kmcmplib/src/Compiler.cpp | 136 +++++++----------- .../src/kmcmplib/src/CompilerInterfaces.cpp | 89 ++++++++---- developer/src/kmcmplib/src/compfile.h | 29 ---- developer/src/kmcmplib/src/kmcmplib.h | 5 +- developer/src/kmcmplib/src/meson.build | 8 +- developer/src/kmcmplib/tests/api-test.cpp | 50 +++++-- developer/src/kmcmplib/tests/kmcompxtest.cpp | 33 ++++- developer/src/kmcmplib/tests/meson.build | 4 +- 9 files changed, 196 insertions(+), 162 deletions(-) diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index 4054d81e81..5c2ae46fda 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -36,7 +36,7 @@ struct KMCMP_COMPILER_RESULT { }; // TODO: parameters in UTF-8 -typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, char* szText, void* context); +typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, const char* szText, void* context); // parameters in UTF-8 // TODO typical usage: @@ -48,7 +48,7 @@ typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, char* szT // delete[] buf; // return error; // } -typedef bool (*kmcmp_LoadFileProc)(char* loadFilename, char* baseFilename, void* buffer, int* bufferSize); +typedef bool (*kmcmp_LoadFileProc)(const char* loadFilename, const char* baseFilename, void* buffer, int* bufferSize, void* context); // Parameters in UTF-8 EXTERN bool kmcmp_CompileKeyboard( diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index b09e36cbc1..8e0f94cb29 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -94,6 +94,9 @@ #include "CasedKeys.h" #include #include +#include +#include + #include "CheckFilenameConsistency.h" #include "UnreachableRules.h" #include "CheckForDuplicates.h" @@ -230,6 +233,8 @@ enum LinePrefixType { lptNone, lptKeymanAndKeymanWeb, lptKeymanWebOnly, lptKeyma /* Compile target */ kmcmp_CompilerMessageProc msgproc = NULL; +kmcmp_LoadFileProc loadfileproc = NULL; + void* msgprocContext = NULL; int kmcmp::currentLine = 0; @@ -3068,7 +3073,7 @@ KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataS return CERR_None; } -KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) +KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess) { KMX_DWORD len; PKMX_WCHAR p; @@ -3076,21 +3081,26 @@ KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) KMX_DWORD n; KMX_WCHAR currentQuotes = 0; KMX_WCHAR str[LINESIZE + 3]; - len = (KMX_DWORD)fread( str , 1 ,LINESIZE * 2,fp_in); - if (ferror(fp_in) ) return CERR_CannotReadInfile; - len /= 2; - str[len] = 0; auto cur = ftell(fp_in); - fseek(fp_in, 0, SEEK_END); - auto fsize = ftell(fp_in); - fseek(fp_in, cur, SEEK_SET); - if (cur == fsize) + if(offset >= sz) { + return CERR_EndOfFile; + } - // \r\n is still added here even though Linux doesn`t use \r. - // This is to ensure to still have a working windows-only-version - u16ncat(str, u"\r\n", _countof(str)); // I3481 // Always a "\r\n" to the EOF, avoids funny bugs + len = offset + LINESIZE*2 > sz ? sz-offset : LINESIZE*2; + memcpy(str, infile+offset, len); + offset += len; + len /= 2; + str[len] = 0; - if (len == 0) return CERR_EndOfFile; + if(offset == sz) { + // \r\n is still added here even though Linux doesn`t use \r. + // This is to ensure to still have a working windows-only-version + u16ncat(str, u"\r\n", _countof(str)); // I3481 // Always a "\r\n" to the EOF, avoids funny bugs + } + + if (len == 0) { + return CERR_EndOfFile; + } // neccessary to add this block for using on non-windows platforms (removes all \r for platforms that use \n instead of \r\n) for (p = str, n = 0; n < len; n++, p++) { @@ -3172,9 +3182,13 @@ KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) return (PreProcess ? CERR_None : CERR_LineTooLong); } - if (*p == L'\n') kmcmp::currentLine++; + kmcmp::currentLine++; - fseek(fp_in, -(int)(len * 2 - (int)(p - str) * 2 - 2), SEEK_CUR); + offset -= (int)(len * 2 - (int)(p - str) * 2 - 2); + if(offset >= sz) { + // If we've appended a \n, we can go past EOF + offset = sz; + } p--; while (p >= str && iswspace(*p)) p--; @@ -3427,81 +3441,39 @@ KMX_BOOL kmcmp::IsValidCallStore(PFILE_STORE fs) return i == 1; } -FILE* CreateTempFile() -{ - return tmpfile(); +/////////////////// + +bool hasPreamble(std::u16string result) { + return result.size() > 0 && result[0] == 0xFEFF; } -/////////////////// +bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) { + if(sz == 0) { + return FALSE; + } -FILE* UTF16TempFromUTF8(FILE* fp_in , KMX_BOOL hasPreamble) -{ - FILE *fp_out = CreateTempFile(); - if(fp_out == NULL) // I3228 // I3510 - { - fclose(fp_in); - return NULL; //return INVALID_HANDLE_VALUE; _S2 can I exchange that? + std::u16string result; + + try { + std::wstring_convert, char16_t> converter; + result = converter.from_bytes((char*)infile, (char*)infile+sz-1); + } catch(std::range_error e) { + std::wstring_convert, char16_t> converter; + result = converter.from_bytes((char*)infile, (char*)infile+sz-1); } - PKMX_BYTE buf, p; - PKMX_WCHAR outbuf, poutbuf; - KMX_DWORD len; - KMX_DWORD len2; - KMX_WCHAR prolog = 0xFEFF; - fwrite(&prolog,2, 1, fp_out); - - fseek(fp_in, 0, SEEK_END); - len = (KMX_DWORD)ftell(fp_in); - fseek(fp_in, 0, SEEK_SET); - if (hasPreamble) { - fseek( fp_in,3,SEEK_SET); // Cut off UTF-8 marker - len -= 3; - } - - buf = new KMX_BYTE[len + 1]; // null terminated - outbuf = new KMX_WCHAR[len + 1]; - - len2= (KMX_DWORD)fread(buf,1,len,fp_in); - if (len2) { - buf[len2] = 0; - p = buf; - poutbuf = outbuf; - if (hasPreamble) { - // We have a preamble, so we attempt to read as UTF-8 and allow conversion errors to be filtered. This is not great for a - // compiler but matches existing behaviour -- in future versions we may not do lenient conversion. - ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], lenientConversion); - fwrite(outbuf, (KMX_DWORD)(poutbuf - outbuf) * 2 , 1, fp_out); - } - else { - // No preamble, so we attempt to read as strict UTF-8 and fall back to ANSI if that fails - ConversionResult cr = ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], strictConversion); - if (cr == sourceIllegal) { - // Not a valid UTF-8 file, so fall back to ANSI - // AddCompileError(CHINT_NonUnicodeFile); - // note, while this message is defined, for now we will not emit it - // because we don't support HINT/INFO messages yet and we don't want - // this to cause a blocking compile at this stage - // do strtowstr only when no invalid characters are found - if( p==0){ - poutbuf = strtowstr((PKMX_STR)buf); - fwrite(poutbuf, (KMX_DWORD)u16len(poutbuf) * 2 , 1, fp_out); - delete[] poutbuf; - } - else - AddCompileError(CERR_InvalidCharacter); - } + if(hasPreamble(result)) { + *sz16 = result.size() * 2 - 1; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str() + 2, *sz16); - else { - fwrite(outbuf, (KMX_DWORD)(poutbuf - outbuf) * 2 , 1, fp_out); - } - } } - fclose( fp_in); - delete[] buf; - delete[] outbuf; - fseek( fp_out,2,SEEK_SET); - return fp_out; + *sz16 = result.size() * 2; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str(), *sz16); + + return TRUE; } PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID) diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index 71e1e895f2..fd297f4332 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -11,14 +11,14 @@ #include "../../../../common/windows/cpp/include/ConvertUTF.h" #include "../../../../common/windows/cpp/include/keymanversion.h" -bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk); +bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); #ifdef __EMSCRIPTEN__ /* WASM interface for compiler message callback */ -EM_JS(int, wasm_msgproc, (int line, int msgcode, char* text, char* context), { +EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context), { const proc = globalThis[UTF8ToString(context)]; if(!proc || typeof proc != 'function') { console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); @@ -28,7 +28,21 @@ EM_JS(int, wasm_msgproc, (int line, int msgcode, char* text, char* context), { } }); -int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, char* szText, void* context) { +EM_JS(bool, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int* bufferSize, char* context), { + const proc = globalThis[UTF8ToString(context)]; + if(!proc || typeof proc != 'function') { + return 0; + } else { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + } +}); + +bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buffer, int* bufferSize, void* context) { + char* msgProc = static_cast(context); + return wasm_loadfileproc(filename, baseFilename, buffer, bufferSize, msgProc); +} + +int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, void* context) { char* msgProc = static_cast(context); return wasm_msgproc(line, dwMsgCode, szText, msgProc); } @@ -61,7 +75,7 @@ WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPI pszInfile.c_str(), options, wasm_CompilerMessageProc, - nullptr, //wasm_LoadFileProc, + wasm_LoadFileProc, intf.messageCallback.c_str(), kr ); @@ -116,8 +130,6 @@ EXTERN bool kmcmp_CompileKeyboard( KMCMP_COMPILER_RESULT& result ) { - FILE* fp_in = NULL; - KMX_CHAR str[260]; FILE_KEYBOARD fk; kmcmp::FSaveDebug = options.saveDebug; // I3681 @@ -126,7 +138,7 @@ EXTERN bool kmcmp_CompileKeyboard( kmcmp::FShouldAddCompilerVersion = options.shouldAddCompilerVersion; kmcmp::CompileTarget = options.target; - if (!messageProc || !pszInfile) { // TODO: add loadFileProc + if (!messageProc || !loadFileProc || !pszInfile) { AddCompileError(CERR_BadCallParams); return FALSE; } @@ -142,43 +154,58 @@ EXTERN bool kmcmp_CompileKeyboard( } msgproc = messageProc; - //TODO: loadfileproc = loadFileProc; + loadfileproc = loadFileProc; msgprocContext = (void*)procContext; kmcmp::currentLine = 0; kmcmp::nErrors = 0; - fp_in = Open_File(pszInfile, "rb"); - - if (fp_in == NULL) { + int sz; + if(!loadFileProc(pszInfile, "", nullptr, &sz, msgprocContext)) { AddCompileError(CERR_InfileNotExist); return FALSE; } - // Transfer the file to a memory stream for processing UTF-8 or ANSI to UTF-16? - // What about really large files? Transfer to a temp file... - if (!fread(str, 1, 3, fp_in)) { - fclose(fp_in); + if(sz < 3) { + // Technically, a 3 byte file can never be a valid .kmn, so we can shortcut + // here and avoid testing outside memory bounds for looking at BOM AddCompileError(CERR_CannotReadInfile); return FALSE; } - fseek(fp_in, 0, SEEK_SET); - if (str[0] == UTF8Sig[0] && str[1] == UTF8Sig[1] && str[2] == UTF8Sig[2]) - fp_in = UTF16TempFromUTF8(fp_in, TRUE); - else if (str[0] == UTF16Sig[0] && str[1] == UTF16Sig[1]) - fseek(fp_in, 2, SEEK_SET); - else - fp_in = UTF16TempFromUTF8(fp_in, FALSE); - if (fp_in == NULL) { - AddCompileError(CERR_CannotCreateTempfile); + KMX_BYTE* infile = new KMX_BYTE[sz]; + if(!infile) { + AddCompileError(CERR_CannotAllocateMemory); return FALSE; } + if(!loadFileProc(pszInfile, "", infile, &sz, msgprocContext)) { + delete[] infile; + AddCompileError(CERR_CannotReadInfile); + return FALSE; + } + + int offset = 0; + if(infile[0] == (KMX_BYTE) UTF16Sig[0] && infile[1] == (KMX_BYTE) UTF16Sig[1]) { + // UTF-16 source file + offset = 2; + } else { + // UTF-8 source file + KMX_BYTE* infile16; + int sz16; + if(!UTF16TempFromUTF8(infile, sz, &infile16, &sz16)) { + delete[] infile; + AddCompileError(CERR_CannotCreateTempfile); + return FALSE; + } + delete[] infile; + infile = infile16; + sz = sz16; + } kmcmp::CodeConstants = new kmcmp::NamedCodeConstants; - bool success = CompileKeyboardHandle(fp_in, &fk); + bool success = CompileKeyboardHandle(infile+offset, sz-offset, &fk); delete kmcmp::CodeConstants; - fclose(fp_in); + delete[] infile; if (kmcmp::nErrors > 0 || !success) { return FALSE; @@ -204,7 +231,7 @@ EXTERN bool kmcmp_CompileKeyboard( return TRUE; } -bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) +bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) { PKMX_WCHAR str, p; @@ -263,8 +290,10 @@ bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) AddStore(fk, TSS_CUSTOMKEYMANEDITION, u"0"); AddStore(fk, TSS_CUSTOMKEYMANEDITIONNAME, u"Keyman"); + int offset = 0; + // must preprocess for group and store names -> this isn't really necessary, but never mind! - while ((msg = ReadLine(fp_in, str, TRUE)) == CERR_None) + while ((msg = ReadLine(infile, sz, offset, str, TRUE)) == CERR_None) { p = str; switch (LineTokenType(&p)) @@ -301,7 +330,7 @@ bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) return FALSE; } - fseek( fp_in,2,SEEK_SET); + offset = 0; kmcmp::currentLine = 0; /* Reindex the list of codeconstants after stores added */ @@ -309,7 +338,7 @@ bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) kmcmp::CodeConstants->reindex(); /* ReadLine will automatically skip over $Keyman lines, and parse wrapped lines */ - while ((msg = ReadLine(fp_in, str, FALSE)) == CERR_None) + while ((msg = ReadLine(infile, sz, offset, str, FALSE)) == CERR_None) { msg = ParseLine(fk, str); if (msg != CERR_None) { diff --git a/developer/src/kmcmplib/src/compfile.h b/developer/src/kmcmplib/src/compfile.h index 595d1fecec..7c262be090 100644 --- a/developer/src/kmcmplib/src/compfile.h +++ b/developer/src/kmcmplib/src/compfile.h @@ -197,33 +197,4 @@ struct COMPILEMESSAGES { typedef COMPILEMESSAGES *PCOMPILEMESSAGES; -/* -struct TVersion -{ - //int MinVersion; // 0x0500 usually - //int CompilerVersion[4]; - //int MinCompilerVersion[4]; - int KeyboardVersion; // 0x0500 usually -}; - -extern TVersion FVersionInfo; -*/ - -/* -#define bstrcpy(c,d) (LPBYTE)strcpy((LPSTR)(c),(LPSTR)(d)) -#define bstrlen(c) strlen((LPSTR)(c)) -#define bstrcmp(c,d) strcmp((LPSTR)(c),(LPSTR)(d)) -#define bstrncmp(c,d,n) strncmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrnicmp(c,d,n) strnicmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstricmp(c,d) stricmp((LPSTR)(c),(LPSTR)(d)) -#define bstrchr(c,ch) (LPBYTE)strchr((LPSTR)(c),(char)ch) -#define bstrncpy(c,d,n) (LPBYTE)strncpy((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrtok(c,d) (LPBYTE)strtok((LPSTR)(c),(LPSTR)(d)) -#define bstrcat(c,d) (LPBYTE)strcat((LPSTR)(c),(LPSTR)(d)) -#define bstrncat(c,d,n) (LPBYTE)strncat((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrrev(c) (LPBYTE)strrev((LPSTR)(c)) -#define batoi(c) atoi((LPSTR)(c)) -#define bstrtol(c,d,n) strtol((LPSTR)(c),(LPSTR *)(d),(n)) -*/ - #endif // _COMPFILE_H diff --git a/developer/src/kmcmplib/src/kmcmplib.h b/developer/src/kmcmplib/src/kmcmplib.h index 78e945ee0d..70718f98bc 100644 --- a/developer/src/kmcmplib/src/kmcmplib.h +++ b/developer/src/kmcmplib/src/kmcmplib.h @@ -24,6 +24,7 @@ namespace kmcmp { } extern kmcmp_CompilerMessageProc msgproc; +extern kmcmp_LoadFileProc loadfileproc; extern void* msgprocContext; extern KMX_BOOL AWarnDeprecatedCode_GLOBAL_LIB; @@ -40,10 +41,10 @@ KMX_BOOL AddCompileError(KMX_DWORD msg); PKMX_WCHAR strtowstr(PKMX_STR in); PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID); -FILE* UTF16TempFromUTF8(FILE* fp_in , KMX_BOOL hasPreamble); +bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16); KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataSize); KMX_DWORD AddStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, const KMX_WCHAR * str, KMX_DWORD *dwStoreID= NULL); -KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess); +KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess); KMX_DWORD ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str); KMX_DWORD ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p); KMX_DWORD ProcessGroupFinish(PFILE_KEYBOARD fk); diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 8264c3d28e..727d6831e6 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -7,6 +7,7 @@ # TODO: is this required? It should be Keyman Core only defns += ['-DKMN_KBP_EXPORTING'] version_res = [] +lib_links = [] if cpp_compiler.get_id() == 'gcc' or cpp_compiler.get_id() == 'clang' warns += [ @@ -24,12 +25,13 @@ endif name_suffix = [] if cpp_compiler.get_id() == 'emscripten' - links += ['-lnodefs.js', '-sMODULARIZE', '-sEXPORT_ES6', '--whole-archive', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] + lib_links = ['--whole-archive', '--bind', '-sMODULARIZE', '-sEXPORT_ES6'] + links += ['-lnodefs.js', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] # tests are building as ES6 so we need to declare the file extension # note that meson currently struggles with the sanitycheckc_cross.exe # program, because it has a hard coded extension (.exe) which is not # valid for node programs in module mode. - name_suffix = '.mjs' + # name_suffix = '.mjs' endif icu = subproject('icu-for-uset', default_options: [ 'default_library=static', 'cpp_std=c++17', 'warning_level=0', 'werror=false']) @@ -63,7 +65,7 @@ lib = library('kmcmplib', version_res, cpp_args: defns + warns + flags, - link_args: links, + link_args: links + lib_links, version: meson.project_version(), include_directories: inc, install: true, diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index 4bb0276a79..b347773220 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -18,13 +18,14 @@ #include #include "../src/compfile.h" #include +#include "../src/filesystem.h" void setup(); -void test_kmcmp_CompileKeyboard(); +void test_kmcmp_CompileKeyboard(char *kmn_file); std::vector error_vec; -int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { error_vec.push_back(dwMsgCode); const char*t = "unknown"; switch(dwMsgCode & 0xF000) { @@ -37,9 +38,42 @@ int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { return 1; } +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { + FILE* fp = Open_File(filename, "rb"); + if(!fp) { + return false; + } + + if(!data) { + // return size + if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + *size = ftell(fp); + if(*size == -1L) { + fclose(fp); + return false; + } + } else { + // return data + if(fread(data, 1, *size, fp) != *size) { + fclose(fp); + return false; + } + } + fclose(fp); + return true; +} + int main(int argc, char *argv[]) { + if(argc < 1) { + puts("Usage: api-test "); + puts("Warning: blank_keyboard will be overwritten"); + return 1; + } setup(); - test_kmcmp_CompileKeyboard(); + test_kmcmp_CompileKeyboard(argv[1]); return 0; } @@ -48,13 +82,9 @@ void setup() { error_vec.clear(); } -void test_kmcmp_CompileKeyboard() { - char kmn_file[L_tmpnam], kmx_file[L_tmpnam]; - tmpnam(kmn_file); - tmpnam(kmx_file); - +void test_kmcmp_CompileKeyboard(char *kmn_file) { // Create an empty file - FILE *fp = fopen(kmn_file, "w"); + FILE *fp = Open_File(kmn_file, "wb"); fclose(fp); // It should fail when a zero-byte file is passed in @@ -65,7 +95,7 @@ void test_kmcmp_CompileKeyboard() { options.warnDeprecatedCode = true; options.shouldAddCompilerVersion = false; options.target = CKF_KEYMAN; - assert(!kmcmp_CompileKeyboard(kmn_file, options, msgproc, nullptr, nullptr, result)); + assert(!kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)); assert(error_vec.size() == 1); assert(error_vec[0] == CERR_CannotReadInfile); diff --git a/developer/src/kmcmplib/tests/kmcompxtest.cpp b/developer/src/kmcmplib/tests/kmcompxtest.cpp index 87d9e53cf7..c5d189798b 100644 --- a/developer/src/kmcmplib/tests/kmcompxtest.cpp +++ b/developer/src/kmcmplib/tests/kmcompxtest.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "../src/filesystem.h" #ifdef _MSC_VER #else @@ -28,7 +29,7 @@ vector < int > error_vec; #define CERR_WARNING 0x00002000 #define CERR_HINT 0x00001000 -int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { error_vec.push_back(dwMsgCode); const char*t = "unknown"; @@ -42,6 +43,34 @@ int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) return 1; } +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { + FILE* fp = Open_File(filename, "rb"); + if(!fp) { + return false; + } + + if(!data) { + // return size + if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + *size = ftell(fp); + if(*size == -1L) { + fclose(fp); + return false; + } + } else { + // return data + if(fread(data, 1, *size, fp) != *size) { + fclose(fp); + return false; + } + } + fclose(fp); + return true; +} + #include "../src/filesystem.h" int main(int argc, char *argv[]) @@ -79,7 +108,7 @@ int main(int argc, char *argv[]) options.shouldAddCompilerVersion = false; options.target = CKF_KEYMAN; - if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, nullptr, nullptr, result)) { + if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)) { char* testname = strrchr( (char*) kmn_file, '/') + 1; if(strncmp(testname, pfirst5, 5) == 0){ return __LINE__; // exit code: CERR_ in Name + no Error found diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index 49924f2d78..2468ac53b1 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -113,10 +113,10 @@ apitest = executable('api-test', 'api-test.cpp', name_suffix: name_suffix, link_args: links + tests_flags, objects: lib.extract_all_objects(), - dependencies: icuuc_dep, + dependencies: icuuc_dep ) -test('api-test', apitest) +test('api-test', apitest, args: [output_path / 'blank_keyboard.kmx']) usetapitest = executable('uset-api-test', 'uset-api-test.cpp', cpp_args: defns, -- GitLab From 47c8a95fb0743d471eee87b90ee2353469e99ad2 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 07:44:35 +0700 Subject: [PATCH 323/386] refactor(developer): complete fs move out of kmcmplib * Moves filesystem access out of kmcmplib into kmc-kmn * Adds filesystem access callback to kmcmplib unit tests * Cleans up callback interface through wasm * Adds unit tests for various file load scenarios * Removes nodefs dependency from kmcmplib wasm build, and removes corresponding path mappings which were previously required for wasm builds; note that these are still present for the unit tests for kmcmplib. --- .../src/kmc-kmn/src/compiler/compiler.ts | 55 ++++++++-- .../kmcmplib/src/CheckFilenameConsistency.cpp | 47 ++------ .../kmcmplib/src/CheckFilenameConsistency.h | 2 - developer/src/kmcmplib/src/Compiler.cpp | 102 ++++++++---------- .../src/kmcmplib/src/CompilerInterfaces.cpp | 44 ++++---- .../src/kmcmplib/src/NamedCodeConstants.cpp | 97 ++++++----------- .../src/kmcmplib/src/NamedCodeConstants.h | 5 +- developer/src/kmcmplib/src/compfile.h | 3 +- developer/src/kmcmplib/src/kmcmplib.h | 1 - developer/src/kmcmplib/src/meson.build | 9 +- developer/src/kmcmplib/src/pch.h | 2 - developer/src/kmcmplib/tests/api-test.cpp | 53 ++------- .../valid-keyboards/compile_legacy.bat | 4 + .../fixtures/valid-keyboards/k001_utf16.kmn | Bin 0 -> 452 bytes .../fixtures/valid-keyboards/k001_utf16.kmx | Bin 0 -> 356 bytes .../valid-keyboards/k002_utf8_without_bom.kmn | 11 ++ .../valid-keyboards/k002_utf8_without_bom.kmx | Bin 0 -> 378 bytes .../valid-keyboards/k003_utf8_with_bom.kmn | 11 ++ .../valid-keyboards/k003_utf8_with_bom.kmx | Bin 0 -> 372 bytes .../fixtures/valid-keyboards/k004_ansi.kmn | 13 +++ .../fixtures/valid-keyboards/k004_ansi.kmx | Bin 0 -> 350 bytes .../fixtures/valid-keyboards/k005_bitmap.bmp | Bin 0 -> 246 bytes .../fixtures/valid-keyboards/k005_bitmap.kmn | 12 +++ .../fixtures/valid-keyboards/k005_bitmap.kmx | Bin 0 -> 656 bytes .../fixtures/valid-keyboards/k006_icon.ico | Bin 0 -> 318 bytes .../fixtures/valid-keyboards/k006_icon.kmn | 12 +++ .../fixtures/valid-keyboards/k006_icon.kmx | Bin 0 -> 728 bytes .../valid-keyboards/k007_includecodes_r_n.kmn | 12 +++ .../valid-keyboards/k007_includecodes_r_n.kmx | Bin 0 -> 462 bytes .../valid-keyboards/k007_includecodes_r_n.txt | 2 + .../valid-keyboards/k008_includecodes_n.kmn | 12 +++ .../valid-keyboards/k008_includecodes_n.kmx | Bin 0 -> 454 bytes .../valid-keyboards/k008_includecodes_n.txt | 2 + developer/src/kmcmplib/tests/kmcompxtest.cpp | 49 +-------- developer/src/kmcmplib/tests/meson.build | 41 +++++-- .../src/kmcmplib/tests/util_callbacks.cpp | 59 ++++++++++ developer/src/kmcmplib/tests/util_callbacks.h | 8 ++ .../util_filesystem.cpp} | 41 ++++++- .../filesystem.h => tests/util_filesystem.h} | 5 +- 39 files changed, 406 insertions(+), 308 deletions(-) create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.ico create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.txt create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt create mode 100644 developer/src/kmcmplib/tests/util_callbacks.cpp create mode 100644 developer/src/kmcmplib/tests/util_callbacks.h rename developer/src/kmcmplib/{src/filesystem.cpp => tests/util_filesystem.cpp} (84%) rename developer/src/kmcmplib/{src/filesystem.h => tests/util_filesystem.h} (82%) diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 606bec638c..b27440fd06 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -42,13 +42,16 @@ const baseOptions: CompilerOptions = { */ let callbackProcIdentifier = 0; +const + callbackPrefix = 'kmnCompilerCallbacks_'; + export class KmnCompiler { private Module: any; - callbackName: string; + callbackID: string; // a unique numeric id added to globals with prefixed names callbacks: CompilerCallbacks; constructor() { - this.callbackName = 'kmnCompilerCallback' + callbackProcIdentifier; + this.callbackID = callbackPrefix + callbackProcIdentifier.toString(); callbackProcIdentifier++; } @@ -58,6 +61,7 @@ export class KmnCompiler { try { this.Module = await loadWasmHost(); } catch(e: any) { + /* c8 ignore next 3 */ this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({e})); return false; } @@ -74,7 +78,9 @@ export class KmnCompiler { // Can't report a message here. throw Error('Must call Compiler.init(callbacks) before proceeding'); } - if(!this.Module) { // fail if wasm not loaded or function not found + if(!this.Module) { + /* c8 ignore next 4 */ + // fail if wasm not loaded or function not found this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({})); return false; } @@ -83,15 +89,17 @@ export class KmnCompiler { public run(infile: string, outfile: string, options?: CompilerOptions): boolean { if(!this.verifyInitialized()) { + /* c8 ignore next 2 */ return false; } options = {...baseOptions, ...options}; - (globalThis as any)[this.callbackName] = this.compilerMessageCallback; - // TODO: use callbacks for file access -- so kmc-kmn is entirely fs agnostic + (globalThis as any)[this.callbackID] = { + message: this.compilerMessageCallback, + loadFile: this.loadFileCallback + }; let result = this.runCompiler(infile, outfile, options); - delete (globalThis as any)[this.callbackName]; - //TODO: write the file out! + delete (globalThis as any)[this.callbackID]; if(result) { if(result.kmx) { this.callbacks.fs.writeFileSync(result.kmx.filename, result.kmx.data); @@ -111,6 +119,30 @@ export class KmnCompiler { return 1; } + private loadFileCallback = (filename: string, baseFilename: string, buffer: number, bufferSize: number): number => { + // TODO: we can optimize this in future by avoiding loading the file twice + let resolvedFilename = this.callbacks.resolveFilename(baseFilename, filename); + let data = this.callbacks.loadFile(resolvedFilename); + if(!data) { + return 0; + } + + if(buffer == 0) { + /* We need to return buffer size required */ + return data.byteLength; + } + + if(bufferSize != data.byteLength) { + // TODO: consider chucking a wobbly because this is a bug + /* c8 ignore next 2 */ + return 0; + } + + this.Module.HEAP8.set(data, buffer); + + return 1; + } + private runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { let result: CompilerResult = {}; let wasm_interface = new this.Module.CompilerInterface(); @@ -122,8 +154,7 @@ export class KmnCompiler { wasm_options.warnDeprecatedCode = options.warnDeprecatedCode; wasm_options.shouldAddCompilerVersion = options.shouldAddCompilerVersion; wasm_options.target = 0; //CKF_KEYMAN; TODO, support KMW - wasm_interface.messageCallback = this.callbackName; - wasm_interface.loadFileCallback = this.callbackName; // TODO: this is wrong, needs to be a new callback; not yet used though + wasm_interface.callbacksKey = this.callbackID; // key of object on globalThis wasm_result = this.Module.kmcmp_compile(infile, wasm_options, wasm_interface); if(!wasm_result.result) { return null; @@ -143,6 +174,7 @@ export class KmnCompiler { return result; } catch(e) { + /* c8 ignore next 3 */ this.callbacks.reportMessage(CompilerMessages.Fatal_UnexpectedException({e:e})); return null; } finally { @@ -163,6 +195,7 @@ export class KmnCompiler { reader.validate(kvks, this.callbacks.loadSchema('kvks')); } catch(e) { console.log(e); + // TODO: also unit test // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } @@ -170,6 +203,7 @@ export class KmnCompiler { let vk = reader.transform(kvks, errors); if(!vk || errors.length) { console.dir(errors); + // TODO: also unit test // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } @@ -188,10 +222,12 @@ export class KmnCompiler { */ public parseUnicodeSet(pattern: string, bufferSize: number) : UnicodeSet | null { if(!this.verifyInitialized()) { + /* c8 ignore next 2 */ return null; } if (!bufferSize) { + /* c8 ignore next 2 */ bufferSize = 100; // TODO-LDML: Preflight mode? Reuse buffer? } const buf = this.Module.asm.malloc(bufferSize * 2 * this.Module.HEAPU32.BYTES_PER_ELEMENT); @@ -239,6 +275,7 @@ function getUnicodeSetError(rc: number) : CompilerEvent { case KMCMP_FATAL_OUT_OF_RANGE: return CompilerMessages.Fatal_UnicodeSetOutOfRange(); default: + /* c8 ignore next */ return CompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}); } } diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp index 627298bf37..3a185bc403 100644 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp +++ b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp @@ -7,52 +7,12 @@ #include #include "CheckFilenameConsistency.h" #include "kmx_u16.h" -#include "filesystem.h" #ifdef _MSC_VER #include #endif -namespace kmcmp { - extern KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer -} -bool IsRelativePath(KMX_CHAR const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if ((*p == '\\') || (*p == '/')) return FALSE; - if (*p && *(p + 1) == ':') return FALSE; - - return TRUE; -} - -bool IsRelativePath(KMX_WCHAR const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if ((*p == u'\\') || (*p == u'/'))return FALSE; - if (*p && *(p + 1) == u':') return FALSE; - - return TRUE; -} KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissingFile) { PKMX_WCHAR WFilename = strtowstr(( KMX_CHAR *)Filename); @@ -62,6 +22,12 @@ KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissin } KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile) { + // TODO: we no longer have filesystem access here. We could move this check to + // kmc itself, and make it consistent across all compilers that use the same + // loader callback + return CERR_None; + +#if 0 // not ready yet: needs more attention-> common includes for non-Windows platforms KMX_WCHAR Name[260]; // TODO: fixed buffer sizes bad @@ -115,6 +81,7 @@ KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissin #endif return CERR_None; +#endif } KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk) { diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.h b/developer/src/kmcmplib/src/CheckFilenameConsistency.h index 39f86db391..eba8f2d8bf 100644 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.h +++ b/developer/src/kmcmplib/src/CheckFilenameConsistency.h @@ -7,5 +7,3 @@ KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk); KMX_DWORD CheckFilenameConsistency(KMX_CHAR const * Filename, bool ReportMissingFile); KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile); -bool IsRelativePath(KMX_CHAR const * p); -bool IsRelativePath(KMX_WCHAR const * p); diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 8e0f94cb29..91e02e14e7 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -101,7 +101,6 @@ #include "UnreachableRules.h" #include "CheckForDuplicates.h" #include "kmx_u16.h" -#include "filesystem.h" #include /* These macros are adapted from winnt.h and legacy use only */ @@ -125,7 +124,6 @@ namespace kmcmp{ KMX_BOOL FMnemonicLayout = FALSE; KMX_BOOL FOldCharPosMatching = FALSE; int CompileTarget; - KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer int BeginLine[4]; KMX_BOOL IsValidCallStore(PFILE_STORE fs); @@ -867,7 +865,6 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE int i, j; KMX_DWORD msg; PKMX_WCHAR p, q; - KMX_CHAR *pp; if (!pssBuf) pssBuf = new KMX_WCHAR[GLOBAL_BUFSIZE]; PKMX_WCHAR buf = pssBuf; @@ -917,13 +914,9 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE case TSS_INCLUDECODES: VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_NamedCodes); - pp = wstrtostr(sp->dpString); - if (!kmcmp::CodeConstants->LoadFile(pp)) - { - delete[] pp; + if (!kmcmp::CodeConstants->LoadFile(fk, sp->dpString)) { return CERR_CannotLoadIncludeFile; } - delete[] pp; kmcmp::CodeConstants->reindex(); // I4982 break; @@ -3234,58 +3227,32 @@ KMX_BOOL IsSameToken(PKMX_WCHAR *p, KMX_WCHAR const * token) return FALSE; } -KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +static bool endsWith(const std::string& str, const std::string& suffix) { - FILE *fp; - KMX_WCHAR szNewName[260]; - - if (IsRelativePath(szName)) - { - PKMX_WCHAR WCompileDir = strtowstr(kmcmp::CompileDir); - u16ncpy(szNewName, WCompileDir, _countof(szNewName)); // I3481 - u16ncat(szNewName,szName, _countof(szNewName )); // I3481 - } - else - u16ncpy(szNewName, szName, _countof(szNewName)); // I3481 - - fp=Open_File(szNewName, u"rb"); - - if ( fp == NULL) - { - // else if filename.bmp is not in the folder -> attempt to open filename.bmp.bmp ! - if ( u16cmp(szNewName+u16len(szNewName)-4, u".bmp") ) - u16ncat(szNewName, u".bmp", _countof(szNewName)); // I3481 + return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix); +} - fp= Open_File(szNewName, u"rb"); +KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +{ + auto szNameUtf8 = string_from_u16string(szName); - if ( fp == NULL) + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, (int*) FileSize, msgprocContext)) { + // Append .bmp and try again + if(endsWith(szNameUtf8, ".bmp")) { return CERR_CannotReadBitmapFile; + } + szNameUtf8.append(".bmp"); + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, (int*) FileSize, msgprocContext)) { + return CERR_CannotReadBitmapFile; + } } - KMX_DWORD msg; - if ((msg = CheckFilenameConsistency(szNewName, FALSE)) != CERR_None) { - return msg; - } - - fseek(fp, 0, SEEK_END); - *FileSize = (KMX_DWORD)ftell(fp); - fseek(fp ,0,SEEK_SET); - if (*FileSize < 0) { - fclose(fp); - return CERR_CannotReadBitmapFile; - } - - if (*FileSize < 2) return CERR_CannotReadBitmapFile; *Buf = new KMX_BYTE[*FileSize]; - - if (fread(*Buf, 1, *FileSize, fp) < (size_t) *FileSize) { - delete[] * Buf; - *Buf = NULL; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), *Buf, (int*) FileSize, msgprocContext)) { + delete[] *Buf; return CERR_CannotReadBitmapFile; } - fclose(fp); - /* Test for version 7.0 icon support */ if (*((PKMX_CHAR)*Buf) != 'B' && *(((PKMX_CHAR)*Buf) + 1) != 'M') { VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); @@ -3447,6 +3414,8 @@ bool hasPreamble(std::u16string result) { return result.size() > 0 && result[0] == 0xFEFF; } +#include "unicode/ucnv.h" + bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) { if(sz == 0) { return FALSE; @@ -3456,23 +3425,36 @@ bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) try { std::wstring_convert, char16_t> converter; - result = converter.from_bytes((char*)infile, (char*)infile+sz-1); + result = converter.from_bytes((char*)infile, (char*)infile+sz); } catch(std::range_error e) { - std::wstring_convert, char16_t> converter; - result = converter.from_bytes((char*)infile, (char*)infile+sz-1); + UErrorCode status = U_ZERO_ERROR; + // TODO: we need ICU data files here @srl295 plz help! + UConverter* conv = ucnv_open("windows-1252", &status); + if(U_FAILURE(status)) { + return FALSE; + } + + char16_t* dest = new char16_t[sz*2]; + ucnv_toUChars(conv, dest, sz*2, (char*)infile, sz, &status); + if(U_FAILURE(status)) { + delete[] dest; + return FALSE; + } + + result = dest; + delete[] dest; } if(hasPreamble(result)) { - *sz16 = result.size() * 2 - 1; + *sz16 = result.size() * 2 - 2; *tempfile = new KMX_BYTE[*sz16]; - memcpy(*tempfile, result.c_str() + 2, *sz16); - + memcpy(*tempfile, result.c_str() + 1, *sz16); + } else { + *sz16 = result.size() * 2; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str(), *sz16); } - *sz16 = result.size() * 2; - *tempfile = new KMX_BYTE[*sz16]; - memcpy(*tempfile, result.c_str(), *sz16); - return TRUE; } diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index fd297f4332..1a4e77f8e8 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -3,7 +3,6 @@ #include #include #include "kmcmplib.h" -#include "filesystem.h" #include "CheckFilenameConsistency.h" #include "CheckNCapsConsistency.h" #include "DeprecationChecks.h" @@ -19,7 +18,7 @@ bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); WASM interface for compiler message callback */ EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context), { - const proc = globalThis[UTF8ToString(context)]; + const proc = globalThis[UTF8ToString(context)].message; if(!proc || typeof proc != 'function') { console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); return 0; @@ -28,18 +27,27 @@ EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context } }); -EM_JS(bool, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int* bufferSize, char* context), { - const proc = globalThis[UTF8ToString(context)]; +EM_JS(int, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int bufferSize, char* context), { + const proc = globalThis[UTF8ToString(context)].loadFile; if(!proc || typeof proc != 'function') { return 0; } else { - return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + if(buffer == 0) { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), 0, 0); + } else { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + } } }); bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buffer, int* bufferSize, void* context) { char* msgProc = static_cast(context); - return wasm_loadfileproc(filename, baseFilename, buffer, bufferSize, msgProc); + if(buffer == nullptr) { + *bufferSize = wasm_loadfileproc(filename, baseFilename, 0, 0, msgProc); + return *bufferSize != 0; + } else { + return wasm_loadfileproc(filename, baseFilename, buffer, *bufferSize, msgProc) == 1; + } } int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, void* context) { @@ -48,8 +56,7 @@ int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, v } struct WASM_COMPILER_INTERFACE { - std::string messageCallback; // int line, uint32_t dwMsgCode, char* szText - std::string loadFileCallback; // TODO: char* filename, char* baseFilename --> buffer + std::string callbacksKey; // key of callbacks object on globalThis }; struct WASM_COMPILER_RESULT { @@ -76,7 +83,7 @@ WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPI options, wasm_CompilerMessageProc, wasm_LoadFileProc, - intf.messageCallback.c_str(), + intf.callbacksKey.c_str(), kr ); @@ -103,8 +110,7 @@ EMSCRIPTEN_BINDINGS(compiler_interface) { emscripten::class_("CompilerInterface") .constructor<>() - .property("messageCallback", &WASM_COMPILER_INTERFACE::messageCallback) - .property("loadFileCallback", &WASM_COMPILER_INTERFACE::loadFileCallback) + .property("callbacksKey", &WASM_COMPILER_INTERFACE::callbacksKey) ; emscripten::class_("CompilerResult") @@ -131,6 +137,8 @@ EXTERN bool kmcmp_CompileKeyboard( ) { FILE_KEYBOARD fk; + fk.extra = new FILE_KEYBOARD_EXTRA; + fk.extra->kmnFilename = pszInfile; kmcmp::FSaveDebug = options.saveDebug; // I3681 kmcmp::FCompilerWarningsAsErrors = options.compilerWarningsAsErrors; // I4865 @@ -143,16 +151,6 @@ EXTERN bool kmcmp_CompileKeyboard( return FALSE; } - PKMX_STR p; - - if ((p = strrchr_slash((char*)pszInfile)) != nullptr) { - strncpy(kmcmp::CompileDir, pszInfile, (int)(p - pszInfile + 1)); // I3481 - kmcmp::CompileDir[(int)(p - pszInfile + 1)] = 0; - } - else { - kmcmp::CompileDir[0] = 0; - } - msgproc = messageProc; loadfileproc = loadFileProc; msgprocContext = (void*)procContext; @@ -172,7 +170,7 @@ EXTERN bool kmcmp_CompileKeyboard( return FALSE; } - KMX_BYTE* infile = new KMX_BYTE[sz]; + KMX_BYTE* infile = new KMX_BYTE[sz+1]; if(!infile) { AddCompileError(CERR_CannotAllocateMemory); return FALSE; @@ -182,6 +180,7 @@ EXTERN bool kmcmp_CompileKeyboard( AddCompileError(CERR_CannotReadInfile); return FALSE; } + infile[sz] = 0; // zero-terminate for safety, not technically needed but helps avoid memory bugs int offset = 0; if(infile[0] == (KMX_BYTE) UTF16Sig[0] && infile[1] == (KMX_BYTE) UTF16Sig[1]) { @@ -266,7 +265,6 @@ bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) fk->dpDeadKeyArray = NULL; fk->cxVKDictionary = 0; // I3438 fk->dpVKDictionary = NULL; // I3438 - fk->extra = new FILE_KEYBOARD_EXTRA; fk->extra->kvksFilename = u""; /* fk->szMessage[0] = 0; fk->szLanguageName[0] = 0;*/ diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.cpp b/developer/src/kmcmplib/src/NamedCodeConstants.cpp index e6b1ae6d70..30091b7cb8 100644 --- a/developer/src/kmcmplib/src/NamedCodeConstants.cpp +++ b/developer/src/kmcmplib/src/NamedCodeConstants.cpp @@ -27,15 +27,12 @@ #include "CheckFilenameConsistency.h" #include #include "kmcompx.h" -#include "filesystem.h" using namespace kmcmp; int IsHangulSyllable(const KMX_WCHAR *codename, int *code); namespace kmcmp { - extern KMX_CHAR CompileDir[]; - int __cdecl sort_entries(const void *elem1, const void *elem2) { return u16icmp( @@ -117,85 +114,61 @@ char *kmc_strupr(char *s) { return s; } -KMX_BOOL NamedCodeConstants::IntLoadFile(const KMX_CHAR *filename) -{ +KMX_BOOL NamedCodeConstants::LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filename) { const int str_size = 256; - FILE *fp = NULL; if (CheckFilenameConsistency(filename, FALSE) != 0) { return FALSE; } - fp = Open_File(filename, "rt"); - if(fp == NULL) { - return FALSE; // I3481 + auto szNameUtf8 = string_from_u16string(filename); + + int FileSize; + KMX_BYTE* Buf; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, &FileSize, msgprocContext)) { + return FALSE; } - KMX_CHAR str[str_size], *p, *q, *context = NULL; - KMX_BOOL isEol , first = TRUE; + Buf = new KMX_BYTE[FileSize+1]; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), Buf, &FileSize, msgprocContext)) { + delete[] Buf; + return FALSE; + } + Buf[FileSize] = 0; // zero-terminate for strtok - while(fgets(str, str_size, fp)) - { - isEol = *(strchr(str, 0) - 1) == '\n'; - p = strtok_r(str, ";", &context); // I3481 - q = strtok_r(NULL, ";\n", &context); - if(p && q) - { - if(first && *p == (KMX_CHAR)0xEF && *(p+1) == (KMX_CHAR)0xBB && *(p+2) == (KMX_CHAR)0xBF) p += 3; // I3056 UTF-8 // I3512 - first = FALSE; + char* filetok; + char* filecontext; + filetok = strtok_r((char*)Buf, "\n", &filecontext); + + if(*filetok == (KMX_CHAR)0xEF && *(filetok+1) == (KMX_CHAR)0xBB && *(filetok+2) == (KMX_CHAR)0xBF) filetok += 3; // I3056 UTF-8 // I3512 + + while(filetok) { + KMX_CHAR str[str_size], *p, *q, *context = NULL; + + if(strlen(filetok) >= str_size) { + delete[] Buf; + // TODO chuck a wobbly + return FALSE; + } + strcpy(str, filetok); + p = strtok_r(str, ";\r", &context); // I3481 + q = strtok_r(nullptr, ";\r", &context); + if(p && q) { kmc_strupr(q); // I3481 // I3641 - long n = strtol(p, NULL, 16); + long n = strtol(p, nullptr, 16); if (*q != '<') { PKMX_WCHAR q0 = strtowstr(q); AddCode_IncludedCodes((int)n, q0); delete[] q0; } } - if(!isEol ) - { - while(fgets(str, str_size, fp)) if(*(strchr(str, 0)-1) == '\n') break; - } + filetok = strtok_r(nullptr, "\n", &filecontext); } - fclose(fp); - - return TRUE; -} - -KMX_BOOL NamedCodeConstants::LoadFile(const KMX_CHAR *filename) -{ - const int buf_size = 260; - KMX_CHAR buf[buf_size]; - // Look in current directory first -- REMOVED AS DANGEROUS - /* strncpy(buf, filename, (buf_size-1)); buf[buf_size-1] = 0; // I3481 - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - */ - // Then look in keyboard file directory (CompileDir) - strncpy(buf, CompileDir, (buf_size-1)); buf[buf_size-1] = 0; // I3481 - strncat(buf, filename, (buf_size-1)-strlen(CompileDir)); buf[buf_size-1] = 0; - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - - //TODO: sort out how to find common includes in non-Windows platforms: - #ifdef _WINDOWS_ - // Finally look in kmcmpdll.dll directory - GetModuleFileName(0, buf, buf_size); - - KMX_CHAR *p = strrchr_slash(buf); - if(p) - p++; - else - p = buf; - *p = 0; - strncat_s(buf, _countof(buf), filename, (buf_size-1)-strlen(buf)); buf[buf_size-1] = 0; // I3481 // I3641 - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - #endif + delete[] Buf; reindex(); - - return FALSE; + return TRUE; } void NamedCodeConstants::reindex() diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.h b/developer/src/kmcmplib/src/NamedCodeConstants.h index 83d707d092..c537a73361 100644 --- a/developer/src/kmcmplib/src/NamedCodeConstants.h +++ b/developer/src/kmcmplib/src/NamedCodeConstants.h @@ -2,6 +2,8 @@ #ifndef NAMEDCODECONSTANTS_H #define NAMEDCODECONSTANTS_H +#include "compfile.h" + #define MAX_ENAME 128 #define ALLOC_SIZE 256 @@ -23,14 +25,13 @@ namespace kmcmp{ int GetCode_IncludedCodes(const KMX_WCHAR *codename); void AddCode_IncludedCodes(int n, const KMX_WCHAR *p); - KMX_BOOL IntLoadFile(const KMX_CHAR *filename); public: NamedCodeConstants(); ~NamedCodeConstants(); void reindex(); void AddCode(int n, const KMX_WCHAR *p, KMX_DWORD storeIndex); - KMX_BOOL LoadFile(const KMX_CHAR *filename); + KMX_BOOL LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filename); int GetCode(const KMX_WCHAR *codename, KMX_DWORD *storeIndex); }; } diff --git a/developer/src/kmcmplib/src/compfile.h b/developer/src/kmcmplib/src/compfile.h index 7c262be090..c54663aba7 100644 --- a/developer/src/kmcmplib/src/compfile.h +++ b/developer/src/kmcmplib/src/compfile.h @@ -123,7 +123,8 @@ typedef FILE_VKDICTIONARY *PFILE_VKDICTIONARY; * Extra metadata for API consumers */ struct FILE_KEYBOARD_EXTRA { - std::u16string kvksFilename; // original TSS_VISUALKEYBOARD value + std::string kmnFilename; // utf-8 + std::u16string kvksFilename; // utf-16, original TSS_VISUALKEYBOARD value }; typedef struct FILE_KEYBOARD_EXTRA* PFILE_KEYBOARD_EXTRA; diff --git a/developer/src/kmcmplib/src/kmcmplib.h b/developer/src/kmcmplib/src/kmcmplib.h index 70718f98bc..f22c836bae 100644 --- a/developer/src/kmcmplib/src/kmcmplib.h +++ b/developer/src/kmcmplib/src/kmcmplib.h @@ -14,7 +14,6 @@ namespace kmcmp { extern KMX_BOOL FMnemonicLayout; extern KMX_BOOL FOldCharPosMatching; extern int CompileTarget; - extern KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer extern int BeginLine[4]; extern int currentLine; extern NamedCodeConstants *CodeConstants; diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 727d6831e6..a81efac562 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -25,8 +25,10 @@ endif name_suffix = [] if cpp_compiler.get_id() == 'emscripten' - lib_links = ['--whole-archive', '--bind', '-sMODULARIZE', '-sEXPORT_ES6'] - links += ['-lnodefs.js', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] + # wasm-exceptions supported in Node 18+, Chrome 95+, Firefox 100+, Safari 15.2+ + flags += ['-fwasm-exceptions'] + lib_links = ['--whole-archive', '-sMODULARIZE', '-sEXPORT_ES6'] + links += ['-fwasm-exceptions', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] # tests are building as ES6 so we need to declare the file extension # note that meson currently struggles with the sanitycheckc_cross.exe # program, because it has a hard coded extension (.exe) which is not @@ -44,7 +46,6 @@ lib = library('kmcmplib', 'CompilerInterfaces.cpp', 'DeprecationChecks.cpp', 'Edition.cpp', - 'filesystem.cpp', 'NamedCodeConstants.cpp', 'versioning.cpp', 'virtualcharkeys.cpp', @@ -79,7 +80,7 @@ if cpp_compiler.get_id() == 'emscripten' host = executable('wasm-host', #'wasm-host.cpp', cpp_args: defns, include_directories: inc, - link_args: links, + link_args: links + lib_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep) endif diff --git a/developer/src/kmcmplib/src/pch.h b/developer/src/kmcmplib/src/pch.h index f878ad11be..358e28998a 100644 --- a/developer/src/kmcmplib/src/pch.h +++ b/developer/src/kmcmplib/src/pch.h @@ -10,6 +10,4 @@ #include "../../../../common/windows/cpp/include/crc32.h" #include -#include - #include diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index b347773220..cdbabea4d5 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -18,54 +18,12 @@ #include #include "../src/compfile.h" #include -#include "../src/filesystem.h" +#include "util_filesystem.h" +#include "util_callbacks.h" void setup(); void test_kmcmp_CompileKeyboard(char *kmn_file); -std::vector error_vec; - -int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { - error_vec.push_back(dwMsgCode); - const char*t = "unknown"; - switch(dwMsgCode & 0xF000) { - case CERR_HINT: t=" hint"; break; - case CERR_WARNING: t="warning"; break; - case CERR_ERROR: t=" error"; break; - case CERR_FATAL: t=" fatal"; break; - } - printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); - return 1; -} - -bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { - FILE* fp = Open_File(filename, "rb"); - if(!fp) { - return false; - } - - if(!data) { - // return size - if(fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - return false; - } - *size = ftell(fp); - if(*size == -1L) { - fclose(fp); - return false; - } - } else { - // return data - if(fread(data, 1, *size, fp) != *size) { - fclose(fp); - return false; - } - } - fclose(fp); - return true; -} - int main(int argc, char *argv[]) { if(argc < 1) { puts("Usage: api-test "); @@ -82,6 +40,13 @@ void setup() { error_vec.clear(); } +/* + TODO: tests to run: + 4. ANSI (no BOM of course) + 8. file without blank last line (cannot compare with fixture due to bug in kmcmpdll...) + Hint to add: k004_ansi.kmn: Hint: 10A6 Keyman Developer has detected that the file has ANSI encoding. Consider converting this file to UTF-8 +*/ + void test_kmcmp_CompileKeyboard(char *kmn_file) { // Create an empty file FILE *fp = Open_File(kmn_file, "wb"); diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat new file mode 100644 index 0000000000..e346418eb4 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat @@ -0,0 +1,4 @@ +@echo off +echo Compiles the keyboards using the legacy kmcomp.exe +echo to use as baseline comparisons for kmcmplib +for %%d in (*.kmn) do kmcomp -no-compiler-version -d %%d \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn new file mode 100644 index 0000000000000000000000000000000000000000..2b3180c36ab4b473e74b1b3487fc8f0a61d623f6 GIT binary patch literal 452 zcmZvX%}T>S6otRF;Ci;RG8IwmMz9N^r4>Z2P-{0*q)pQpYC6SO#HDW`K8}yz1F2_b zEO8;>PVStad(V7-rC7RTRHQsou;NLlV@XTQNQ_74DzC9(@0sYdEGXqE<#-S~6_Scs zhQAAVAtuv(qPk(oDf=`z;)0$KKQreYOjH}hfM HO;rB?W(q}E literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx new file mode 100644 index 0000000000000000000000000000000000000000..13ea2a686e09c16db68ff7bb355e114b9125d76d GIT binary patch literal 356 zcmX|-y-EW?6h@CC{(@LJjfK^g5*smH5)hGKgd~cM=wcK?h&EbS+F6LTi1-vfKuBe6 zY2hPS_yT(Fx_IF%-}l|QGdt5f>~@%g>e1pi)hKINm-ap;#+1e%<2!;qYd!Vk use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx new file mode 100644 index 0000000000000000000000000000000000000000..0b9c9294182d9387165c98e1ffc84c8fe45452e3 GIT binary patch literal 378 zcmX|-zb}JP6owxn6pNv86O*ZdP*T+eoFYo)Fd(S=j8hhJK#^I(|T)`TOA8S;iuF8z!+fx45AU`;5awoSE5eup-GIf`;l(pDb^*mi- zgBt5Bs~4F|6HDysw`j7)rhS|MtM+EoD_MGH-mK1!I^)1;9k+mrTP?|74J)FJ+x_0F i=_X>k3LRTrscR?m>{x#(?dZ7 use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx new file mode 100644 index 0000000000000000000000000000000000000000..ee1cf4225502e3c7944b58f301e5be649af4ec00 GIT binary patch literal 372 zcmX|-y-EW?6h@B`{B116X{}O;1cXJqBp?!k5sZqJk{HD$Xi`MP(k_Kq2MIojmG}T+ zXuY}scO@6Efi@` zVV7<77VFupB1igtn$*~{k9%Rq-fA{K%}z+nsvN3Q&Yafqd|2~NN&bIY3KDI+*JNKs gw;=I(PHeTMs$I;pj=nFg=(!IcVH94&lMfV=KM7PUb^rhX literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn new file mode 100644 index 0000000000..50a1b31ea1 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn @@ -0,0 +1,13 @@ +c Description: Verifies that kmcmplib can compile an ANSI file +c This has some high-ascii letters in cp1252 to ensure that +c it fails to load as 'utf8 without bom' + +store(&NAME) 'k004_ansi' +store(&VERSION) '9.0' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ÀÐ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx new file mode 100644 index 0000000000000000000000000000000000000000..6c41da979cb2a3ec20aadf6de0ca778fb8ce5ec1 GIT binary patch literal 350 zcmX|-y-EW?6h@CCYQTV1Y%Ef3DG6A_&MX88!36RnVxa_+Vi8S|DsPZN@&q9dkp~bf zYfGyKTe*wxmCUXD) literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp new file mode 100644 index 0000000000000000000000000000000000000000..509b89ae6df7f09a442ae60f215a6c7b3211b34f GIT binary patch literal 246 zcmZvUF%Ez*2t_e5>FO~&hr4&_cK5)zF7R>OXdqB#E&96yg&T8zj G@{Su^DQ%(v literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn new file mode 100644 index 0000000000..926ccdeeaf --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load a bitmap without file extension + +store(&NAME) 'k005_bitmap' +store(&VERSION) '9.0' +store(&BITMAP) 'k005_bitmap' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx new file mode 100644 index 0000000000000000000000000000000000000000..bc9bc9b671724050e169e59fce976a18d54a030e GIT binary patch literal 656 zcmZ{hyGjF55QhJV5JkZh;`acRmwz7&=*hh@R?A}o^(Fw$>p)BB*B zffhV~2T#xjoA1IijKLecfV&^}PM(8l@|;2L6X&2nE|qnpYn`g0GgqI`FK9t~+E=Ix zr$i`ut*fMpR>+q$@22zGrmm`{HEp0v|4#>;sWV^Frn^B}<&If*f9SrwNI%dPd8h-O z>yVd8RbILaLmMHn(q5d*m8d36E=qSuO$aC_)80MxBQ&hmt@rA+ysp>gH(DpC*#9-V z0w3%j85xb@rP%qu9Eyo?oCG{6#Oic92&~e!$x2D|BF%`g<4g%dtuD`P6eIRAp=916x*GXhr`_H use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx new file mode 100644 index 0000000000000000000000000000000000000000..f994a1dcac9e5ae797eb88eed9cee4b0e6f85b00 GIT binary patch literal 728 zcmbu6JxhX76vzKYU{S*}M9=CF;v`N*4WU9rNH3^GLlE{dNK~#(-JqeGBgC;O`Vb+F zjedqcfJ@|NzjNS>L1T*-NfuF<9K>=T?=vWszRYPY!J|SPwy!O@9q0W6a zs|}U3t`#~KmG_fb?GRU0)0#G^OMfTx$qp&2GJSP+l(glyNUQA0_>-Idlm+r-ZR3ZN zkGPps<)(Wuk}(_$?Z#oQL^NgPqI6B^2FirfN$!sL0U8$TD(=PY_%-hEj3JX^Ulc{? zasM{<);X}Z%7EofoYz5F%?RQ`(K+8^8W)_p-7YPBsr@Qn>kZm|*}mHT`SFvrMGq4+QP0yrw1lmGw# literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn new file mode 100644 index 0000000000..71d72d5120 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load an includecodes file with \r\n line endings + +store(&NAME) 'k007_includecodes_r_n' +store(&VERSION) '9.0' +store(&includecodes) 'k007_includecodes_r_n.txt' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > $LOWER_A +$LOWER_A + [K_B] > $LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx new file mode 100644 index 0000000000000000000000000000000000000000..f8b4eac1b0d9103c7767e487b5109a06aed8e1ec GIT binary patch literal 462 zcma)%J4%C55JrzSDq4tzwy9Ev-%9L~fQZBwiHe=Z#3J|zmm`-ru-1)wlxv$dkL4`TUG*`bcreZN>By?W7ilL#L#M^jq*~&ofWJMn2 zUSj{dJj$$m%7-jT;@)rbqU@`;C3M|ihyS0LqfJlZ use(main) + +group(main) using keys + ++ [K_A] > $LOWER_A +$LOWER_A + [K_B] > $LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx new file mode 100644 index 0000000000000000000000000000000000000000..aa158cdd546cdde915e4a55c1db521e57d4abb04 GIT binary patch literal 454 zcma)%zb=De6o;Q$N*Khz%F>vcZrIv}AYW9XNQWlUmZ3onNVox$!C+{51>y<}(!t;Y zT!2O58f-l0Q{9?4$@8Ay@0|1gOKQ7SCSc(<`M}7;Vq_(BQ96omLphAs!QiZwuH4I< zJV;Ms|C>C@xO~dH%*dj9zs%FJpf~T9ja`yZ6E%3TAmB8Bu*W5Yup0s-ZP)RHs)YS%IvAz p!xzGhzq^ya7*m9eU;geZ=_aFHgeP0iQr1olYJL5SG{V!AP(Sg;I(+~D literal 0 HcmV?d00001 diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt new file mode 100644 index 0000000000..4cc3fe4351 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt @@ -0,0 +1,2 @@ +0061;LOWER_A +0062;LOWER_B diff --git a/developer/src/kmcmplib/tests/kmcompxtest.cpp b/developer/src/kmcmplib/tests/kmcompxtest.cpp index c5d189798b..5dbaee88bc 100644 --- a/developer/src/kmcmplib/tests/kmcompxtest.cpp +++ b/developer/src/kmcmplib/tests/kmcompxtest.cpp @@ -13,7 +13,8 @@ #include #include #include -#include "../src/filesystem.h" +#include "util_filesystem.h" +#include "util_callbacks.h" #ifdef _MSC_VER #else @@ -22,57 +23,11 @@ using namespace std; -vector < int > error_vec; - #define CERR_FATAL 0x00008000 #define CERR_ERROR 0x00004000 #define CERR_WARNING 0x00002000 #define CERR_HINT 0x00001000 -int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) -{ - error_vec.push_back(dwMsgCode); - const char*t = "unknown"; - switch(dwMsgCode & 0xF000) { - case CERR_HINT: t=" hint"; break; - case CERR_WARNING: t="warning"; break; - case CERR_ERROR: t=" error"; break; - case CERR_FATAL: t=" fatal"; break; - } - printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); - return 1; -} - -bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { - FILE* fp = Open_File(filename, "rb"); - if(!fp) { - return false; - } - - if(!data) { - // return size - if(fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - return false; - } - *size = ftell(fp); - if(*size == -1L) { - fclose(fp); - return false; - } - } else { - // return data - if(fread(data, 1, *size, fp) != *size) { - fclose(fp); - return false; - } - } - fclose(fp); - return true; -} - -#include "../src/filesystem.h" - int main(int argc, char *argv[]) { if(argc < 4) { diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index 2468ac53b1..cc51189a63 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -6,16 +6,20 @@ fs = import('fs') -tests_flags = [] +tests_links = [] + +if cpp_compiler.get_id() == 'emscripten' + tests_links += ['-lnodefs.js'] +endif input_path = meson.current_source_dir() / '../../../../common/test/keyboards/baseline' output_path = meson.current_build_dir() -kmcompxtest = executable('kmcompxtest', 'kmcompxtest.cpp', - cpp_args: defns, +kmcompxtest = executable('kmcompxtest', ['kmcompxtest.cpp','util_filesystem.cpp','util_callbacks.cpp'], + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep, ) @@ -76,6 +80,25 @@ foreach kbd : tests test(kbd, kmcompxtest, args: [kbd_src, kbd_obj, join_paths(input_path, kbd) + '.kmx']) endforeach +valid_keyboard_tests = [ + 'k001_utf16', + 'k002_utf8_without_bom', + 'k003_utf8_with_bom', + # 'k004_ansi', # TODO: enable ansi test when we have the icu datafiles + 'k005_bitmap', + 'k006_icon', + 'k007_includecodes_r_n', + 'k008_includecodes_n' +] + +fixtures_path = meson.current_source_dir() / 'fixtures/valid-keyboards' + +foreach kbd : valid_keyboard_tests + kbd_src = join_paths(fixtures_path, kbd) + '.kmn' + kbd_obj = join_paths(output_path, kbd) + '.kmx' + test(kbd, kmcompxtest, args: [kbd_src, kbd_obj, join_paths(fixtures_path, kbd) + '.kmx']) +endforeach + # Test fixtures that come from keyboards repo -- but only for a "full" test, # which typically we run on CI no more than once a day, because it's expensive. @@ -107,11 +130,11 @@ endif # Test the API endpoints -apitest = executable('api-test', 'api-test.cpp', - cpp_args: defns, +apitest = executable('api-test', ['api-test.cpp','util_filesystem.cpp','util_callbacks.cpp'], + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep ) @@ -119,10 +142,10 @@ apitest = executable('api-test', 'api-test.cpp', test('api-test', apitest, args: [output_path / 'blank_keyboard.kmx']) usetapitest = executable('uset-api-test', 'uset-api-test.cpp', - cpp_args: defns, + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep, ) diff --git a/developer/src/kmcmplib/tests/util_callbacks.cpp b/developer/src/kmcmplib/tests/util_callbacks.cpp new file mode 100644 index 0000000000..e610a54c09 --- /dev/null +++ b/developer/src/kmcmplib/tests/util_callbacks.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include "util_filesystem.h" +#include "../src/compfile.h" +#include + +std::vector error_vec; + +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { + error_vec.push_back(dwMsgCode); + const char*t = "unknown"; + switch(dwMsgCode & 0xF000) { + case CERR_HINT: t=" hint"; break; + case CERR_WARNING: t="warning"; break; + case CERR_ERROR: t=" error"; break; + case CERR_FATAL: t=" fatal"; break; + } + printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); + return 1; +} + +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { + std::string resolvedFilename = filename; + if(baseFilename && *baseFilename && IsRelativePath(filename)) { + char* p; + if ((p = strrchr_slash((char*)baseFilename)) != nullptr) { + std::string basePath = std::string(baseFilename, (int)(p - baseFilename + 1)); + resolvedFilename = basePath; + resolvedFilename.append(filename); + } + } + + FILE* fp = Open_File(resolvedFilename.c_str(), "rb"); + if(!fp) { + return false; + } + + if(!data) { + // return size + if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + *size = ftell(fp); + if(*size == -1L) { + fclose(fp); + return false; + } + } else { + // return data + if(fread(data, 1, *size, fp) != *size) { + fclose(fp); + return false; + } + } + fclose(fp); + return true; +} \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/util_callbacks.h b/developer/src/kmcmplib/tests/util_callbacks.h new file mode 100644 index 0000000000..345de64b9f --- /dev/null +++ b/developer/src/kmcmplib/tests/util_callbacks.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context); +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context); + +extern std::vector error_vec; \ No newline at end of file diff --git a/developer/src/kmcmplib/src/filesystem.cpp b/developer/src/kmcmplib/tests/util_filesystem.cpp similarity index 84% rename from developer/src/kmcmplib/src/filesystem.cpp rename to developer/src/kmcmplib/tests/util_filesystem.cpp index a343641acc..7f9edd6b36 100644 --- a/developer/src/kmcmplib/src/filesystem.cpp +++ b/developer/src/kmcmplib/tests/util_filesystem.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "filesystem.h" +#include "util_filesystem.h" #ifdef _MSC_VER #include @@ -186,3 +186,42 @@ KMX_BOOL kmcmp_FileExists(const KMX_WCHAR* filename) { return FALSE; }; + + +bool IsRelativePath(KMX_CHAR const * p) { + // Relative path (returns TRUE): + // ..\...\BITMAP.BMP + // PATH\BITMAP.BMP + // BITMAP.BMP + + // Semi-absolute path (returns FALSE): + // \...\BITMAP.BMP + + // Absolute path (returns FALSE): + // C:\...\BITMAP.BMP + // \\SERVER\SHARE\...\BITMAP.BMP + + if ((*p == '\\') || (*p == '/')) return FALSE; + if (*p && *(p + 1) == ':') return FALSE; + + return TRUE; +} + +bool IsRelativePath(KMX_WCHAR const * p) { + // Relative path (returns TRUE): + // ..\...\BITMAP.BMP + // PATH\BITMAP.BMP + // BITMAP.BMP + + // Semi-absolute path (returns FALSE): + // \...\BITMAP.BMP + + // Absolute path (returns FALSE): + // C:\...\BITMAP.BMP + // \\SERVER\SHARE\...\BITMAP.BMP + + if ((*p == u'\\') || (*p == u'/'))return FALSE; + if (*p && *(p + 1) == u':') return FALSE; + + return TRUE; +} \ No newline at end of file diff --git a/developer/src/kmcmplib/src/filesystem.h b/developer/src/kmcmplib/tests/util_filesystem.h similarity index 82% rename from developer/src/kmcmplib/src/filesystem.h rename to developer/src/kmcmplib/tests/util_filesystem.h index 786b7268ac..4c94dae6bf 100644 --- a/developer/src/kmcmplib/src/filesystem.h +++ b/developer/src/kmcmplib/tests/util_filesystem.h @@ -1,7 +1,7 @@ #pragma once #include -#include "kmx_u16.h" +#include "../src/kmx_u16.h" // Opens files on windows and non-windows platforms. Datatypes for Filename and mode must be the same. // returns FILE* if file could be opened; FILE needs to be closed in calling function @@ -10,3 +10,6 @@ FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode); FILE* Open_File(const KMX_WCHAR* Filename, const KMX_WCHAR* mode); KMX_BOOL kmcmp_FileExists(const KMX_CHAR *filename); KMX_BOOL kmcmp_FileExists(const KMX_WCHAR *filename); + +bool IsRelativePath(KMX_CHAR const * p); +bool IsRelativePath(KMX_WCHAR const * p); -- GitLab From 6f4f20acb8e90d9c25f6b5da56299b3148580f07 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 12:13:59 +1000 Subject: [PATCH 324/386] chore(developer): Add TODO issue numbers to PR --- developer/src/kmc-kmn/src/compiler/compiler.ts | 8 ++++---- developer/src/kmcmplib/include/kmcmplibapi.h | 2 +- developer/src/kmcmplib/src/CheckFilenameConsistency.cpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmcmplib/src/CompilerInterfaces.cpp | 3 +++ developer/src/kmcmplib/src/meson.build | 5 ----- developer/src/kmcmplib/tests/api-test.cpp | 2 +- developer/src/kmcmplib/tests/meson.build | 2 +- 8 files changed, 12 insertions(+), 14 deletions(-) diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index b27440fd06..3c6df80e7e 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -120,7 +120,7 @@ export class KmnCompiler { } private loadFileCallback = (filename: string, baseFilename: string, buffer: number, bufferSize: number): number => { - // TODO: we can optimize this in future by avoiding loading the file twice + // TODO: we can optimize this in future by avoiding loading the file twice #8885 let resolvedFilename = this.callbacks.resolveFilename(baseFilename, filename); let data = this.callbacks.loadFile(resolvedFilename); if(!data) { @@ -133,7 +133,7 @@ export class KmnCompiler { } if(bufferSize != data.byteLength) { - // TODO: consider chucking a wobbly because this is a bug + // TODO: consider chucking a wobbly because this is a bug #8885 /* c8 ignore next 2 */ return 0; } @@ -195,7 +195,7 @@ export class KmnCompiler { reader.validate(kvks, this.callbacks.loadSchema('kvks')); } catch(e) { console.log(e); - // TODO: also unit test + // TODO: also unit test #8886 // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } @@ -203,7 +203,7 @@ export class KmnCompiler { let vk = reader.transform(kvks, errors); if(!vk || errors.length) { console.dir(errors); - // TODO: also unit test + // TODO: also unit test #8886 // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); return null; } diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index 5c2ae46fda..0b41af6990 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -35,7 +35,7 @@ struct KMCMP_COMPILER_RESULT { std::string kvksFilename; }; -// TODO: parameters in UTF-8 +// TODO: parameters in UTF-8 #8887 typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, const char* szText, void* context); // parameters in UTF-8 diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp index 3a185bc403..6d113d116b 100644 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp +++ b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp @@ -24,7 +24,7 @@ KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissin KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile) { // TODO: we no longer have filesystem access here. We could move this check to // kmc itself, and make it consistent across all compilers that use the same - // loader callback + // loader callback -- see #8883 return CERR_None; #if 0 diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 91e02e14e7..49484b1acc 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -3428,7 +3428,7 @@ bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) result = converter.from_bytes((char*)infile, (char*)infile+sz); } catch(std::range_error e) { UErrorCode status = U_ZERO_ERROR; - // TODO: we need ICU data files here @srl295 plz help! + // TODO: we need ICU data files here #8884 UConverter* conv = ucnv_open("windows-1252", &status); if(U_FAILURE(status)) { return FALSE; diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index 1a4e77f8e8..16b5b98654 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -13,6 +13,9 @@ bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); #ifdef __EMSCRIPTEN__ +// TODO: move emscripten wrappers into their own .cpp. Also move CompileKeyboardHandle +// into its own .cpp, so CompilerInterfaces.cpp has only C public API functions listed. +// #8889 /* WASM interface for compiler message callback diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index a81efac562..e697a0dced 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -29,11 +29,6 @@ if cpp_compiler.get_id() == 'emscripten' flags += ['-fwasm-exceptions'] lib_links = ['--whole-archive', '-sMODULARIZE', '-sEXPORT_ES6'] links += ['-fwasm-exceptions', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] - # tests are building as ES6 so we need to declare the file extension - # note that meson currently struggles with the sanitycheckc_cross.exe - # program, because it has a hard coded extension (.exe) which is not - # valid for node programs in module mode. - # name_suffix = '.mjs' endif icu = subproject('icu-for-uset', default_options: [ 'default_library=static', 'cpp_std=c++17', 'warning_level=0', 'werror=false']) diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index cdbabea4d5..cb0337caef 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -42,7 +42,7 @@ void setup() { /* TODO: tests to run: - 4. ANSI (no BOM of course) + 4. ANSI (no BOM of course) #8884 8. file without blank last line (cannot compare with fixture due to bug in kmcmpdll...) Hint to add: k004_ansi.kmn: Hint: 10A6 Keyman Developer has detected that the file has ANSI encoding. Consider converting this file to UTF-8 */ diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index cc51189a63..0d4c078ed3 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -84,7 +84,7 @@ valid_keyboard_tests = [ 'k001_utf16', 'k002_utf8_without_bom', 'k003_utf8_with_bom', - # 'k004_ansi', # TODO: enable ansi test when we have the icu datafiles + # 'k004_ansi', # TODO: enable ansi test when we have the icu datafiles #8884 'k005_bitmap', 'k006_icon', 'k007_includecodes_r_n', -- GitLab From e2930fa08058d17cbb4be9da462bea520e1d4505 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 10:23:42 +0700 Subject: [PATCH 325/386] chore(developer): fix npm pack for kmc-ldml --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index 20d7ce98c4..75d9e1abef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1030,6 +1030,7 @@ } }, "developer/src/kmc-ldml": { + "name": "@keymanapp/kmc-ldml", "license": "MIT", "dependencies": { "@keymanapp/keyman-version": "*", -- GitLab From 9d351f4798d80ef71044ae80812a7cc7b2e08322 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 31 May 2023 15:22:54 +0700 Subject: [PATCH 326/386] chore(developer): verify kvks files and report errors Fixes #8886. .kvks compiler now returns helpful errors for xml parse failures and schema validation errors, and for invalid virtual key codes. Introduces extra infrastructure for reporting and unit testing messages to kmc-kmn. This also fixes unhandled xml load exceptions and simplifies the error reporting coming out of the kvks loader, on the basis that only one type of error was being reported anyway. --- common/web/types/src/kvk/kvks-file-reader.ts | 25 ++++------ .../web/types/test/kvk/test-kvk-round-trip.ts | 14 +++--- common/web/types/test/kvk/test-kvks-file.ts | 14 +++--- .../src/kmc-kmn/src/compiler/compiler.ts | 48 +++++++++---------- .../src/kmc-kmn/src/compiler/messages.ts | 8 ++++ .../error_invalid_kvks_file.kmn | 12 +++++ .../error_invalid_kvks_file.kvks | 9 ++++ .../warn_invalid_vkey_in_kvks_file.kmn | 12 +++++ .../warn_invalid_vkey_in_kvks_file.kvks | 15 ++++++ developer/src/kmc-kmn/test/helpers/index.ts | 17 +++++++ developer/src/kmc-kmn/test/test-messages.ts | 47 +++++++++++++++++- developer/src/kmc-kmn/test/tsconfig.json | 3 +- 12 files changed, 167 insertions(+), 57 deletions(-) create mode 100644 developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kvks create mode 100644 developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks create mode 100644 developer/src/kmc-kmn/test/helpers/index.ts diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/common/web/types/src/kvk/kvks-file-reader.ts index c12b7036e4..184c162210 100644 --- a/common/web/types/src/kvk/kvks-file-reader.ts +++ b/common/web/types/src/kvk/kvks-file-reader.ts @@ -7,12 +7,6 @@ import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKey import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js'; import { BUILDER_KVK_HEADER_VERSION } from './kvk-file.js'; -export enum KVKSParseErrorType { invalidVkey }; -export class KVKSParseError extends Error { - public type: KVKSParseErrorType; - public vkey: string; -}; - export default class KVKSFileReader { public read(file: Uint8Array): KVKSourceFile { let source: KVKSourceFile; @@ -33,10 +27,12 @@ export default class KVKSFileReader { // rather than using the version tagged on npmjs.com. }); - parser.parseString(file, (e: unknown, r: unknown) => { source = r as KVKSourceFile }); - source = this.boxArrays(source); - this.cleanupFlags(source); - this.cleanupUnderscore('visualkeyboard', source.visualkeyboard); + parser.parseString(file, (e: unknown, r: unknown) => { if(e) { throw e }; source = r as KVKSourceFile }); + if(source) { + source = this.boxArrays(source); + this.cleanupFlags(source); + this.cleanupUnderscore('visualkeyboard', source.visualkeyboard); + } return source; } @@ -80,7 +76,7 @@ export default class KVKSFileReader { } } - public transform(source: KVKSourceFile, errors?: KVKSParseError[]): VisualKeyboard { + public transform(source: KVKSourceFile, invalidVkeys?: string[]): VisualKeyboard { // NOTE: at this point, the xml should have been validated // and matched the schema result so we can assume properties exist let result: VisualKeyboard = { @@ -118,11 +114,8 @@ export default class KVKSFileReader { for(let sourceKey of layer.key) { let vkey = (USVirtualKeyCodes as any)[sourceKey.$?.vkey]; if(!vkey) { - if(errors) { - let e = new KVKSParseError(); - e.type = KVKSParseErrorType.invalidVkey; - e.vkey = sourceKey.$?.vkey; - errors.push(e); + if(typeof invalidVkeys !== 'undefined') { + invalidVkeys.push(sourceKey.$?.vkey); } continue; } diff --git a/common/web/types/test/kvk/test-kvk-round-trip.ts b/common/web/types/test/kvk/test-kvk-round-trip.ts index 41dbe2dbc0..031ad60b9e 100644 --- a/common/web/types/test/kvk/test-kvk-round-trip.ts +++ b/common/web/types/test/kvk/test-kvk-round-trip.ts @@ -5,7 +5,7 @@ import Hexy from 'hexy'; import gitDiff from 'git-diff'; const { hexy } = Hexy; import { loadSchema, makePathToFixture } from '../helpers/index.js'; -import KvksFileReader, { KVKSParseError } from "../../src/kvk/kvks-file-reader.js"; +import KvksFileReader from "../../src/kvk/kvks-file-reader.js"; import KvkFileReader from "../../src/kvk/kvk-file-reader.js"; import KvkFileWriter from "../../src/kvk/kvk-file-writer.js"; import KvksFileWriter from "../../src/kvk/kvks-file-writer.js"; @@ -53,9 +53,9 @@ describe('kvks-file-reader', function () { assert.doesNotThrow(() => { reader.validate(kvks, loadSchema('kvks')); }); - const errors: KVKSParseError[] = []; - const vk = reader.transform(kvks, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk = reader.transform(kvks, invalidVkeys); + assert.isEmpty(invalidVkeys); const writer = new KvkFileWriter(); const output = writer.write(vk); assertBufferMatch(Buffer.from(output), compiled); @@ -76,9 +76,9 @@ describe('kvks-file-reader', function () { assert.doesNotThrow(() => { kvksReader.validate(kvks, loadSchema('kvks')); }); - const errors: KVKSParseError[] = []; - const vk2 = kvksReader.transform(kvks, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk2 = kvksReader.transform(kvks, invalidVkeys); + assert.isEmpty(invalidVkeys); // make sure the binary is the same assert.deepEqual(vk2, vk); diff --git a/common/web/types/test/kvk/test-kvks-file.ts b/common/web/types/test/kvk/test-kvks-file.ts index 523bfaf54d..4cbb299a00 100644 --- a/common/web/types/test/kvk/test-kvks-file.ts +++ b/common/web/types/test/kvk/test-kvks-file.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import 'mocha'; import { loadSchema, makePathToFixture } from '../helpers/index.js'; -import KvksFileReader, { KVKSParseError } from "../../src/kvk/kvks-file-reader.js"; +import KvksFileReader from "../../src/kvk/kvks-file-reader.js"; import KvksFileWriter from "../../src/kvk/kvks-file-writer.js"; import { verify_khmer_angkor } from './test-kvk-utils.js'; import { assert } from 'chai'; @@ -16,9 +16,9 @@ describe('kvks-file-reader', function() { assert.doesNotThrow(() => { reader.validate(kvks, loadSchema('kvks')); }); - const errors: KVKSParseError[] = []; - const vk = reader.transform(kvks, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk = reader.transform(kvks, invalidVkeys); + assert.isEmpty(invalidVkeys); verify_khmer_angkor(vk); }); }); @@ -30,9 +30,9 @@ describe('kvks-file-writer', function() { const reader = new KvksFileReader(); const kvksExpected = reader.read(input); - const errors: KVKSParseError[] = []; - const vk = reader.transform(kvksExpected, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk = reader.transform(kvksExpected, invalidVkeys); + assert.isEmpty(invalidVkeys); const writer = new KvksFileWriter(); const output = writer.write(vk); diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 3c6df80e7e..4761fc5c3b 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -88,18 +88,7 @@ export class KmnCompiler { } public run(infile: string, outfile: string, options?: CompilerOptions): boolean { - if(!this.verifyInitialized()) { - /* c8 ignore next 2 */ - return false; - } - - options = {...baseOptions, ...options}; - (globalThis as any)[this.callbackID] = { - message: this.compilerMessageCallback, - loadFile: this.loadFileCallback - }; let result = this.runCompiler(infile, outfile, options); - delete (globalThis as any)[this.callbackID]; if(result) { if(result.kmx) { this.callbacks.fs.writeFileSync(result.kmx.filename, result.kmx.data); @@ -133,9 +122,8 @@ export class KmnCompiler { } if(bufferSize != data.byteLength) { - // TODO: consider chucking a wobbly because this is a bug #8885 /* c8 ignore next 2 */ - return 0; + throw new Error(`Second call, expected file size ${bufferSize} == ${data.byteLength}`); } this.Module.HEAP8.set(data, buffer); @@ -143,7 +131,19 @@ export class KmnCompiler { return 1; } - private runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { + public runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { + if(!this.verifyInitialized()) { + /* c8 ignore next 2 */ + return null; + } + + options = {...baseOptions, ...options}; + + (globalThis as any)[this.callbackID] = { + message: this.compilerMessageCallback, + loadFile: this.loadFileCallback + }; + let result: CompilerResult = {}; let wasm_interface = new this.Module.CompilerInterface(); let wasm_options = new this.Module.CompilerOptions(); @@ -183,6 +183,7 @@ export class KmnCompiler { } wasm_interface.delete(); wasm_options.delete(); + delete (globalThis as any)[this.callbackID]; } } @@ -190,22 +191,19 @@ export class KmnCompiler { // The compiler detected a .kvks file, which needs to be captured let reader = new KvksFileReader(); kvksFilename = this.callbacks.resolveFilename(kmnFilename, kvksFilename); - let kvks = reader.read(this.callbacks.loadFile(kvksFilename)); + let filename = this.callbacks.path.basename(kvksFilename); + let kvks = null; try { + kvks = reader.read(this.callbacks.loadFile(kvksFilename)); reader.validate(kvks, this.callbacks.loadSchema('kvks')); } catch(e) { - console.log(e); - // TODO: also unit test #8886 - // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); + this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({filename, e})); return null; } - let errors: any = []; //TODO: KVKSParseError[]; - let vk = reader.transform(kvks, errors); - if(!vk || errors.length) { - console.dir(errors); - // TODO: also unit test #8886 - // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); - return null; + let invalidVkeys: string[] = []; + let vk = reader.transform(kvks, invalidVkeys); + for(let invalidVkey of invalidVkeys) { + this.callbacks.reportMessage(CompilerMessages.Warn_InvalidVkeyInKvksFile({filename, invalidVkey})); } let writer = new KvkFileWriter(); return { diff --git a/developer/src/kmc-kmn/src/compiler/messages.ts b/developer/src/kmc-kmn/src/compiler/messages.ts index 10578870d7..0769096e07 100644 --- a/developer/src/kmc-kmn/src/compiler/messages.ts +++ b/developer/src/kmc-kmn/src/compiler/messages.ts @@ -67,6 +67,14 @@ export class CompilerMessages { static Error_UnicodeSetSyntaxError = () => m(this.ERROR_UnicodeSetSyntaxError, `UnicodeSet had a Syntax Error while parsing`); static ERROR_UnicodeSetSyntaxError = SevError | 0x1007; + + static Error_InvalidKvksFile = (o:{filename: string, e: any}) => m(this.ERROR_InvalidKvksFile, + `Error encountered parsing ${o.filename}: ${o.e}`); + static ERROR_InvalidKvksFile = SevError | 0x1008; + + static Warn_InvalidVkeyInKvksFile = (o:{filename: string, invalidVkey: string}) => m(this.WARN_InvalidVkeyInKvksFile, + `Invalid virtual key ${o.invalidVkey} found in ${o.filename}`); + static WARN_InvalidVkeyInKvksFile = SevWarn | 0x1009; } export function mapErrorFromKmcmplib(line: number, code: number, msg: string): CompilerEvent { diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kmn new file mode 100644 index 0000000000..f37d6397c5 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmc throws an error with an invalid .kvks file + +store(&NAME) 'error_invalid_kvks_file' +store(&VERSION) '9.0' +store(&VISUALKEYBOARD) 'error_invalid_kvks_file.kvks' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kvks b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kvks new file mode 100644 index 0000000000..c681064f5b --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kvks @@ -0,0 +1,9 @@ + + +
+ 10.0 + caps_lock_layer_3620 + + +
+
diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kmn new file mode 100644 index 0000000000..18a88973e9 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmc reports a warning with an invalid vkey in the kvks + +store(&NAME) 'warn_invalid_vkey_in_kvks_file' +store(&VERSION) '9.0' +store(&VISUALKEYBOARD) 'warn_invalid_vkey_in_kvks_file.kvks' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks new file mode 100644 index 0000000000..ff441fd7c5 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks @@ -0,0 +1,15 @@ + + +
+ 10.0 + caps_lock_layer_3620 + + +
+ + + + ážž + + +
diff --git a/developer/src/kmc-kmn/test/helpers/index.ts b/developer/src/kmc-kmn/test/helpers/index.ts new file mode 100644 index 0000000000..8250dfd101 --- /dev/null +++ b/developer/src/kmc-kmn/test/helpers/index.ts @@ -0,0 +1,17 @@ +/** + * Helpers and utilities for the Mocha tests. + */ +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +/** + * Builds a path to the fixture with the given path components. + * + * e.g., makePathToFixture('example.qaa.trivial') + * e.g., makePathToFixture('example.qaa.trivial', 'model.ts') + * + * @param components One or more path components. + */ + export function makePathToFixture(...components: string[]): string { + return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url)); +} diff --git a/developer/src/kmc-kmn/test/test-messages.ts b/developer/src/kmc-kmn/test/test-messages.ts index 0dee53f121..dce0c2477d 100644 --- a/developer/src/kmc-kmn/test/test-messages.ts +++ b/developer/src/kmc-kmn/test/test-messages.ts @@ -1,9 +1,54 @@ import 'mocha'; +import { assert } from 'chai'; import { CompilerMessages } from '../src/compiler/messages.js'; -import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; +import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; +import { makePathToFixture } from './helpers/index.js'; +import { KmnCompiler } from '../src/main.js'; describe('CompilerMessages', function () { + const callbacks = new TestCompilerCallbacks(); + it('should have a valid CompilerMessages object', function() { return verifyCompilerMessagesObject(CompilerMessages); }); + + // + // Message tests + // + + async function testForMessage(context: Mocha.Context, fixture: string[], messageId?: number) { + context.timeout(10000); + + callbacks.clear(); + + const compiler = new KmnCompiler(); + assert(await compiler.init(callbacks)); + assert(compiler.verifyInitialized()); + + const kmnPath = makePathToFixture(...fixture); + const outfile = callbacks.path.basename(kmnPath, '.kmn') + '.kmx'; + + // Note: throwing away compile results (just to memory) + compiler.runCompiler(kmnPath, outfile, {saveDebug: true, shouldAddCompilerVersion: false}); + + if(messageId) { + assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); + assert.lengthOf(callbacks.messages, 1); + } else { + assert.lengthOf(callbacks.messages, 0, `messages should be empty, but instead got: `+JSON.stringify(callbacks.messages,null,2)); + } + } + + // ERROR_InvalidKvksFile + + it('should generate ERROR_InvalidKvksFile if the kvks is not valid XML', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_invalid_kvks_file.kmn'], CompilerMessages.ERROR_InvalidKvksFile); + }); + + // WARN_InvalidVkeyInKvksFile + + it('should generate WARN_InvalidVkeyInKvksFile if the kvks contains an invalid virtual key', async function() { + await testForMessage(this, ['invalid-keyboards', 'warn_invalid_vkey_in_kvks_file.kmn'], CompilerMessages.WARN_InvalidVkeyInKvksFile); + }); + }); diff --git a/developer/src/kmc-kmn/test/tsconfig.json b/developer/src/kmc-kmn/test/tsconfig.json index 115412f57f..f61df15d4a 100644 --- a/developer/src/kmc-kmn/test/tsconfig.json +++ b/developer/src/kmc-kmn/test/tsconfig.json @@ -13,7 +13,8 @@ }, }, "include": [ - "**/test-*.ts" + "**/test-*.ts", + "./helpers/index.ts" ], "references": [ { "path": "../../../../common/web/keyman-version/tsconfig.esm.json" }, -- GitLab From 717b1eea9889db65c12c27e33ed871a7c0e85f6f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Jun 2023 08:04:34 +1000 Subject: [PATCH 327/386] chore: Apply code review suggestion Co-authored-by: Eberhard Beilharz --- .../invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks | 1 - 1 file changed, 1 deletion(-) diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks index ff441fd7c5..f7b247db34 100644 --- a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks @@ -3,7 +3,6 @@
10.0 caps_lock_layer_3620 -
-- GitLab From c3ecae4e98ce2898949bb0d60e68b3802b4e37b0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Jun 2023 05:29:19 +0700 Subject: [PATCH 328/386] refactor(developer): rearrange kmcmplib interface source Fixes #8889. No code changes, just moves WASM interfaces into CompilerInterfacesWasm.cpp, and CompileKeyboardHandle is renamed to CompileKeyboardBuffer and moved into its own source file. --- .../kmcmplib/src/CompileKeyboardBuffer.cpp | 166 ++++++++++ .../src/kmcmplib/src/CompileKeyboardBuffer.h | 5 + .../src/kmcmplib/src/CompilerInterfaces.cpp | 285 +----------------- .../kmcmplib/src/CompilerInterfacesWasm.cpp | 117 +++++++ developer/src/kmcmplib/src/meson.build | 2 + 5 files changed, 292 insertions(+), 283 deletions(-) create mode 100644 developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp create mode 100644 developer/src/kmcmplib/src/CompileKeyboardBuffer.h create mode 100644 developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp diff --git a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp new file mode 100644 index 0000000000..c932f68a4f --- /dev/null +++ b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp @@ -0,0 +1,166 @@ +#include "pch.h" +#include +#include "kmcmplib.h" +#include "CheckFilenameConsistency.h" +#include "CheckNCapsConsistency.h" +#include "DeprecationChecks.h" +#include "versioning.h" +#include "CompileKeyboardBuffer.h" +#include "../../../../common/windows/cpp/include/keymanversion.h" + +bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) +{ + PKMX_WCHAR str, p; + + KMX_DWORD msg; + + kmcmp::FMnemonicLayout = FALSE; + + if (!fk) { + AddCompileError(CERR_SomewhereIGotItWrong); + return FALSE; + } + + str = new KMX_WCHAR[LINESIZE]; + if (!str) { + AddCompileError(CERR_CannotAllocateMemory); + return FALSE; + } + + fk->KeyboardID = 0; + fk->version = 0; + fk->dpStoreArray = NULL; + fk->dpGroupArray = NULL; + fk->cxStoreArray = 0; + fk->cxGroupArray = 0; + fk->StartGroup[0] = fk->StartGroup[1] = -1; + fk->szName[0] = 0; + fk->szCopyright[0] = 0; + fk->dwFlags = KF_AUTOMATICVERSION; + fk->currentGroup = 0xFFFFFFFF; + fk->currentStore = 0; + fk->cxDeadKeyArray = 0; + fk->dpDeadKeyArray = NULL; + fk->cxVKDictionary = 0; // I3438 + fk->dpVKDictionary = NULL; // I3438 + fk->extra->kvksFilename = u""; +/* fk->szMessage[0] = 0; + fk->szLanguageName[0] = 0;*/ + fk->dwBitmapSize = 0; + fk->dwHotKey = 0; + + kmcmp::BeginLine[BEGIN_ANSI] = -1; + kmcmp::BeginLine[BEGIN_UNICODE] = -1; + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; + + + /* Add a store for the Keyman 6.0 copyright information string */ + + if(kmcmp::FShouldAddCompilerVersion) { + u16sprintf(str,LINESIZE, L"Created with Keyman Developer version %d.%d.%d.%d", KEYMAN_VersionMajor, KEYMAN_VersionMinor, KEYMAN_VersionPatch, 0); + AddStore(fk, TSS_KEYMANCOPYRIGHT, str); + } + + /* Add a system store for the Keyman edition number */ + AddStore(fk, TSS_CUSTOMKEYMANEDITION, u"0"); + AddStore(fk, TSS_CUSTOMKEYMANEDITIONNAME, u"Keyman"); + + int offset = 0; + + // must preprocess for group and store names -> this isn't really necessary, but never mind! + while ((msg = ReadLine(infile, sz, offset, str, TRUE)) == CERR_None) + { + p = str; + switch (LineTokenType(&p)) + { + case T_VERSION: + *(p + 4) = 0; + if ((msg = AddStore(fk, TSS_VERSION, p)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + break; + + case T_GROUP: + if ((msg = ProcessGroupLine(fk, p)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + break; + + case T_STORE: + if ((msg = ProcessStoreLine(fk, p)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + break; + + default: + break; + } + } + + if (msg != CERR_EndOfFile) { + AddCompileError(msg); + return FALSE; + } + + offset = 0; + kmcmp::currentLine = 0; + + /* Reindex the list of codeconstants after stores added */ + + kmcmp::CodeConstants->reindex(); + + /* ReadLine will automatically skip over $Keyman lines, and parse wrapped lines */ + while ((msg = ReadLine(infile, sz, offset, str, FALSE)) == CERR_None) + { + msg = ParseLine(fk, str); + if (msg != CERR_None) { + AddCompileError(msg); + return FALSE; + } + } + + if (msg != CERR_EndOfFile) { + AddCompileError(msg); + return FALSE; + } + + ProcessGroupFinish(fk); + + if (kmcmp::FSaveDebug) kmcmp::RecordDeadkeyNames(fk); + + /* Add the compiler version as a system store */ + if ((msg = kmcmp::AddCompilerVersionStore(fk)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + + if ((msg = BuildVKDictionary(fk)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + + if ((msg = CheckFilenameConsistencyForCalls(fk)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + + delete str; + + if (!kmcmp::CheckKeyboardFinalVersion(fk)) { + return FALSE; + } + + /* Warn on inconsistent use of NCAPS */ + if (!kmcmp::FMnemonicLayout) { + CheckNCapsConsistency(fk); + } + + /* Flag presence of deprecated features */ + kmcmp::CheckForDeprecatedFeatures(fk); + + return TRUE; +} diff --git a/developer/src/kmcmplib/src/CompileKeyboardBuffer.h b/developer/src/kmcmplib/src/CompileKeyboardBuffer.h new file mode 100644 index 0000000000..78bac66653 --- /dev/null +++ b/developer/src/kmcmplib/src/CompileKeyboardBuffer.h @@ -0,0 +1,5 @@ +#pragma once + +#include "compfile.h" + +bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index 16b5b98654..fdfbdf1961 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -1,134 +1,9 @@ #include "pch.h" - #include #include #include "kmcmplib.h" -#include "CheckFilenameConsistency.h" -#include "CheckNCapsConsistency.h" -#include "DeprecationChecks.h" -#include "versioning.h" #include "../../../../common/windows/cpp/include/ConvertUTF.h" -#include "../../../../common/windows/cpp/include/keymanversion.h" - -bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); - -#ifdef __EMSCRIPTEN__ -// TODO: move emscripten wrappers into their own .cpp. Also move CompileKeyboardHandle -// into its own .cpp, so CompilerInterfaces.cpp has only C public API functions listed. -// #8889 - -/* - WASM interface for compiler message callback -*/ -EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context), { - const proc = globalThis[UTF8ToString(context)].message; - if(!proc || typeof proc != 'function') { - console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); - return 0; - } else { - return proc(line, msgcode, UTF8ToString(text)); - } -}); - -EM_JS(int, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int bufferSize, char* context), { - const proc = globalThis[UTF8ToString(context)].loadFile; - if(!proc || typeof proc != 'function') { - return 0; - } else { - if(buffer == 0) { - return proc(UTF8ToString(filename), UTF8ToString(baseFilename), 0, 0); - } else { - return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); - } - } -}); - -bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buffer, int* bufferSize, void* context) { - char* msgProc = static_cast(context); - if(buffer == nullptr) { - *bufferSize = wasm_loadfileproc(filename, baseFilename, 0, 0, msgProc); - return *bufferSize != 0; - } else { - return wasm_loadfileproc(filename, baseFilename, buffer, *bufferSize, msgProc) == 1; - } -} - -int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, void* context) { - char* msgProc = static_cast(context); - return wasm_msgproc(line, dwMsgCode, szText, msgProc); -} - -struct WASM_COMPILER_INTERFACE { - std::string callbacksKey; // key of callbacks object on globalThis -}; - -struct WASM_COMPILER_RESULT { - bool result; - // Following are pointer offsets in heap + buffer size - int kmx; - int kmxSize; - // Following are compiler side-channel data, required for - // follow-on transform - std::string kvksFilename; - // TODO: additional data to be passed back -}; - -WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPILER_OPTIONS options, const WASM_COMPILER_INTERFACE intf) { - WASM_COMPILER_RESULT r = {false}; - KMCMP_COMPILER_RESULT kr; - - r.kmx = 0; - r.kmxSize = 0; - r.kvksFilename = ""; - - r.result = kmcmp_CompileKeyboard( - pszInfile.c_str(), - options, - wasm_CompilerMessageProc, - wasm_LoadFileProc, - intf.callbacksKey.c_str(), - kr - ); - - if(r.result) { - // TODO: additional data as required by kmc_kmw - r.kmx = (int) kr.kmx; - r.kmxSize = (int) kr.kmxSize; - r.kvksFilename = kr.kvksFilename; - } - - return r; -} - -EMSCRIPTEN_BINDINGS(compiler_interface) { - - emscripten::class_("CompilerOptions") - .constructor<>() - .property("saveDebug", &KMCMP_COMPILER_OPTIONS::saveDebug) - .property("compilerWarningsAsErrors", &KMCMP_COMPILER_OPTIONS::compilerWarningsAsErrors) - .property("warnDeprecatedCode", &KMCMP_COMPILER_OPTIONS::warnDeprecatedCode) - .property("shouldAddCompilerVersion", &KMCMP_COMPILER_OPTIONS::shouldAddCompilerVersion) - .property("target", &KMCMP_COMPILER_OPTIONS::target) - ; - - emscripten::class_("CompilerInterface") - .constructor<>() - .property("callbacksKey", &WASM_COMPILER_INTERFACE::callbacksKey) - ; - - emscripten::class_("CompilerResult") - .constructor<>() - .property("result", &WASM_COMPILER_RESULT::result) - .property("kmx", &WASM_COMPILER_RESULT::kmx) - .property("kmxSize", &WASM_COMPILER_RESULT::kmxSize) - .property("kvksFilename", &WASM_COMPILER_RESULT::kvksFilename) - ; - - emscripten::function("kmcmp_compile", &kmcmp_wasm_compile); - emscripten::function("kmcmp_parseUnicodeSet", &kmcmp_parseUnicodeSet); -} - -#endif +#include "CompileKeyboardBuffer.h" EXTERN bool kmcmp_CompileKeyboard( const char* pszInfile, @@ -204,7 +79,7 @@ EXTERN bool kmcmp_CompileKeyboard( } kmcmp::CodeConstants = new kmcmp::NamedCodeConstants; - bool success = CompileKeyboardHandle(infile+offset, sz-offset, &fk); + bool success = CompileKeyboardBuffer(infile+offset, sz-offset, &fk); delete kmcmp::CodeConstants; delete[] infile; @@ -233,159 +108,3 @@ EXTERN bool kmcmp_CompileKeyboard( return TRUE; } -bool CompileKeyboardHandle(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) -{ - PKMX_WCHAR str, p; - - KMX_DWORD msg; - - kmcmp::FMnemonicLayout = FALSE; - - if (!fk) { - AddCompileError(CERR_SomewhereIGotItWrong); - return FALSE; - } - - str = new KMX_WCHAR[LINESIZE]; - if (!str) { - AddCompileError(CERR_CannotAllocateMemory); - return FALSE; - } - - fk->KeyboardID = 0; - fk->version = 0; - fk->dpStoreArray = NULL; - fk->dpGroupArray = NULL; - fk->cxStoreArray = 0; - fk->cxGroupArray = 0; - fk->StartGroup[0] = fk->StartGroup[1] = -1; - fk->szName[0] = 0; - fk->szCopyright[0] = 0; - fk->dwFlags = KF_AUTOMATICVERSION; - fk->currentGroup = 0xFFFFFFFF; - fk->currentStore = 0; - fk->cxDeadKeyArray = 0; - fk->dpDeadKeyArray = NULL; - fk->cxVKDictionary = 0; // I3438 - fk->dpVKDictionary = NULL; // I3438 - fk->extra->kvksFilename = u""; -/* fk->szMessage[0] = 0; - fk->szLanguageName[0] = 0;*/ - fk->dwBitmapSize = 0; - fk->dwHotKey = 0; - - kmcmp::BeginLine[BEGIN_ANSI] = -1; - kmcmp::BeginLine[BEGIN_UNICODE] = -1; - kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; - kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; - - - /* Add a store for the Keyman 6.0 copyright information string */ - - if(kmcmp::FShouldAddCompilerVersion) { - u16sprintf(str,LINESIZE, L"Created with Keyman Developer version %d.%d.%d.%d", KEYMAN_VersionMajor, KEYMAN_VersionMinor, KEYMAN_VersionPatch, 0); - AddStore(fk, TSS_KEYMANCOPYRIGHT, str); - } - - /* Add a system store for the Keyman edition number */ - AddStore(fk, TSS_CUSTOMKEYMANEDITION, u"0"); - AddStore(fk, TSS_CUSTOMKEYMANEDITIONNAME, u"Keyman"); - - int offset = 0; - - // must preprocess for group and store names -> this isn't really necessary, but never mind! - while ((msg = ReadLine(infile, sz, offset, str, TRUE)) == CERR_None) - { - p = str; - switch (LineTokenType(&p)) - { - case T_VERSION: - *(p + 4) = 0; - if ((msg = AddStore(fk, TSS_VERSION, p)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - break; - - case T_GROUP: - if ((msg = ProcessGroupLine(fk, p)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - break; - - case T_STORE: - if ((msg = ProcessStoreLine(fk, p)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - break; - - default: - break; - } - } - - if (msg != CERR_EndOfFile) { - AddCompileError(msg); - return FALSE; - } - - offset = 0; - kmcmp::currentLine = 0; - - /* Reindex the list of codeconstants after stores added */ - - kmcmp::CodeConstants->reindex(); - - /* ReadLine will automatically skip over $Keyman lines, and parse wrapped lines */ - while ((msg = ReadLine(infile, sz, offset, str, FALSE)) == CERR_None) - { - msg = ParseLine(fk, str); - if (msg != CERR_None) { - AddCompileError(msg); - return FALSE; - } - } - - if (msg != CERR_EndOfFile) { - AddCompileError(msg); - return FALSE; - } - - ProcessGroupFinish(fk); - - if (kmcmp::FSaveDebug) kmcmp::RecordDeadkeyNames(fk); - - /* Add the compiler version as a system store */ - if ((msg = kmcmp::AddCompilerVersionStore(fk)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - - if ((msg = BuildVKDictionary(fk)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - - if ((msg = CheckFilenameConsistencyForCalls(fk)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - - delete str; - - if (!kmcmp::CheckKeyboardFinalVersion(fk)) { - return FALSE; - } - - /* Warn on inconsistent use of NCAPS */ - if (!kmcmp::FMnemonicLayout) { - CheckNCapsConsistency(fk); - } - - /* Flag presence of deprecated features */ - kmcmp::CheckForDeprecatedFeatures(fk); - - return TRUE; -} diff --git a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp new file mode 100644 index 0000000000..a20cd9f1b4 --- /dev/null +++ b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp @@ -0,0 +1,117 @@ +#include "pch.h" +#include + +#ifdef __EMSCRIPTEN__ + +/* + WASM interface for compiler message callback +*/ +EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context), { + const proc = globalThis[UTF8ToString(context)].message; + if(!proc || typeof proc != 'function') { + console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); + return 0; + } else { + return proc(line, msgcode, UTF8ToString(text)); + } +}); + +EM_JS(int, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int bufferSize, char* context), { + const proc = globalThis[UTF8ToString(context)].loadFile; + if(!proc || typeof proc != 'function') { + return 0; + } else { + if(buffer == 0) { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), 0, 0); + } else { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + } + } +}); + +bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buffer, int* bufferSize, void* context) { + char* msgProc = static_cast(context); + if(buffer == nullptr) { + *bufferSize = wasm_loadfileproc(filename, baseFilename, 0, 0, msgProc); + return *bufferSize != 0; + } else { + return wasm_loadfileproc(filename, baseFilename, buffer, *bufferSize, msgProc) == 1; + } +} + +int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, void* context) { + char* msgProc = static_cast(context); + return wasm_msgproc(line, dwMsgCode, szText, msgProc); +} + +struct WASM_COMPILER_INTERFACE { + std::string callbacksKey; // key of callbacks object on globalThis +}; + +struct WASM_COMPILER_RESULT { + bool result; + // Following are pointer offsets in heap + buffer size + int kmx; + int kmxSize; + // Following are compiler side-channel data, required for + // follow-on transform + std::string kvksFilename; + // TODO: additional data to be passed back +}; + +WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPILER_OPTIONS options, const WASM_COMPILER_INTERFACE intf) { + WASM_COMPILER_RESULT r = {false}; + KMCMP_COMPILER_RESULT kr; + + r.kmx = 0; + r.kmxSize = 0; + r.kvksFilename = ""; + + r.result = kmcmp_CompileKeyboard( + pszInfile.c_str(), + options, + wasm_CompilerMessageProc, + wasm_LoadFileProc, + intf.callbacksKey.c_str(), + kr + ); + + if(r.result) { + // TODO: additional data as required by kmc_kmw + r.kmx = (int) kr.kmx; + r.kmxSize = (int) kr.kmxSize; + r.kvksFilename = kr.kvksFilename; + } + + return r; +} + +EMSCRIPTEN_BINDINGS(compiler_interface) { + + emscripten::class_("CompilerOptions") + .constructor<>() + .property("saveDebug", &KMCMP_COMPILER_OPTIONS::saveDebug) + .property("compilerWarningsAsErrors", &KMCMP_COMPILER_OPTIONS::compilerWarningsAsErrors) + .property("warnDeprecatedCode", &KMCMP_COMPILER_OPTIONS::warnDeprecatedCode) + .property("shouldAddCompilerVersion", &KMCMP_COMPILER_OPTIONS::shouldAddCompilerVersion) + .property("target", &KMCMP_COMPILER_OPTIONS::target) + ; + + emscripten::class_("CompilerInterface") + .constructor<>() + .property("callbacksKey", &WASM_COMPILER_INTERFACE::callbacksKey) + ; + + emscripten::class_("CompilerResult") + .constructor<>() + .property("result", &WASM_COMPILER_RESULT::result) + .property("kmx", &WASM_COMPILER_RESULT::kmx) + .property("kmxSize", &WASM_COMPILER_RESULT::kmxSize) + .property("kvksFilename", &WASM_COMPILER_RESULT::kvksFilename) + ; + + emscripten::function("kmcmp_compile", &kmcmp_wasm_compile); + emscripten::function("kmcmp_parseUnicodeSet", &kmcmp_parseUnicodeSet); +} + +#endif diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index e697a0dced..2ca06929bc 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -37,8 +37,10 @@ icuuc_dep = icu.get_variable('icuuc_dep') lib = library('kmcmplib', 'CasedKeys.cpp', 'CharToKeyConversion.cpp', + 'CompileKeyboardBuffer.cpp', 'Compiler.cpp', 'CompilerInterfaces.cpp', + 'CompilerInterfacesWasm.cpp', 'DeprecationChecks.cpp', 'Edition.cpp', 'NamedCodeConstants.cpp', -- GitLab From c5bd7611908ad5dc93c1eb3abeae56e96c47395c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Jun 2023 07:07:00 +0700 Subject: [PATCH 329/386] chore(ios): replace fv cert --- oem/firstvoices/ios/exportAppStore.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oem/firstvoices/ios/exportAppStore.plist b/oem/firstvoices/ios/exportAppStore.plist index 123af31a0c..c4ac972730 100644 --- a/oem/firstvoices/ios/exportAppStore.plist +++ b/oem/firstvoices/ios/exportAppStore.plist @@ -7,9 +7,9 @@ teamID D7TR486TEH signingCertificate - EFA9DE793B2A25F38270468AE62060CFF55CD634 + C4726D4C8BD4C2FE38A551B84CEDB8E4B3FD12D6 installerSigningCertificate - EFA9DE793B2A25F38270468AE62060CFF55CD634 + C4726D4C8BD4C2FE38A551B84CEDB8E4B3FD12D6 provisioningProfiles com.firstvoices.keyboards -- GitLab From 5e668e15678ce63155cdae0bf3d0e0dab2c41c15 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 26 May 2023 15:16:02 +0700 Subject: [PATCH 330/386] change(web): worker-embedding via JSON.stringify --- common/predictive-text/src/unwrap.ts | 2 +- common/web/lm-worker/build-wrap-and-minify.js | 30 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/common/predictive-text/src/unwrap.ts b/common/predictive-text/src/unwrap.ts index 410c9db718..4199a3d760 100644 --- a/common/predictive-text/src/unwrap.ts +++ b/common/predictive-text/src/unwrap.ts @@ -6,6 +6,6 @@ * @param fn The function whose body will be returned. */ export default function unwrap(encodedSrc: string): string { - let wrapper = decodeURIComponent(encodedSrc); + let wrapper = JSON.parse(encodedSrc); return wrapper; } \ No newline at end of file diff --git a/common/web/lm-worker/build-wrap-and-minify.js b/common/web/lm-worker/build-wrap-and-minify.js index 0b816f02dd..16e0a6f99a 100644 --- a/common/web/lm-worker/build-wrap-and-minify.js +++ b/common/web/lm-worker/build-wrap-and-minify.js @@ -65,11 +65,39 @@ const srcMapString = `//# sourceMappingURL=data:application/json;charset=utf-8;b * but my attempts to do so end up triggering errors when loading. */ +let rawScript = workerConcatenation.script.toString(); + +let start = performance.now(); +let jsonEncoded = JSON.stringify(rawScript); +let end = performance.now(); +console.log(`JSON.stringify time taken: ${end-start}`); +console.log(`JSON.stringify size: ${jsonEncoded.length}`); + +let start3 = performance.now(); +let jsonParsed = JSON.parse(jsonEncoded); +let end3 = performance.now(); +console.log(`JSON.parse time taken: ${end3-start3}`); + +// Two layers of encoding: one for the raw source (parsed by the JS engine), +// one to 'unwrap' it from a string _within_ that source. +let jsonDoubleEncoded = JSON.stringify(jsonEncoded); + +let start2 = performance.now(); +let uriEncoded = encodeURIComponent(rawScript); +let end2 = performance.now(); +console.log(`encodeURIComponent time taken: ${end2-start2}`); +console.log(`encodeURIComponent size: ${uriEncoded.length}`); + +let start4 = performance.now(); +decodeURIComponent(uriEncoded); +let end4 = performance.now(); +console.log(`decodeURIComponent time taken: ${end4-start4}`); + let wrapper = ` // Autogenerated code. Do not modify! // --START:LMLayerWorkerCode-- -export var LMLayerWorkerCode = \`${encodeURIComponent(workerConcatenation.script.toString())}\` +export var LMLayerWorkerCode = ${jsonDoubleEncoded}; ${MINIFY && "// Sourcemaps have been omitted for this release build."} export var LMLayerWorkerSourcemapComment = "${DEBUG ? srcMapString : ''}"; -- GitLab From 645ddcf8ffdc95e98b010ea71b0593c23fb78cf3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 26 May 2023 15:17:00 +0700 Subject: [PATCH 331/386] change(web): enables invisible identifier minification --- web/src/app/browser/build-bundler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 17a495e02a..ba49bccc5a 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -70,7 +70,7 @@ let result = await esbuild.build({ sourcemap: true, minifyWhitespace: true, minifySyntax: true, - minifyIdentifiers: false, + minifyIdentifiers: true, format: "iife", nodePaths: ['../../../../node_modules'], entryPoints: { -- GitLab From c5f527c20489198f362de43b1bad932fa22be4dc Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 1 Jun 2023 08:54:47 +0700 Subject: [PATCH 332/386] chore(web): cleans up temp time-profiling code for the lm-worker --- common/web/lm-worker/build-wrap-and-minify.js | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/common/web/lm-worker/build-wrap-and-minify.js b/common/web/lm-worker/build-wrap-and-minify.js index 16e0a6f99a..e5aef61060 100644 --- a/common/web/lm-worker/build-wrap-and-minify.js +++ b/common/web/lm-worker/build-wrap-and-minify.js @@ -66,32 +66,9 @@ const srcMapString = `//# sourceMappingURL=data:application/json;charset=utf-8;b */ let rawScript = workerConcatenation.script.toString(); - -let start = performance.now(); -let jsonEncoded = JSON.stringify(rawScript); -let end = performance.now(); -console.log(`JSON.stringify time taken: ${end-start}`); -console.log(`JSON.stringify size: ${jsonEncoded.length}`); - -let start3 = performance.now(); -let jsonParsed = JSON.parse(jsonEncoded); -let end3 = performance.now(); -console.log(`JSON.parse time taken: ${end3-start3}`); - // Two layers of encoding: one for the raw source (parsed by the JS engine), // one to 'unwrap' it from a string _within_ that source. -let jsonDoubleEncoded = JSON.stringify(jsonEncoded); - -let start2 = performance.now(); -let uriEncoded = encodeURIComponent(rawScript); -let end2 = performance.now(); -console.log(`encodeURIComponent time taken: ${end2-start2}`); -console.log(`encodeURIComponent size: ${uriEncoded.length}`); - -let start4 = performance.now(); -decodeURIComponent(uriEncoded); -let end4 = performance.now(); -console.log(`decodeURIComponent time taken: ${end4-start4}`); +let jsonDoubleEncoded = JSON.stringify(JSON.stringify(rawScript)); let wrapper = ` // Autogenerated code. Do not modify! -- GitLab From 4658fa0f860ffe07b9bc69e6932612dadacde687 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 1 Jun 2023 11:35:06 +0700 Subject: [PATCH 333/386] feat(web): keyman.views anchorpoint for OSK classes --- web/src/app/browser/src/keymanEngine.ts | 21 +++----- web/src/app/browser/src/viewsAnchorpoint.ts | 55 +++++++++++++++++++++ 2 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 web/src/app/browser/src/viewsAnchorpoint.ts diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 74c240f10d..8d1509df84 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -3,9 +3,6 @@ import { Device as DeviceDetector } from 'keyman/engine/device-detect'; import { getAbsoluteY } from 'keyman/engine/dom-utils'; import { OutputTarget } from 'keyman/engine/element-wrappers'; import { - AnchoredOSKView, - FloatingOSKView, - FloatingOSKViewConfiguration, OSKView, TwoStateActivator, VisualKeyboard @@ -13,6 +10,7 @@ import { import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId as prefixed } from 'keyman/engine/package-cache'; import { DeviceSpec, Keyboard, ProcessorInitOptions, extendString } from "@keymanapp/keyboard-processor"; +import * as views from './viewsAnchorpoint.js'; import { BrowserConfiguration, BrowserInitOptionDefaults, BrowserInitOptionSpec } from './configuration.js'; import { default as ContextManager } from './contextManager.js'; import DefaultBrowserRules from './defaultBrowserRules.js'; @@ -88,6 +86,11 @@ export default class KeymanEngine extends KeymanEngineBase Date: Thu, 1 Jun 2023 11:36:29 +0700 Subject: [PATCH 334/386] chore(developer): attempt #1 to fix dev build, KMW integration --- developer/src/server/build.sh | 6 ++---- developer/src/server/src/site/test.js | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 53585f1df6..11109898aa 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -124,20 +124,18 @@ fi if (( build_keymanweb )); then pushd "$KEYMAN_ROOT/web/" - ./build.sh --no-minify + ./build.sh build popd fi if (( copy_keymanweb )); then - WEB_SRC="$KEYMAN_ROOT/web/build/app/web/debug" - UI_SRC="$KEYMAN_ROOT/web/build/app/ui/debug" + WEB_SRC="$KEYMAN_ROOT/web/publish/debug" DST="$(dirname "$THIS_SCRIPT")/src/site/resource" rm -rf "$DST" mkdir -p "$DST/osk" mkdir -p "$DST/ui" cp "$WEB_SRC/"*.js "$WEB_SRC/"*.js.map "$DST/" - cp "$UI_SRC/"*.js "$UI_SRC/"*.js.map "$DST/" cp -R "$WEB_SRC/osk/"* "$DST/osk/" cp -R "$WEB_SRC/ui/"* "$DST/ui/" cp "$KEYMAN_ROOT/web/LICENSE" "$DST/" diff --git a/developer/src/server/src/site/test.js b/developer/src/server/src/site/test.js index 954ac203c4..de27deccc5 100644 --- a/developer/src/server/src/site/test.js +++ b/developer/src/server/src/site/test.js @@ -221,7 +221,7 @@ window.onload = function() { // Create a new on screen keyboard view and tell KeymanWeb that // we are using the targetDevice for context input. - newOSK = new com.keyman.osk.InlinedOSKView(targetDevice, keyman.util.device.coreSpec); + newOSK = new keyman.views.InlinedOSKView(keyman, { device: targetDevice }); keyman.core.contextDevice = targetDevice; keyman.osk = newOSK; -- GitLab From 47f47a05c91d1b6ab6ddcd83aca475a1c46a27d1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 1 Jun 2023 12:18:15 +0700 Subject: [PATCH 335/386] fix(developer): forgot a path component --- developer/src/server/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 11109898aa..a4fff54af8 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -129,7 +129,7 @@ if (( build_keymanweb )); then fi if (( copy_keymanweb )); then - WEB_SRC="$KEYMAN_ROOT/web/publish/debug" + WEB_SRC="$KEYMAN_ROOT/web/build/publish/debug" DST="$(dirname "$THIS_SCRIPT")/src/site/resource" rm -rf "$DST" -- GitLab From c95ba9d94d7d476fe94590d4fa76710c419062f7 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 1 Jun 2023 14:53:50 +0700 Subject: [PATCH 336/386] fix(common/models): missing lm-worker dependencies --- common/web/lm-worker/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/web/lm-worker/build.sh b/common/web/lm-worker/build.sh index 9e544501cb..75a72df12d 100755 --- a/common/web/lm-worker/build.sh +++ b/common/web/lm-worker/build.sh @@ -28,6 +28,8 @@ WORKER_OUTPUT_FILENAME=build/lib/worker-main.js builder_describe \ "Compiles the Language Modeling Layer for common use in predictive text and autocorrective applications." \ "@/common/web/keyman-version" \ + "@/common/models/wordbreakers" \ + "@/common/models/templates" \ "@/common/tools/sourcemap-path-remapper" \ configure clean build test --ci -- GitLab From 75058eb6237e349f60e9ef20c5b5156365cc5fc1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 1 Jun 2023 15:04:49 +0700 Subject: [PATCH 337/386] chore(web): updates per PR review --- common/web/keyboard-processor/src/keyboards/keyboard.ts | 4 ++-- web/src/engine/attachment/src/pageContextAttachment.ts | 2 +- web/src/engine/element-wrappers/src/contentEditable.ts | 4 ++-- web/src/engine/element-wrappers/src/designIFrame.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/web/keyboard-processor/src/keyboards/keyboard.ts b/common/web/keyboard-processor/src/keyboards/keyboard.ts index b99286030e..82c3883785 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboard.ts +++ b/common/web/keyboard-processor/src/keyboards/keyboard.ts @@ -504,8 +504,8 @@ export default class Keyboard { * (implicit 'NO_CAPS') layer but not a 'caps' layer. With caps set, it just * highlights the key on the 'default' layer instead. * - * 'Caps' could thus be logical-ORed with 'no-caps' below by mistake. We should - * never have both set at the same time under any condition. + * We should never set both `CAPS` and `NO_CAPS` at the same time, and + * same for the other modifiers. */ Lkc.Lstates = 0; Lkc.Lstates |= stateKeys['K_CAPS'] ? Codes.modifierCodes['CAPS'] : Codes.modifierCodes['NO_CAPS']; diff --git a/web/src/engine/attachment/src/pageContextAttachment.ts b/web/src/engine/attachment/src/pageContextAttachment.ts index e0a5b5d47c..ce115a9ddc 100644 --- a/web/src/engine/attachment/src/pageContextAttachment.ts +++ b/web/src/engine/attachment/src/pageContextAttachment.ts @@ -681,7 +681,7 @@ export class PageContextAttachment extends EventEmitter { } - /** if(!this.isAttached(Pelem)) { + /** * Function disableControl * Scope Public * @param {Element} Pelem Element to be disabled diff --git a/web/src/engine/element-wrappers/src/contentEditable.ts b/web/src/engine/element-wrappers/src/contentEditable.ts index e80686ac50..1754e2c90b 100644 --- a/web/src/engine/element-wrappers/src/contentEditable.ts +++ b/web/src/engine/element-wrappers/src/contentEditable.ts @@ -103,12 +103,12 @@ export default class ContentEditable extends OutputTarget<{}> { } getDeadkeyCaret(): number { - return (this.getTextBeforeCaret() ?? this.getText()).kmwLength(); + return this.getTextBeforeCaret().kmwLength(); } getTextBeforeCaret(): string { if(!this.hasSelection()) { - return; + return this.getText(); } let caret = this.getCarets().start; diff --git a/web/src/engine/element-wrappers/src/designIFrame.ts b/web/src/engine/element-wrappers/src/designIFrame.ts index 3c88630f3e..10d8a76a93 100644 --- a/web/src/engine/element-wrappers/src/designIFrame.ts +++ b/web/src/engine/element-wrappers/src/designIFrame.ts @@ -125,12 +125,12 @@ export default class DesignIFrame extends OutputTarget<{}> { } getDeadkeyCaret(): number { - return (this.getTextBeforeCaret() ?? this.getText()).kmwLength(); + return this.getTextBeforeCaret().kmwLength(); } getTextBeforeCaret(): string { if(!this.hasSelection()) { - return; + return this.getText(); } let caret = this.getCarets().start; -- GitLab From c3458d040cfe8950db7253367d88f78f06b9ff18 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Jun 2023 15:11:38 +0700 Subject: [PATCH 338/386] refactor(developer): move filename consistency check to kmc kmcmplib no longer has any filesystem access, so it cannot verify if a referenced filename in a source file has the same case as the actual filename on disk (a risk when moving projects between platforms). So I opted to move this to the `loadFile` callback in kmc, which is the only place where filesystem is actually accessed, and added corresponding unit test. Small additional fixes here: 1. Move from `Buffer` to `Uint8Array` in all kmc-* modules, so that we remove that barrier to running on web. 2. Use `callbacks.loadFile` instead of `callbacks.fs.readFileSync`, so that we can be sure to run the filename consistency check. 3. Fixed kps parser silently swallowing xml errors on load. 4. Added silent mode to NodeCompilerCallbacks so we could cleanly test the new filename consistency hint. 5. Noted a location where we still have NodeJS deps in kmc-ldml. --- common/web/types/src/kpj/kpj-file-reader.ts | 4 +- common/web/types/src/kvk/kvks-file-reader.ts | 4 +- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 11 +- .../web/types/src/util/compiler-interfaces.ts | 6 +- .../test/helpers/TestCompilerCallbacks.ts | 2 +- .../src/common/include/kmn_compiler_errors.h | 4 +- .../src/common/web/test-helpers/index.ts | 4 +- developer/src/kmc-kmn/test/test-messages.ts | 3 +- .../kmc-package/src/compiler/kmp-compiler.ts | 27 ++++- .../kmc/src/messages/NodeCompilerCallbacks.ts | 48 +++++++- developer/src/kmc/src/messages/messages.ts | 6 +- .../hint_filename_has_differing_case.kmn | 6 + developer/src/kmc/test/test-messages.ts | 71 +++++++++++ .../kmcmplib/src/CheckFilenameConsistency.cpp | 112 ------------------ .../kmcmplib/src/CheckFilenameConsistency.h | 9 -- developer/src/kmcmplib/src/CompMsg.cpp | 2 - .../kmcmplib/src/CompileKeyboardBuffer.cpp | 6 - developer/src/kmcmplib/src/Compiler.cpp | 14 --- .../src/kmcmplib/src/NamedCodeConstants.cpp | 5 - developer/src/kmcmplib/src/meson.build | 16 +-- 20 files changed, 174 insertions(+), 186 deletions(-) create mode 100644 developer/src/kmc/test/fixtures/invalid-keyboards/hint_filename_has_differing_case.kmn delete mode 100644 developer/src/kmcmplib/src/CheckFilenameConsistency.cpp delete mode 100644 developer/src/kmcmplib/src/CheckFilenameConsistency.h diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index 5f768c54f1..21e92c975b 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -27,8 +27,8 @@ export class KPJFileReader { return data as KPJFile; } - public validate(source: KPJFile, schemaBuffer: Buffer): void { - const schema = JSON.parse(schemaBuffer.toString('utf8')); + public validate(source: KPJFile, schemaBuffer: Uint8Array): void { + const schema = JSON.parse(new TextDecoder().decode(schemaBuffer)); const ajv = new Ajv(); if(!ajv.validate(schema, source)) { throw new Error(ajv.errorsText()); diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/common/web/types/src/kvk/kvks-file-reader.ts index 184c162210..13c6c8716c 100644 --- a/common/web/types/src/kvk/kvks-file-reader.ts +++ b/common/web/types/src/kvk/kvks-file-reader.ts @@ -68,8 +68,8 @@ export default class KVKSFileReader { } } - public validate(source: KVKSourceFile, schemaBuffer: Buffer): void { - const schema = JSON.parse(schemaBuffer.toString('utf8')); + public validate(source: KVKSourceFile, schemaBuffer: Uint8Array): void { + const schema = JSON.parse(new TextDecoder().decode(schemaBuffer)); const ajv = new Ajv(); if(!ajv.validate(schema, source)) { throw new Error(ajv.errorsText()); diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 35d7f62795..bc2779ab8e 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -7,6 +7,7 @@ import { CompilerCallbacks } from '../util/compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; import { CommonTypesMessages } from '../util/common-events.js'; import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js'; +import { fileURLToPath } from 'url'; interface NameAndProps { '$'?: any; // content @@ -21,9 +22,9 @@ export default class LDMLKeyboardXMLSourceFileReader { this.callbacks = callbacks; } - readImportFile(version: string, subpath: string): Buffer { - // TODO-LDML: sanitize input string - let importPath = new URL(`../import/${version}/${subpath}`, import.meta.url); + readImportFile(version: string, subpath: string): Uint8Array { + // TODO-LDML: use this.callbacks.resolveFilename to get the actual path + let importPath = fileURLToPath(new URL(`../import/${version}/${subpath}`, import.meta.url)); return this.callbacks.loadFile(importPath); } @@ -201,8 +202,8 @@ export default class LDMLKeyboardXMLSourceFileReader { /** * @returns true if valid, false if invalid */ - public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile, schemaSource: Buffer): boolean { - const schema = JSON.parse(schemaSource.toString('utf8')); + public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile, schemaSource: Uint8Array): boolean { + const schema = JSON.parse(new TextDecoder().decode(schemaSource)); const ajv = new Ajv(); if(!ajv.validate(schema, source)) { for (let err of ajv.errors) { diff --git a/common/web/types/src/util/compiler-interfaces.ts b/common/web/types/src/util/compiler-interfaces.ts index 849e945321..d3932b7bfa 100644 --- a/common/web/types/src/util/compiler-interfaces.ts +++ b/common/web/types/src/util/compiler-interfaces.ts @@ -120,13 +120,11 @@ export interface CompilerFileSystemCallbacks { export interface CompilerCallbacks { /** * Attempt to load a file. Return falsy if not found. - * TODO: accept only string * TODO: never return falsy, just throw if not found? - * TODO: Buffer is Node-only. * @param baseFilename * @param filename */ - loadFile(filename: string | URL): Buffer; + loadFile(filename: string): Uint8Array; get path(): CompilerPathCallbacks; get fs(): CompilerFileSystemCallbacks; @@ -138,7 +136,7 @@ export interface CompilerCallbacks { */ resolveFilename(baseFilename: string, filename: string): string; - loadSchema(schema: CompilerSchema): Buffer; + loadSchema(schema: CompilerSchema): Uint8Array; reportMessage(event: CompilerEvent): void; debug(msg: string): void; }; diff --git a/common/web/types/test/helpers/TestCompilerCallbacks.ts b/common/web/types/test/helpers/TestCompilerCallbacks.ts index 596a731d58..8316264a77 100644 --- a/common/web/types/test/helpers/TestCompilerCallbacks.ts +++ b/common/web/types/test/helpers/TestCompilerCallbacks.ts @@ -42,7 +42,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks { return resolveFilename(baseFilename, filename); } - loadFile(filename: string | URL): Buffer { + loadFile(filename: string): Uint8Array { // TODO: error management, does it belong here? try { return loadFile(filename); diff --git a/developer/src/common/include/kmn_compiler_errors.h b/developer/src/common/include/kmn_compiler_errors.h index 6cfae03fc9..31c5faf0c7 100644 --- a/developer/src/common/include/kmn_compiler_errors.h +++ b/developer/src/common/include/kmn_compiler_errors.h @@ -224,8 +224,8 @@ #define CWARN_KeyShouldIncludeNCaps 0x000020AD #define CHINT_UnreachableRule 0x000010AE -#define CHINT_FilenameHasDifferingCase 0x000010AF -#define CWARN_MissingFile 0x000020B0 +#define CHINT_FilenameHasDifferingCase 0x000010AF // only used in kmcmpdll +#define CWARN_MissingFile 0x000020B0 // only used in kmcmpdll #define CERR_BufferOverflow 0x000080C0 #define CERR_Break 0x000080C1 diff --git a/developer/src/common/web/test-helpers/index.ts b/developer/src/common/web/test-helpers/index.ts index 7f34107dc9..0a85287d3d 100644 --- a/developer/src/common/web/test-helpers/index.ts +++ b/developer/src/common/web/test-helpers/index.ts @@ -36,7 +36,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks { /* CompilerCallbacks */ - loadFile(filename: string | URL): Buffer { + loadFile(filename: string): Uint8Array { try { return fs.readFileSync(filename); } catch(e) { @@ -77,7 +77,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks { this.messages.push(event); } - loadSchema(schema: CompilerSchema): Buffer { + loadSchema(schema: CompilerSchema): Uint8Array { return fs.readFileSync(new URL(SCHEMA_BASE + schema + '.schema.json', import.meta.url)); } diff --git a/developer/src/kmc-kmn/test/test-messages.ts b/developer/src/kmc-kmn/test/test-messages.ts index dce0c2477d..4d724daaf5 100644 --- a/developer/src/kmc-kmn/test/test-messages.ts +++ b/developer/src/kmc-kmn/test/test-messages.ts @@ -1,4 +1,5 @@ import 'mocha'; +import path from 'path'; import { assert } from 'chai'; import { CompilerMessages } from '../src/compiler/messages.js'; import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; @@ -26,7 +27,7 @@ describe('CompilerMessages', function () { assert(compiler.verifyInitialized()); const kmnPath = makePathToFixture(...fixture); - const outfile = callbacks.path.basename(kmnPath, '.kmn') + '.kmx'; + const outfile = path.basename(kmnPath, '.kmn') + '.kmx'; // Note: throwing away compile results (just to memory) compiler.runCompiler(kmnPath, outfile, {saveDebug: true, shouldAddCompilerVersion: false}); diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index b660641df9..198a1ef2de 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -16,7 +16,12 @@ export class KmpCompiler { public transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile { // Load the KPS data from XML as JS structured data. - const data = this.callbacks.fs.readFileSync(kpsFilename, 'utf-8'); + const buffer = this.callbacks.loadFile(kpsFilename); + if(!buffer) { + this.callbacks.reportMessage(CompilerMessages.Error_FileDoesNotExist({filename: kpsFilename})); + return null; + } + const data = new TextDecoder().decode(buffer); const kpsPackage = (() => { let a: KpsFile.KpsPackage; @@ -24,7 +29,8 @@ export class KmpCompiler { tagNameProcessors: [xml2js.processors.firstCharLowerCase], explicitArray: false }); - parser.parseString(data, (e: unknown, r: unknown) => { a = r as KpsFile.KpsPackage }); + // TODO: add unit test for xml errors parsing .kps file + parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as KpsFile.KpsPackage }); return a; })(); @@ -284,9 +290,20 @@ export class KmpCompiler { * we want that to remain the responsibility of the keyboard compiler, so we'll warn the * few users who are still doing this */ - private warnIfKvkFileIsNotBinary(filename: string, data: Buffer) { - // TODO: Buffer is not available on web - if(filename.match(/\.kvk$/) && data.compare(Buffer.from(KvkFile.KVK_HEADER_IDENTIFIER_BYTES), 0, 3, 0, 3) != 0) { + private warnIfKvkFileIsNotBinary(filename: string, data: Uint8Array) { + if(!filename.match(/\.kvk$/)) { + return; + } + + if(data.byteLength < 4) { + // TODO: Not a valid .kvk file; should we be reporting this? + return; + } + + if(data[0] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[0] || + data[1] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[1] || + data[2] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[2] || + data[3] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[3]) { this.callbacks.reportMessage(CompilerMessages.Warn_FileIsNotABinaryKvkFile({filename: filename})); } } diff --git a/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts index ceae18c4c5..f8232cde5f 100644 --- a/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts @@ -1,14 +1,52 @@ import * as fs from 'fs'; import * as path from 'path'; import { CompilerCallbacks, CompilerSchema, CompilerEvent, compilerErrorSeverityName, CompilerPathCallbacks, CompilerFileSystemCallbacks } from '@keymanapp/common-types'; +import { InfrastructureMessages } from './messages.js'; /** * Concrete implementation for CLI use */ +// TODO: Make a common class for all the CompilerCallbacks implementations + export class NodeCompilerCallbacks implements CompilerCallbacks { - // TODO: REMOVE! - loadFile(filename: string | URL): Buffer { + /* NodeCompilerCallbacks */ + + messages: CompilerEvent[] = []; + silent: boolean; + + constructor(silent?: boolean) { + this.silent = !!silent; + } + + clear() { + this.messages = []; + } + + hasMessage(code: number): boolean { + return this.messages.find((item) => item.code == code) === undefined ? false : true; + } + + private verifyFilenameConsistency(originalFilename: string): void { + if(fs.existsSync(originalFilename)) { + // Note, we only check this if the file exists, because + // if it is not found, that will be returned as an error + // from loadFile anyway. + const filename = fs.realpathSync(originalFilename); + const nativeFilename = fs.realpathSync.native(filename); + if(filename != nativeFilename) { + this.reportMessage(InfrastructureMessages.Hint_FilenameHasDifferingCase({ + reference: originalFilename, + filename: nativeFilename + })); + } + } + } + + /* CompilerCallbacks */ + + loadFile(filename: string): Uint8Array { + this.verifyFilenameConsistency(filename); try { return fs.readFileSync(filename); } catch (e) { @@ -29,6 +67,10 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { } reportMessage(event: CompilerEvent): void { + this.messages.push(event); + if(this.silent) { + return; + } const code = event.code.toString(16); if(event.line) { console.log(`${compilerErrorSeverityName(event.code)} ${code} [${event.line}]: ${event.message}`); @@ -41,7 +83,7 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { console.debug(msg); } - loadSchema(schema: CompilerSchema) { + loadSchema(schema: CompilerSchema): Uint8Array { let schemaPath = new URL('../util/' + schema + '.schema.json', import.meta.url); return fs.readFileSync(schemaPath); } diff --git a/developer/src/kmc/src/messages/messages.ts b/developer/src/kmc/src/messages/messages.ts index 1f1761e715..358a59406b 100644 --- a/developer/src/kmc/src/messages/messages.ts +++ b/developer/src/kmc/src/messages/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const Namespace = CompilerErrorNamespace.Infrastructure; const SevInfo = CompilerErrorSeverity.Info | Namespace; -// const SevHint = CompilerErrorSeverity.Hint | Namespace; +const SevHint = CompilerErrorSeverity.Hint | Namespace; // const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -39,5 +39,9 @@ export class InfrastructureMessages { static Error_InvalidProjectFile = (o:{message:string}) => m(this.ERROR_InvalidProjectFile, `Project file is not valid: ${o.message}`); static ERROR_InvalidProjectFile = SevError | 0x0008; + + static Hint_FilenameHasDifferingCase = (o:{reference:string, filename:string}) => m(this.HINT_FilenameHasDifferingCase, + `File ${o.filename} differs in case from reference ${o.reference}; this will fail on platforms with case-sensitive filesystems.`); + static HINT_FilenameHasDifferingCase = SevHint | 0x0009; } diff --git a/developer/src/kmc/test/fixtures/invalid-keyboards/hint_filename_has_differing_case.kmn b/developer/src/kmc/test/fixtures/invalid-keyboards/hint_filename_has_differing_case.kmn new file mode 100644 index 0000000000..5792016e10 --- /dev/null +++ b/developer/src/kmc/test/fixtures/invalid-keyboards/hint_filename_has_differing_case.kmn @@ -0,0 +1,6 @@ +store(&name) 'hint_filename_has_differing_case' +store(&version) '7.0' + +begin unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc/test/test-messages.ts b/developer/src/kmc/test/test-messages.ts index 7b591ad72d..045718075d 100644 --- a/developer/src/kmc/test/test-messages.ts +++ b/developer/src/kmc/test/test-messages.ts @@ -1,9 +1,80 @@ import 'mocha'; +import { assert } from 'chai'; import { InfrastructureMessages } from '../src/messages/messages.js'; import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; +import { makePathToFixture } from './helpers/index.js'; +import { NodeCompilerCallbacks } from '../src/messages/NodeCompilerCallbacks.js'; describe('InfrastructureMessages', function () { it('should have a valid InfrastructureMessages object', function() { return verifyCompilerMessagesObject(InfrastructureMessages); }); + + // + // Message tests + // + + /* + TODO: + + let callbacks = new TestCompilerCallbacks(); + + async function testForMessage(context: Mocha.Context, fixture: string[], messageId?: number) { + context.timeout(10000); + + callbacks.clear(); + + const builder = new BuildKmnKeyboard(); + const path = makePathToFixture(...fixture); + let result = await builder.build(path, callbacks, { + compilerVersion: false, + compilerWarningsAsErrors: true, + debug: false, + warnDeprecatedCode: true, + }); + + if(messageId) { + assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); + assert.lengthOf(callbacks.messages, 1); + } else { + assert.lengthOf(callbacks.messages, 0, `messages should be empty, but instead got: `+JSON.stringify(callbacks.messages,null,2)); + assert.isTrue(result); + } + } + + // ERROR_FileDoesNotExist + + it('should generate ERROR_FileDoesNotExist if a file does not exist', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_file_does_not_exist.kmn'], CompilerMessages.ERROR_FileDoesNotExist); + }); + + // ERROR_FileTypeNotRecognized + + it('should generate ERROR_FileTypeNotRecognized if a file is not a recognized type', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_file_type_not_recognized.xxx'], CompilerMessages.ERROR_FileTypeNotRecognized); + }); + + // ERROR_OutFileNotValidForProjects + + it('should generate ERROR_OutFileNotValidForProjects if an output file is specified for a project build', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_out_file_not_valid_for_projects.kpj'], CompilerMessages.ERROR_OutFileNotValidForProjects); + }); + + // ERROR_InvalidProjectFile + + it('should generate ERROR_InvalidProjectFile if a project file is invalid', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_invalid_project_file.kpj'], CompilerMessages.ERROR_InvalidProjectFile); + }); + */ + + // HINT_FilenameHasDifferingCase + + it('should generate HINT_FilenameHasDifferingCase if a referenced file has differing case', async function() { + // This message is generated by NodeCompilerCallbacks, because that's where the filesystem is visible, + // so we can't use our usual testForMessage pattern. + const ncb = new NodeCompilerCallbacks(true); + ncb.loadFile(makePathToFixture('invalid-keyboards', 'Hint_Filename_Has_Differing_Case.kmn')); + assert.isTrue(ncb.hasMessage(InfrastructureMessages.HINT_FilenameHasDifferingCase), + `HINT_FilenameHasDifferingCase not generated, instead got: `+JSON.stringify(ncb.messages,null,2)); + }); }); diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp deleted file mode 100644 index 6d113d116b..0000000000 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp +++ /dev/null @@ -1,112 +0,0 @@ - -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1 -#include "pch.h" -#include "compfile.h" -#include -#include "kmcmplib.h" -#include -#include "CheckFilenameConsistency.h" -#include "kmx_u16.h" - -#ifdef _MSC_VER -#include -#endif - - - -KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissingFile) { - PKMX_WCHAR WFilename = strtowstr(( KMX_CHAR *)Filename); - KMX_DWORD const result = CheckFilenameConsistency(WFilename, ReportMissingFile); - delete WFilename; - return result; -} - -KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile) { - // TODO: we no longer have filesystem access here. We could move this check to - // kmc itself, and make it consistent across all compilers that use the same - // loader callback -- see #8883 - return CERR_None; - -#if 0 - // not ready yet: needs more attention-> common includes for non-Windows platforms - KMX_WCHAR Name[260]; // TODO: fixed buffer sizes bad - - - if (IsRelativePath(Filename)) { - PKMX_WCHAR WCompileDir = strtowstr(kmcmp::CompileDir); - u16ncpy(Name, WCompileDir, _countof(Name)); // I3481 - u16ncat(Name, Filename, _countof(Name)); // I3481 - delete[] WCompileDir; - } else { - u16ncpy(Name, Filename, _countof(Name)); // I3481 - } - -#ifndef _MSC_VER - // Filename consistency only needs to be checked on Windows, because other - // platforms are going to fail if the filename is inconsistent anyway! - if(!kmcmp_FileExists(Name)) { - if (ReportMissingFile) { - u16cpy(ErrExtraW, u"referenced file '"); - u16ncat(ErrExtraW, Filename, 256); - u16ncat(ErrExtraW, u"'", 256); - strcpy(ErrExtraLIB, string_from_u16string(ErrExtraW).c_str()); - AddWarning(CWARN_MissingFile); - } - return CERR_None; - } - return CERR_None; -#else - _wfinddata_t fi; - intptr_t n; - if ((n = _wfindfirst((const wchar_t*) Name, &fi)) == -1) { - if (ReportMissingFile) { - sprintf(ErrExtraLIB, "referenced file '%ls'", (wchar_t*) Filename); - AddWarning(CWARN_MissingFile); - } - return CERR_None; - } - - _findclose(n); - - KMX_WCHAR FName[_MAX_FNAME], Ext[_MAX_EXT]; - wchar_t WChName[_MAX_PATH]; - _wsplitpath_s((const wchar_t*)Filename, nullptr, 0, nullptr, 0, (wchar_t*) FName, _MAX_FNAME, (wchar_t*) Ext, _MAX_EXT); - _wmakepath_s(WChName, _MAX_PATH, nullptr, nullptr, (const wchar_t*) FName, (const wchar_t*) Ext); - if (wcscmp(WChName, fi.name) != 0) { - sprintf(ErrExtraLIB, "reference '%ls' does not match actual filename '%ls'", WChName, fi.name); - - AddWarning(CHINT_FilenameHasDifferingCase); - - } -#endif - - return CERR_None; -#endif -} - -KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk) { - // call() statements depend on a fairly ugly hack for js, - // where store(DllFunction) "my.dll:func" will look for a - // file called function.call_js. ( or should this be func.call_js ? ) - // This is ripe for rewrite! - // But let's check what we have anyway - - PFILE_STORE sp; - KMX_DWORD i, msg; - for (i = 0, sp = fk->dpStoreArray; i < fk->cxStoreArray; i++, sp++) { - if (!sp->fIsCall) continue; - - const std::u16string callsite(sp->dpString); - const auto colon = callsite.find(':'); - if (colon == std::u16string::npos) continue; - - auto func1 = callsite.substr(colon + 1); - std::u16string str_js(u".call_js"); - std::u16string func = func1+ str_js; - - if ((msg = CheckFilenameConsistency(func.c_str(), FALSE)) != CERR_None) { - return msg; - } - } - return CERR_None; -} diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.h b/developer/src/kmcmplib/src/CheckFilenameConsistency.h deleted file mode 100644 index eba8f2d8bf..0000000000 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "compfile.h" -#include "kmcmplib.h" - -KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk); -KMX_DWORD CheckFilenameConsistency(KMX_CHAR const * Filename, bool ReportMissingFile); -KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile); - diff --git a/developer/src/kmcmplib/src/CompMsg.cpp b/developer/src/kmcmplib/src/CompMsg.cpp index 2fa0d68322..dd41336a0e 100644 --- a/developer/src/kmcmplib/src/CompMsg.cpp +++ b/developer/src/kmcmplib/src/CompMsg.cpp @@ -111,7 +111,6 @@ const struct CompilerError CompilerErrors[] = { { CERR_DuplicateStore , "A store with this name has already been defined."}, { CERR_RepeatedBegin , "Begin has already been set"}, - { CHINT_FilenameHasDifferingCase , "Casing differences may fail on some platforms: "}, { CHINT_UnreachableRule , "This rule will never be matched as another rule takes precedence"}, { CWARN_TooManyWarnings , "Too many warnings or errors"}, @@ -142,7 +141,6 @@ const struct CompilerError CompilerErrors[] = { { CWARN_NulNotFirstStatementInContext , "nul must be the first statement in the context"}, { CWARN_IfShouldBeAtStartOfContext , "if, platform and baselayout should be at start of context (after nul, if present)"}, { CWARN_KeyShouldIncludeNCaps , "Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches"}, - { CWARN_MissingFile , "The referenced file could not be found: "}, { 0, nullptr } }; diff --git a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp index c932f68a4f..e90938f18a 100644 --- a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp +++ b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp @@ -1,7 +1,6 @@ #include "pch.h" #include #include "kmcmplib.h" -#include "CheckFilenameConsistency.h" #include "CheckNCapsConsistency.h" #include "DeprecationChecks.h" #include "versioning.h" @@ -143,11 +142,6 @@ bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) return FALSE; } - if ((msg = CheckFilenameConsistencyForCalls(fk)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - delete str; if (!kmcmp::CheckKeyboardFinalVersion(fk)) { diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 49484b1acc..b862cb7d9f 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -97,7 +97,6 @@ #include #include -#include "CheckFilenameConsistency.h" #include "UnreachableRules.h" #include "CheckForDuplicates.h" #include "kmx_u16.h" @@ -1045,10 +1044,6 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE delete[] sp->dpString; sp->dpString = q; - - if ((msg = CheckFilenameConsistency( (sp->dpString), FALSE)) != CERR_None) { - return msg; - } } break; case TSS_KMW_RTL: @@ -1059,16 +1054,10 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE case TSS_KMW_HELPFILE: case TSS_KMW_EMBEDJS: VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } break; case TSS_KMW_EMBEDCSS: VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyEmbedCSS); - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } break; case TSS_TARGETS: // I4504 @@ -1118,9 +1107,6 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE case TSS_LAYOUTFILE: // I3483 VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyLayoutFile); // I4140 - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } // Used by KMW compiler break; diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.cpp b/developer/src/kmcmplib/src/NamedCodeConstants.cpp index 30091b7cb8..a8af8fa783 100644 --- a/developer/src/kmcmplib/src/NamedCodeConstants.cpp +++ b/developer/src/kmcmplib/src/NamedCodeConstants.cpp @@ -24,7 +24,6 @@ #include "pch.h" #include #include "NamedCodeConstants.h" -#include "CheckFilenameConsistency.h" #include #include "kmcompx.h" @@ -117,10 +116,6 @@ char *kmc_strupr(char *s) { KMX_BOOL NamedCodeConstants::LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filename) { const int str_size = 256; - if (CheckFilenameConsistency(filename, FALSE) != 0) { - return FALSE; - } - auto szNameUtf8 = string_from_u16string(filename); int FileSize; diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 2ca06929bc..c6ed2b84b0 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -37,29 +37,25 @@ icuuc_dep = icu.get_variable('icuuc_dep') lib = library('kmcmplib', 'CasedKeys.cpp', 'CharToKeyConversion.cpp', + 'CheckForDuplicates.cpp', + 'CheckNCapsConsistency.cpp', 'CompileKeyboardBuffer.cpp', 'Compiler.cpp', 'CompilerInterfaces.cpp', 'CompilerInterfacesWasm.cpp', + 'CompMsg.cpp', 'DeprecationChecks.cpp', 'Edition.cpp', + 'kmx_u16.cpp', 'NamedCodeConstants.cpp', + 'UnreachableRules.cpp', + 'uset-api.cpp', 'versioning.cpp', 'virtualcharkeys.cpp', 'xstring.cpp', - 'CharToKeyConversion.cpp', - 'CheckFilenameConsistency.cpp', - 'CheckForDuplicates.cpp', - 'CheckNCapsConsistency.cpp', - 'UnreachableRules.cpp', - 'kmx_u16.cpp', - 'CompMsg.cpp', - 'uset-api.cpp', - '../../../../common/windows/cpp/src/ConvertUTF.c', '../../../../common/windows/cpp/src/crc32.cpp', '../../../../common/windows/cpp/src/vkeys.cpp', - 'xstring.cpp', version_res, cpp_args: defns + warns + flags, -- GitLab From 7301281247aaa42160ed7b17d363ce851fd9baf4 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Jun 2023 15:31:31 +0700 Subject: [PATCH 339/386] chore(developer): loadFile callback error check and optimization Fixes #8885. Adds some optimization and error checking to the loadFile callback in kmc-kmn. Also handles case of zero-byte bitmap file so we won't crash on it. --- .../src/kmc-kmn/src/compiler/compiler.ts | 22 ++++++++++++++----- developer/src/kmcmplib/src/Compiler.cpp | 6 +++++ .../kmcmplib/src/CompilerInterfacesWasm.cpp | 2 +- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 4761fc5c3b..9962ea6abd 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -108,12 +108,24 @@ export class KmnCompiler { return 1; } + private cachedFile: {filename: string; data: Uint8Array} = { + filename: null, + data: null + }; + private loadFileCallback = (filename: string, baseFilename: string, buffer: number, bufferSize: number): number => { - // TODO: we can optimize this in future by avoiding loading the file twice #8885 let resolvedFilename = this.callbacks.resolveFilename(baseFilename, filename); - let data = this.callbacks.loadFile(resolvedFilename); - if(!data) { - return 0; + let data: Uint8Array; + if(this.cachedFile.filename == resolvedFilename) { + data = this.cachedFile.data; + } + else { + data = this.callbacks.loadFile(resolvedFilename); + if(!data) { + return -1; + } + this.cachedFile.filename = resolvedFilename; + this.cachedFile.data = data; } if(buffer == 0) { @@ -123,7 +135,7 @@ export class KmnCompiler { if(bufferSize != data.byteLength) { /* c8 ignore next 2 */ - throw new Error(`Second call, expected file size ${bufferSize} == ${data.byteLength}`); + throw new Error(`loadFileCallback: second call, expected file size ${bufferSize} == ${data.byteLength}`); } this.Module.HEAP8.set(data, buffer); diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index b862cb7d9f..e5d5f94cbc 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -3233,6 +3233,12 @@ KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD File } } + if(*FileSize < 2) { + // Zero-byte file is invalid; 2 byte file is too, but we only really care + // about the prolog at this point so we don't overrun our buffer + return CERR_CannotReadBitmapFile; + } + *Buf = new KMX_BYTE[*FileSize]; if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), *Buf, (int*) FileSize, msgprocContext)) { delete[] *Buf; diff --git a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp index a20cd9f1b4..0aab9ba51d 100644 --- a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp @@ -33,7 +33,7 @@ bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buf char* msgProc = static_cast(context); if(buffer == nullptr) { *bufferSize = wasm_loadfileproc(filename, baseFilename, 0, 0, msgProc); - return *bufferSize != 0; + return *bufferSize != -1; } else { return wasm_loadfileproc(filename, baseFilename, buffer, *bufferSize, msgProc) == 1; } -- GitLab From d615659d5ef71236005cdcee94f342bec4bd6d39 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 1 Jun 2023 19:14:17 +0200 Subject: [PATCH 340/386] chore(linux): Address code review comments --- linux/keyman-config/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linux/keyman-config/build.sh b/linux/keyman-config/build.sh index 7f63eaff1e..1cb603f091 100755 --- a/linux/keyman-config/build.sh +++ b/linux/keyman-config/build.sh @@ -34,14 +34,14 @@ build_man_pages() { TEMP_DATA_DIR=$(mktemp -d) SCHEMA_DIR=$TEMP_DATA_DIR/glib-2.0/schemas export XDG_DATA_DIRS=$TEMP_DATA_DIR:${XDG_DATA_DIRS-} - export GSETTINGS_SCHEMA_DIR=${SCHEMA_DIR} + export GSETTINGS_SCHEMA_DIR="${SCHEMA_DIR}" mkdir -p "$SCHEMA_DIR" cp ./com.keyman.gschema.xml "$SCHEMA_DIR"/ glib-compile-schemas "$SCHEMA_DIR" ./build-help.sh --man --no-reconf export XDG_DATA_DIRS=${XDG_DATA_DIRS#*:} unset GSETTINGS_SCHEMA_DIR - rm -rf $TEMP_DATA_DIR + rm -rf "$TEMP_DATA_DIR" } build_action() { -- GitLab From af6e162a27f48422f0ecfccc1c93818e09db6998 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 1 Jun 2023 19:16:28 +0200 Subject: [PATCH 341/386] feat(linux): Rename column and add tooltip This renames the column from "Location" to "Area" and shows only the area instead of the full path. This also adds a tooltip to the row which shows the full path to the keyboard. --- linux/keyman-config/keyman_config/get_kmp.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/linux/keyman-config/keyman_config/get_kmp.py b/linux/keyman-config/keyman_config/get_kmp.py index af7b34063d..ed3f7ec8a4 100755 --- a/linux/keyman-config/keyman_config/get_kmp.py +++ b/linux/keyman-config/keyman_config/get_kmp.py @@ -9,6 +9,7 @@ import requests import requests_cache from gi.repository import GObject +from keyman_config import _ from keyman_config import KeymanApiUrl, KeymanDownloadsUrl from keyman_config.deprecated_decorator import deprecated @@ -20,6 +21,16 @@ class InstallLocation(GObject.GEnum): Unknown = 99 +def get_install_area_string(area): + if area == InstallLocation.OS: + return _('System') + elif area == InstallLocation.Shared: + return _('Shared') + elif area == InstallLocation.User: + return _('User') + return _('Unknown') + + def get_package_download_data(packageID, weekCache=False): """ Get package download data from keyboards download api. -- GitLab From 51cc5afde70ba2e68d285f6be6de22bc0465c47c Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 1 Jun 2023 14:02:40 -0400 Subject: [PATCH 342/386] auto: increment master version to 17.0.116 --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index ff0cc6fbbe..e2c639b852 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 17.0.115 alpha 2023-06-01 + +* refactor(developer): complete fs move out of kmcmplib (#8882) +* fix(common): tweak pack/publish support for npm 9.5.1 and node 18.16.0 (#8894) +* feat(linux): Implement Options page and option to disable Sentry error reporting (#7989) + ## 17.0.114 alpha 2023-05-31 * chore(developer): replace cwrap wasm bindings (#8857) diff --git a/VERSION.md b/VERSION.md index 38d1ece2d6..ce5446d392 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.115 \ No newline at end of file +17.0.116 \ No newline at end of file -- GitLab From 78032df68364a0f19173dbe05f56d7586273305f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 1 Jun 2023 20:16:18 +0200 Subject: [PATCH 343/386] fix(core): Fix compilation if hotdoc is installed Partially fixes #8880. --- core/doc/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/doc/meson.build b/core/doc/meson.build index 2772819ce7..a029383a99 100644 --- a/core/doc/meson.build +++ b/core/doc/meson.build @@ -16,7 +16,7 @@ if hotdoc.found() output: 'hotdoc.json', configuration: cfg) deps = files( - '../include/keyman/keyboardprocessor.h.in', + '../include/keyman/keyboardprocessor.h', '../src/jsonpp.hpp', '../src/utfcodec.hpp' ) -- GitLab From 29bc09b7b679b881577ef4d216279c4bfb68c5f7 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 1 Jun 2023 21:21:35 +0200 Subject: [PATCH 344/386] fix(core): Fix doc generation with hotdoc At least builds now, even though the C API documentation is still empty. --- core/doc/hotdoc.json | 6 +++--- core/doc/meson.build | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/doc/hotdoc.json b/core/doc/hotdoc.json index 61a0028fb0..27a54a876f 100644 --- a/core/doc/hotdoc.json +++ b/core/doc/hotdoc.json @@ -1,10 +1,10 @@ { "project_name": "@project_name@", "project_version": "@project_version@", - "sitemap": "../../doc/sitemap.txt", - "index": "../../doc/markdown_files/index.md", + "sitemap": "@source_dir@/sitemap.txt", + "index": "@source_dir@/markdown_files/index.md", "c_sources": [ - "../include/keyboardprocessor.h" + "@include_dir@/keyboardprocessor.h" ], "c_smart_index" : true, "output": ".", diff --git a/core/doc/meson.build b/core/doc/meson.build index a029383a99..57e96d3259 100644 --- a/core/doc/meson.build +++ b/core/doc/meson.build @@ -12,6 +12,8 @@ if hotdoc.found() cfg = configuration_data() cfg.set('project_name', meson.project_name()) cfg.set('project_version', meson.project_version()) + cfg.set('source_dir', meson.current_source_dir()) + cfg.set('include_dir', meson.current_source_dir() / '../include') configure_file(input: 'hotdoc.json', output: 'hotdoc.json', configuration: cfg) @@ -23,7 +25,7 @@ if hotdoc.found() docs = custom_target('docs', output: ['html'], - input: ['sitemap.txt', 'markdown_files/index.md'], + input: ['sitemap.txt', 'markdown_files/index.md', deps], command: [hotdoc, '--verbose', '--conf-file=doc/hotdoc.json', '--output=doc', 'run'], depend_files: deps, install: true, -- GitLab From b313272250be9869e1947fdac2eca37bed1d7392 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 2 Jun 2023 11:38:17 +0700 Subject: [PATCH 345/386] chore(common): remove url module ref from common/web/types The url module is a node module. We need to move responsibility for resolving the path of the LDML XML statements out of common/web/types, and into the ultimate consumer, so it's now surfaced as an option, along with a helper constant that reports the import.meta.url-relative base path of the standard imports that are compiled into common/web/types. This hopefully means we can use this module in both browser and node contexts without trouble. --- common/web/types/src/kvk/kvks-file-writer.ts | 1 - .../src/ldml-keyboard/ldml-keyboard-xml-reader.ts | 15 ++++++++------- common/web/types/src/main.ts | 2 +- common/web/types/test/helpers/index.ts | 5 ++++- .../types/test/helpers/reader-callback-test.ts | 12 +++++++++--- common/web/types/test/kpj/test-kpj-file-reader.ts | 2 -- developer/src/common/web/test-helpers/index.ts | 5 ++++- .../src/kmc-ldml/src/compiler/compiler-options.ts | 7 ++++++- developer/src/kmc-ldml/src/compiler/compiler.ts | 10 ++++------ developer/src/kmc-ldml/test/helpers/index.ts | 11 ++++++++--- developer/src/kmc-ldml/test/test-compiler-e2e.ts | 4 ++-- .../src/kmc-ldml/test/test-keymanweb-compiler.ts | 8 ++++---- .../src/kmc-ldml/test/test-metadata-compiler.ts | 6 +++--- developer/src/kmc-ldml/test/test-testdata-e2e.ts | 4 ++-- .../test/test-visual-keyboard-compiler-e2e.ts | 4 ++-- .../kmc/src/commands/build/BuildLdmlKeyboard.ts | 8 ++++++-- .../src/kmc/src/commands/buildTestData/index.ts | 8 ++++++-- .../src/kmc/src/messages/NodeCompilerCallbacks.ts | 5 ++++- 18 files changed, 73 insertions(+), 44 deletions(-) diff --git a/common/web/types/src/kvk/kvks-file-writer.ts b/common/web/types/src/kvk/kvks-file-writer.ts index 613c6caa51..1b3a819f58 100644 --- a/common/web/types/src/kvk/kvks-file-writer.ts +++ b/common/web/types/src/kvk/kvks-file-writer.ts @@ -96,7 +96,6 @@ export default class KVKSFileWriter { l.key.push(k); } - // console.dir(kvks, {depth:8}); let result = builder.buildObject(kvks); return result; //Uint8Array.from(result); } diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index bc2779ab8e..a1772a9554 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -7,7 +7,6 @@ import { CompilerCallbacks } from '../util/compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; import { CommonTypesMessages } from '../util/common-events.js'; import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js'; -import { fileURLToPath } from 'url'; interface NameAndProps { '$'?: any; // content @@ -15,16 +14,18 @@ interface NameAndProps { '$$'?: any; // children }; -export default class LDMLKeyboardXMLSourceFileReader { - callbacks: CompilerCallbacks; +export class LDMLKeyboardXMLSourceFileReaderOptions { + importsPath: string; +}; + +export const LDMLKeyboardXMLDefaultImportsURL = new URL(`../import/`, import.meta.url); - constructor(callbacks : CompilerCallbacks) { - this.callbacks = callbacks; +export class LDMLKeyboardXMLSourceFileReader { + constructor(private options: LDMLKeyboardXMLSourceFileReaderOptions, private callbacks : CompilerCallbacks) { } readImportFile(version: string, subpath: string): Uint8Array { - // TODO-LDML: use this.callbacks.resolveFilename to get the actual path - let importPath = fileURLToPath(new URL(`../import/${version}/${subpath}`, import.meta.url)); + const importPath = this.callbacks.resolveFilename(this.options.importsPath, `../import/${version}/${subpath}`); return this.callbacks.loadFile(importPath); } diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 4e77550384..c1fa6a4a0a 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -14,7 +14,7 @@ export * as KvksFile from './kvk/kvk-file.js'; export * as LDMLKeyboard from './ldml-keyboard/ldml-keyboard-xml.js'; export { LDMLKeyboardTestDataXMLSourceFile } from './ldml-keyboard/ldml-keyboard-testdata-xml.js'; -export { default as LDMLKeyboardXMLSourceFileReader } from './ldml-keyboard/ldml-keyboard-xml-reader.js'; +export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions, LDMLKeyboardXMLDefaultImportsURL } from './ldml-keyboard/ldml-keyboard-xml-reader.js'; export * as Constants from './consts/virtual-key-constants.js'; diff --git a/common/web/types/test/helpers/index.ts b/common/web/types/test/helpers/index.ts index 58dc32dc9c..06906df148 100644 --- a/common/web/types/test/helpers/index.ts +++ b/common/web/types/test/helpers/index.ts @@ -28,7 +28,10 @@ export function loadSchema(schema: CompilerSchema): Buffer { } export function resolveFilename(baseFilename: string, filename: string) { - const basePath = path.dirname(baseFilename); + const basePath = + baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? + baseFilename : + path.dirname(baseFilename); // Transform separators to platform separators -- we are agnostic // in our use here but path prefers files may use // either / or \, although older kps files were always \. diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts index 8c85a759ac..a99eca71f4 100644 --- a/common/web/types/test/helpers/reader-callback-test.ts +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -1,11 +1,16 @@ import 'mocha'; import {assert} from 'chai'; import { loadFile, makePathToFixture, loadSchema } from '../helpers/index.js'; -import LDMLKeyboardXMLSourceFileReader from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; +import { LDMLKeyboardXMLDefaultImportsURL, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; import { CompilerEvent } from '../../src/util/compiler-interfaces.js'; import { LDMLKeyboardXMLSourceFile } from '../../src/ldml-keyboard/ldml-keyboard-xml.js'; import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboard-testdata-xml.js'; import { TestCompilerCallbacks } from './TestCompilerCallbacks.js'; +import { fileURLToPath } from 'url'; + +const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = { + importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) +}; export interface CompilationCase { /** @@ -71,7 +76,7 @@ export interface TestDataCase { export function testReaderCases(cases : CompilationCase[]) { // we need our own callbacks rather than using the global so messages don't get mixed const callbacks = new TestCompilerCallbacks(); - const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); for (let testcase of cases) { const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: @@ -79,6 +84,7 @@ export function testReaderCases(cases : CompilationCase[]) { it(testHeading, function () { callbacks.clear(); + debugger; const data = loadFile(makePathToFixture(testcase.subpath)); assert.ok(data, `reading ${testcase.subpath}`); const source = reader.load(data); @@ -122,7 +128,7 @@ export function testReaderCases(cases : CompilationCase[]) { export function testTestdataReaderCases(cases : TestDataCase[]) { // we need our own callbacks rather than using the global so messages don't get mixed const callbacks = new TestCompilerCallbacks(); - const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); for (let testcase of cases) { const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/common/web/types/test/kpj/test-kpj-file-reader.ts index b7c2f71013..95b5f1d01a 100644 --- a/common/web/types/test/kpj/test-kpj-file-reader.ts +++ b/common/web/types/test/kpj/test-kpj-file-reader.ts @@ -15,7 +15,6 @@ describe('kpj-file-reader', function () { const input = fs.readFileSync(path); const reader = new KPJFileReader(callbacks); const kpj = reader.read(input); - console.dir(kpj); assert.doesNotThrow(() => { reader.validate(kpj, loadSchema('kpj')); }); @@ -79,7 +78,6 @@ describe('kpj-file-reader', function () { assert.lengthOf(project.files, 2); let f: KeymanDeveloperProjectFile10 = project.files[0]; - console.dir(f); assert.equal(f.id, 'id_f347675c33d2e6b1c705c787fad4941a'); assert.equal(f.filename, 'khmer_angkor.kmn'); assert.equal(f.filePath, 'source/khmer_angkor.kmn'); diff --git a/developer/src/common/web/test-helpers/index.ts b/developer/src/common/web/test-helpers/index.ts index 0a85287d3d..a134aa67df 100644 --- a/developer/src/common/web/test-helpers/index.ts +++ b/developer/src/common/web/test-helpers/index.ts @@ -57,7 +57,10 @@ export class TestCompilerCallbacks implements CompilerCallbacks { } resolveFilename(baseFilename: string, filename: string): string { - const basePath = path.dirname(baseFilename); + const basePath = + baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? + baseFilename : + path.dirname(baseFilename); // Transform separators to platform separators -- we are agnostic // in our use here but path prefers files may use // either / or \, although older kps files were always \. diff --git a/developer/src/kmc-ldml/src/compiler/compiler-options.ts b/developer/src/kmc-ldml/src/compiler/compiler-options.ts index 3d29e6393a..da81ef2f7d 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler-options.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler-options.ts @@ -1,4 +1,4 @@ - +import { LDMLKeyboardXMLSourceFileReaderOptions } from "@keymanapp/common-types"; export interface CompilerOptions { /** @@ -10,4 +10,9 @@ export interface CompilerOptions { * Add metadata about the compiler version to .kmx file when compiling */ addCompilerVersion?: boolean; + + /** + * Paths and other options required for reading .xml files + */ + readerOptions: LDMLKeyboardXMLSourceFileReaderOptions; }; diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index b7b46e49b2..e753beb317 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -30,16 +30,14 @@ const SECTION_COMPILERS = [ export class LdmlKeyboardCompiler { private readonly callbacks: CompilerCallbacks; - // private readonly options: CompilerOptions; // not currently used + private readonly options: CompilerOptions; // not currently used - constructor (callbacks: CompilerCallbacks, _options?: CompilerOptions) { - /* + constructor (callbacks: CompilerCallbacks, options: CompilerOptions) { this.options = { debug: false, addCompilerVersion: true, ...options }; - */ this.callbacks = callbacks; } @@ -54,7 +52,7 @@ export class LdmlKeyboardCompiler { * @returns the source file, or null if invalid */ public load(filename: string): LDMLKeyboardXMLSourceFile | null { - const reader = new LDMLKeyboardXMLSourceFileReader(this.callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks); const data = this.callbacks.loadFile(filename); if(!data) { this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'})); @@ -84,7 +82,7 @@ export class LdmlKeyboardCompiler { * @returns the source file, or null if invalid */ public loadTestData(filename: string): LDMLKeyboardTestDataXMLSourceFile | null { - const reader = new LDMLKeyboardXMLSourceFileReader(this.callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks); const data = this.callbacks.loadFile(filename); if(!data) { this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'})); diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index 8ba8a28aa8..a493af82f1 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -5,7 +5,7 @@ import 'mocha'; import * as path from 'path'; import { fileURLToPath } from 'url'; import { SectionCompiler } from '../../src/compiler/section-compiler.js'; -import { KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; +import { KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLDefaultImportsURL } from '@keymanapp/common-types'; import { LdmlKeyboardCompiler } from '../../src/compiler/compiler.js'; import { assert } from 'chai'; import { KMXPlusMetadataCompiler } from '../../src/compiler/metadata-compiler.js'; @@ -33,6 +33,12 @@ export function makePathToFixture(...components: string[]): string { export const compilerTestCallbacks = new TestCompilerCallbacks(); +export const compilerTestOptions: CompilerOptions = { + readerOptions: { + importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) + } +}; + beforeEach(function() { compilerTestCallbacks.clear(); }); @@ -43,14 +49,13 @@ afterEach(function() { } }); - export function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: TestCompilerCallbacks): Section { callbacks.messages = []; const inputFilename = makePathToFixture(filename); const data = callbacks.loadFile(inputFilename); assert.isNotNull(data); - const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(compilerTestOptions.readerOptions, callbacks); const source = reader.load(data); assert.isNotNull(source); diff --git a/developer/src/kmc-ldml/test/test-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-compiler-e2e.ts index ec71154905..7d8b179f31 100644 --- a/developer/src/kmc-ldml/test/test-compiler-e2e.ts +++ b/developer/src/kmc-ldml/test/test-compiler-e2e.ts @@ -2,7 +2,7 @@ import 'mocha'; import {assert} from 'chai'; import x_hextobin from '@keymanapp/hextobin'; import { KMXBuilder } from '@keymanapp/common-types'; -import {checkMessages, compileKeyboard, makePathToFixture} from './helpers/index.js'; +import {checkMessages, compileKeyboard, compilerTestOptions, makePathToFixture} from './helpers/index.js'; const hextobin = (x_hextobin as any).default; @@ -17,7 +17,7 @@ describe('compiler-tests', function() { const binaryFilename = makePathToFixture('basic.txt'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, {debug: true, addCompilerVersion: false}); + const kmx = compileKeyboard(inputFilename, {...compilerTestOptions, debug: true, addCompilerVersion: false}); assert.isNotNull(kmx); // Use the builder to generate the binary output file diff --git a/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts b/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts index fb4f28daee..711820e5e4 100644 --- a/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts +++ b/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts @@ -1,6 +1,6 @@ import 'mocha'; import { assert } from 'chai'; -import { checkMessages, compilerTestCallbacks, makePathToFixture } from './helpers/index.js'; +import { checkMessages, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js'; import { LdmlKeyboardKeymanWebCompiler } from '../src/compiler/keymanweb-compiler.js'; import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js'; import * as fs from 'fs'; @@ -16,7 +16,7 @@ describe('LdmlKeyboardKeymanWebCompiler', function() { // Load input data; we'll use the LDML keyboard compiler loader to save us // effort here - const k = new LdmlKeyboardCompiler(compilerTestCallbacks, {debug: true, addCompilerVersion: false}); + const k = new LdmlKeyboardCompiler(compilerTestCallbacks, {...compilerTestOptions, debug: true, addCompilerVersion: false}); const source = k.load(inputFilename); checkMessages(); assert.isNotNull(source, 'k.load should not have returned null'); @@ -27,7 +27,7 @@ describe('LdmlKeyboardKeymanWebCompiler', function() { assert.isTrue(valid, 'k.validate should not have failed'); // Actual test: compile to javascript - const jsCompiler = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {debug: true}); + const jsCompiler = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, debug: true}); const output = jsCompiler.compile('basic.xml', source); assert.isNotNull(output); @@ -36,7 +36,7 @@ describe('LdmlKeyboardKeymanWebCompiler', function() { assert.strictEqual(output, outputFixture); // Second test: compile to javascript without debug formatting - const jsCompilerNoDebug = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {debug: false}); + const jsCompilerNoDebug = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, debug: false}); const outputNoDebug = jsCompilerNoDebug.compile('basic.xml', source); assert.isNotNull(outputNoDebug); diff --git a/developer/src/kmc-ldml/test/test-metadata-compiler.ts b/developer/src/kmc-ldml/test/test-metadata-compiler.ts index 59500d0995..ede77ed4bc 100644 --- a/developer/src/kmc-ldml/test/test-metadata-compiler.ts +++ b/developer/src/kmc-ldml/test/test-metadata-compiler.ts @@ -1,6 +1,6 @@ import 'mocha'; import { assert } from 'chai'; -import { checkMessages, compileKeyboard, makePathToFixture } from './helpers/index.js'; +import { checkMessages, compileKeyboard, compilerTestOptions, makePathToFixture } from './helpers/index.js'; import { KMX } from '@keymanapp/common-types'; import KEYMAN_VERSION from '@keymanapp/keyman-version'; @@ -13,7 +13,7 @@ describe('kmx metadata compiler', function () { const inputFilename = makePathToFixture('basic.xml'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:true}); + const kmx = compileKeyboard(inputFilename, {...compilerTestOptions, debug:true, addCompilerVersion:true}); checkMessages(); assert.isNotNull(kmx); @@ -48,7 +48,7 @@ describe('kmx metadata compiler', function () { const inputFilename = makePathToFixture('basic.xml'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:false}); + const kmx = compileKeyboard(inputFilename, {...compilerTestOptions, debug:true, addCompilerVersion:false}); checkMessages(); assert.isNotNull(kmx); diff --git a/developer/src/kmc-ldml/test/test-testdata-e2e.ts b/developer/src/kmc-ldml/test/test-testdata-e2e.ts index 25ac196321..0d562584fa 100644 --- a/developer/src/kmc-ldml/test/test-testdata-e2e.ts +++ b/developer/src/kmc-ldml/test/test-testdata-e2e.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import 'mocha'; import {assert} from 'chai'; -import {loadTestdata, makePathToFixture} from './helpers/index.js'; +import {compilerTestOptions, loadTestdata, makePathToFixture} from './helpers/index.js'; describe('testdata-tests', function() { this.slow(500); // 0.5 sec -- json schema validation takes a while @@ -14,7 +14,7 @@ describe('testdata-tests', function() { const jsonFilename = makePathToFixture('test-fr.json'); // Compile the keyboard - const testData = loadTestdata(inputFilename, {debug: true, addCompilerVersion: false}); + const testData = loadTestdata(inputFilename, compilerTestOptions); assert.isNotNull(testData); const jsonData = JSON.parse(readFileSync(jsonFilename, 'utf-8')); diff --git a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts index 1b1e34069e..4af6827838 100644 --- a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts +++ b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts @@ -2,7 +2,7 @@ import 'mocha'; import {assert} from 'chai'; import x_hextobin from '@keymanapp/hextobin'; import { KvkFileWriter } from '@keymanapp/common-types'; -import {checkMessages, compileVisualKeyboard, makePathToFixture} from './helpers/index.js'; +import {checkMessages, compilerTestOptions, compileVisualKeyboard, makePathToFixture} from './helpers/index.js'; const hextobin = (x_hextobin as any).default; @@ -17,7 +17,7 @@ describe('visual-keyboard-compiler', function() { const binaryFilename = makePathToFixture('basic-kvk.txt'); // Compile the visual keyboard - const vk = compileVisualKeyboard(inputFilename, {debug: true, addCompilerVersion: false}); + const vk = compileVisualKeyboard(inputFilename, {...compilerTestOptions, debug: true, addCompilerVersion: false}); assert.isNotNull(vk); // Use the builder to generate the binary output file diff --git a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts index 024f7f5340..9c6c5c8731 100644 --- a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts +++ b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts @@ -1,8 +1,9 @@ import * as path from 'path'; import * as fs from 'fs'; import * as kmcLdml from '@keymanapp/kmc-ldml'; -import { KvkFileWriter, CompilerCallbacks } from '@keymanapp/common-types'; +import { KvkFileWriter, CompilerCallbacks, LDMLKeyboardXMLDefaultImportsURL } from '@keymanapp/common-types'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; +import { fileURLToPath } from 'url'; export class BuildLdmlKeyboard extends BuildActivity { public get name(): string { return 'LDML keyboard'; } @@ -46,11 +47,14 @@ function buildLdmlKeyboardToMemory(inputFilename: string, callbacks: CompilerCal let compilerOptions: kmcLdml.CompilerOptions = { debug: options.debug ?? false, addCompilerVersion: options.compilerVersion ?? true, + readerOptions: { + importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) + } // TODO: warnDeprecatedCode: options.warnDeprecatedCode, // TODO: treatWarningsAsErrors: options.treatWarningsAsErrors, } - const k = new kmcLdml.LdmlKeyboardCompiler(callbacks, options); + const k = new kmcLdml.LdmlKeyboardCompiler(callbacks, compilerOptions); let source = k.load(inputFilename); if (!source) { return [null, null, null]; diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index 0990b91edb..11a9789d6c 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -1,8 +1,9 @@ import * as fs from 'fs'; import * as path from 'path'; import * as kmc from '@keymanapp/kmc-ldml'; -import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; +import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLDefaultImportsURL } from '@keymanapp/common-types'; import { NodeCompilerCallbacks } from '../../messages/NodeCompilerCallbacks.js'; +import { fileURLToPath } from 'url'; export interface BuildTestDataOptions { outFile?: string; @@ -11,7 +12,10 @@ export interface BuildTestDataOptions { export function buildTestData(infile: string, options: BuildTestDataOptions) { let compilerOptions: kmc.CompilerOptions = { debug: false, - addCompilerVersion: false + addCompilerVersion: false, + readerOptions: { + importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) + } }; let testData = loadTestData(infile, compilerOptions); diff --git a/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts index f8232cde5f..dfcb7e2ecb 100644 --- a/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts @@ -93,7 +93,10 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { } resolveFilename(baseFilename: string, filename: string) { - const basePath = path.dirname(baseFilename); + const basePath = + baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? + baseFilename : + path.dirname(baseFilename); // Transform separators to platform separators -- we are agnostic // in our use here but path prefers files may use // either / or \, although older kps files were always \. -- GitLab From a281e1ac6033a2ed99bddc16d97ceceeb72dcffa Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 2 Jun 2023 11:43:25 +0700 Subject: [PATCH 346/386] chore(common): remove redundant path --- common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index a1772a9554..35eba83e05 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -25,7 +25,7 @@ export class LDMLKeyboardXMLSourceFileReader { } readImportFile(version: string, subpath: string): Uint8Array { - const importPath = this.callbacks.resolveFilename(this.options.importsPath, `../import/${version}/${subpath}`); + const importPath = this.callbacks.resolveFilename(this.options.importsPath, `${version}/${subpath}`); return this.callbacks.loadFile(importPath); } -- GitLab From 4e535832d8a9cfe2e28d0727cdd172147c784b7e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 2 Jun 2023 11:45:59 +0700 Subject: [PATCH 347/386] chore: cleanup --- common/web/types/test/helpers/reader-callback-test.ts | 1 - developer/src/kmc-ldml/src/compiler/compiler.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts index a99eca71f4..b4b68cb98f 100644 --- a/common/web/types/test/helpers/reader-callback-test.ts +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -84,7 +84,6 @@ export function testReaderCases(cases : CompilationCase[]) { it(testHeading, function () { callbacks.clear(); - debugger; const data = loadFile(makePathToFixture(testcase.subpath)); assert.ok(data, `reading ${testcase.subpath}`); const source = reader.load(data); diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index e753beb317..4b1472a585 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -30,7 +30,7 @@ const SECTION_COMPILERS = [ export class LdmlKeyboardCompiler { private readonly callbacks: CompilerCallbacks; - private readonly options: CompilerOptions; // not currently used + private readonly options: CompilerOptions; constructor (callbacks: CompilerCallbacks, options: CompilerOptions) { this.options = { -- GitLab From d7f22b186f3596e24765267c7fa0de759b61efdd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 2 Jun 2023 12:01:58 +0700 Subject: [PATCH 348/386] chore(developer): verify long lines compile correctly Fixes #8888. Verifies that wrapped lines (ending in \) are handled correctly in kmcmplib. --- .../valid-keyboards/compile_legacy.bat | 2 +- .../valid-keyboards/k009_long_lines.kmn | 31 ++++++++++++++++++ .../valid-keyboards/k009_long_lines.kmx | Bin 0 -> 1686 bytes developer/src/kmcmplib/tests/meson.build | 5 +-- 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmn create mode 100644 developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat index e346418eb4..5e8b891ea6 100644 --- a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat @@ -1,4 +1,4 @@ @echo off echo Compiles the keyboards using the legacy kmcomp.exe echo to use as baseline comparisons for kmcmplib -for %%d in (*.kmn) do kmcomp -no-compiler-version -d %%d \ No newline at end of file +for %%d in (*.kmn) do ..\..\..\..\..\bin\kmcomp -no-compiler-version -d %%d \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmn new file mode 100644 index 0000000000..a0ad9d0d9f --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmn @@ -0,0 +1,31 @@ +c Description: Verifies that kmcmplib can deal with long lines continued with backslashes, and also comments + +store(&NAME) 'k009_long_lines' +store(&VERSION) '9.0' + +begin unicode > use(main) + +group(main) using keys + +c +c ***Note*** This file has intentional whitespace at the end of line ending in [SHIFT K_N] +c in order to verify that line continuations are working correctly +c + +store(c_key) [K_K] [K_X] [SHIFT K_K] [SHIFT K_X] [K_G] \ + [K_C] [K_Q] [SHIFT K_C] [SHIFT K_Q] [SHIFT K_J] \ + [K_D] [K_Z] [SHIFT K_D] [SHIFT K_Z] [SHIFT K_N] \ + [K_T] [K_F] [SHIFT K_T] [SHIFT K_F] [K_N] \ + [K_B] [K_P] [SHIFT K_B] [SHIFT K_P] [K_M] \ + [K_Y] [K_R] [K_L] [K_V] [K_S] [K_H] [SHIFT K_L] [SHIFT K_G] \ + [RALT K_K] [RALT K_B] +store(c_out) U+1780 U+1781 U+1782 U+1783 U+1784 \ + U+1785 U+1786 U+1787 U+1788 U+1789 \ + U+178A U+178B U+178C U+178D U+178E \ + U+178F U+1790 U+1791 U+1792 U+1793 \ + U+1794 U+1795 U+1796 U+1797 U+1798 \ + U+1799 U+179A U+179B U+179C U+179F U+17A0 U+17A1 U+17A2 \ + U+179D U+179E c deprecated, but they are used in minority languages + ++ any(c_key) > index(c_out, 1) + diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmx new file mode 100644 index 0000000000000000000000000000000000000000..4acd4cc051adc10d35c6ae842b24be5cf43f0cf4 GIT binary patch literal 1686 zcmYk5xldG46vmJHNGx6~I2MK&HN;pD3PZ$LTn8MG0fb>B1BFWj4T3`4O^9LN_kEQm z#=pS$7f=`rLqRBvg`psBu`m|K81y@bxtDvB-}lb<&AIo@ocAW0h?mhpqHpmpPY7$5 z2v!J4ZNTPkqJLN@-J00HU8dsl5-b7+ufZ#D0IY*IK+gIKHb4O6;J7E%HptrfXD3^~i2(RfDRU9-t$gis=Fs(M8nRl%c1j zQ#pK$%IFeZ#^*$5K^{IkrYFm8`JE~F(NTPd?JAM10+$8GW@PeQUyiFv-42@tW+y9f zeYvixbUSPon4PTH^~GHkaXV}l7@LvV*%5X-O56^c1;%D%idl%3PJ7 ztO8^E0ymeCn_9@oeTi=3N!9ApBx>SjZsAsL<96=gPVVAv?%`hU<9;6CK_22^9^p|Q z<8hwgNuJ_qp5a-Z;{{&iC0^!veu0-x&a9_L^bqG~fiPXcv#dd0PgS@V(H7pM&>j@Y zbpls;>Ew;te(qtoG!QaGEBlDvf~c3hgMSZfUx2<<_%)D|?2~LA{sYMMvXAheK*Y;7 z;5R{@mwkr+4hp>N2mDW9zl+@C7yNHf==J@9-vaVp%kPjr!hduxDDkrW@F}3w%MQT@ zAns*{;Zs4v%Z|Vw1(jZw27esbxsV|`34aRMxkz>zJ_FdfNR|nI21JtQNtOkF4%oR! zb{^h Date: Fri, 2 Jun 2023 12:28:13 +0700 Subject: [PATCH 349/386] chore(common): move const url to class prop We still have cjs modules that rely on the ldml-keyboard-xml-reader, and this meant that an exported const was being calculated with an invalid meta url, which crashed the cjs module require() call. --- .../web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts | 6 ++++-- common/web/types/src/main.ts | 2 +- common/web/types/test/helpers/reader-callback-test.ts | 4 ++-- developer/src/kmc-ldml/test/helpers/index.ts | 4 ++-- developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts | 4 ++-- developer/src/kmc/src/commands/buildTestData/index.ts | 4 ++-- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 35eba83e05..fa74126462 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -18,12 +18,14 @@ export class LDMLKeyboardXMLSourceFileReaderOptions { importsPath: string; }; -export const LDMLKeyboardXMLDefaultImportsURL = new URL(`../import/`, import.meta.url); - export class LDMLKeyboardXMLSourceFileReader { constructor(private options: LDMLKeyboardXMLSourceFileReaderOptions, private callbacks : CompilerCallbacks) { } + static get defaultImportsURL() { + return new URL(`../import/`, import.meta.url); + } + readImportFile(version: string, subpath: string): Uint8Array { const importPath = this.callbacks.resolveFilename(this.options.importsPath, `${version}/${subpath}`); return this.callbacks.loadFile(importPath); diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index c1fa6a4a0a..ddaa318d3e 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -14,7 +14,7 @@ export * as KvksFile from './kvk/kvk-file.js'; export * as LDMLKeyboard from './ldml-keyboard/ldml-keyboard-xml.js'; export { LDMLKeyboardTestDataXMLSourceFile } from './ldml-keyboard/ldml-keyboard-testdata-xml.js'; -export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions, LDMLKeyboardXMLDefaultImportsURL } from './ldml-keyboard/ldml-keyboard-xml-reader.js'; +export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from './ldml-keyboard/ldml-keyboard-xml-reader.js'; export * as Constants from './consts/virtual-key-constants.js'; diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts index b4b68cb98f..204e0ec5bf 100644 --- a/common/web/types/test/helpers/reader-callback-test.ts +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -1,7 +1,7 @@ import 'mocha'; import {assert} from 'chai'; import { loadFile, makePathToFixture, loadSchema } from '../helpers/index.js'; -import { LDMLKeyboardXMLDefaultImportsURL, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; +import { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; import { CompilerEvent } from '../../src/util/compiler-interfaces.js'; import { LDMLKeyboardXMLSourceFile } from '../../src/ldml-keyboard/ldml-keyboard-xml.js'; import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboard-testdata-xml.js'; @@ -9,7 +9,7 @@ import { TestCompilerCallbacks } from './TestCompilerCallbacks.js'; import { fileURLToPath } from 'url'; const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = { - importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) }; export interface CompilationCase { diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index a493af82f1..f10af945c8 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -5,7 +5,7 @@ import 'mocha'; import * as path from 'path'; import { fileURLToPath } from 'url'; import { SectionCompiler } from '../../src/compiler/section-compiler.js'; -import { KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLDefaultImportsURL } from '@keymanapp/common-types'; +import { KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; import { LdmlKeyboardCompiler } from '../../src/compiler/compiler.js'; import { assert } from 'chai'; import { KMXPlusMetadataCompiler } from '../../src/compiler/metadata-compiler.js'; @@ -35,7 +35,7 @@ export const compilerTestCallbacks = new TestCompilerCallbacks(); export const compilerTestOptions: CompilerOptions = { readerOptions: { - importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) } }; diff --git a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts index 9c6c5c8731..40b9513d32 100644 --- a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts +++ b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as fs from 'fs'; import * as kmcLdml from '@keymanapp/kmc-ldml'; -import { KvkFileWriter, CompilerCallbacks, LDMLKeyboardXMLDefaultImportsURL } from '@keymanapp/common-types'; +import { KvkFileWriter, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader } from '@keymanapp/common-types'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; import { fileURLToPath } from 'url'; @@ -48,7 +48,7 @@ function buildLdmlKeyboardToMemory(inputFilename: string, callbacks: CompilerCal debug: options.debug ?? false, addCompilerVersion: options.compilerVersion ?? true, readerOptions: { - importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) } // TODO: warnDeprecatedCode: options.warnDeprecatedCode, // TODO: treatWarningsAsErrors: options.treatWarningsAsErrors, diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index 11a9789d6c..b6ebf34a73 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as kmc from '@keymanapp/kmc-ldml'; -import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLDefaultImportsURL } from '@keymanapp/common-types'; +import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLSourceFileReader } from '@keymanapp/common-types'; import { NodeCompilerCallbacks } from '../../messages/NodeCompilerCallbacks.js'; import { fileURLToPath } from 'url'; @@ -14,7 +14,7 @@ export function buildTestData(infile: string, options: BuildTestDataOptions) { debug: false, addCompilerVersion: false, readerOptions: { - importsPath: fileURLToPath(LDMLKeyboardXMLDefaultImportsURL) + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) } }; -- GitLab From 16675ef46b0bfbe7e3d0c239ee6b5e09eda22e0e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 2 Jun 2023 18:54:42 +1000 Subject: [PATCH 350/386] chore: extend from test options in e2e test --- developer/src/kmc-ldml/test/test-testdata-e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmc-ldml/test/test-testdata-e2e.ts b/developer/src/kmc-ldml/test/test-testdata-e2e.ts index 0d562584fa..ece4ce8560 100644 --- a/developer/src/kmc-ldml/test/test-testdata-e2e.ts +++ b/developer/src/kmc-ldml/test/test-testdata-e2e.ts @@ -14,7 +14,7 @@ describe('testdata-tests', function() { const jsonFilename = makePathToFixture('test-fr.json'); // Compile the keyboard - const testData = loadTestdata(inputFilename, compilerTestOptions); + const testData = loadTestdata(inputFilename, {...compilerTestOptions, debug: true, addCompilerVersion: false}); assert.isNotNull(testData); const jsonData = JSON.parse(readFileSync(jsonFilename, 'utf-8')); -- GitLab From f43f808a03174a67bbe7037c074449eefa64cb54 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 2 Jun 2023 17:31:04 +0200 Subject: [PATCH 351/386] feat(linux): Rename column and add tooltip Follow-up of #8631 where I forgot to add one of the changed files. --- linux/keyman-config/keyman_config/view_installed.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index aaa5196fbd..263d665d25 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -19,7 +19,7 @@ from keyman_config.accelerators import bind_accelerator, init_accel from keyman_config.dbus_util import get_keyman_config_service from keyman_config.downloadkeyboard import DownloadKmpWindow from keyman_config.get_kmp import (InstallLocation, get_keyboard_dir, - get_keyman_dir) + get_keyman_dir, get_install_area_string) from keyman_config.install_window import InstallKmpWindow, find_keyman_image from keyman_config.keyboard_details import KeyboardDetailsView from keyman_config.kmpmetadata import get_fonts, parsemetadata @@ -166,7 +166,8 @@ class ViewInstalledWindow(ViewInstalledWindowBase): str, # version str, # packageID int, # enum InstallLocation (KmpArea is GObject version) - str, # InstallLocation path + str, # InstallLocation area + str, # Tooltip with InstallLocation path str, # path to welcome file if it exists or None str) # path to options file if it exists or None @@ -186,9 +187,11 @@ class ViewInstalledWindow(ViewInstalledWindowBase): column = Gtk.TreeViewColumn(_("Version"), renderer, text=2) self.tree.append_column(column) # i18n: column header in table displaying installed keyboards - column = Gtk.TreeViewColumn(_("Location"), renderer, text=5) + column = Gtk.TreeViewColumn(_("Area"), renderer, text=5) self.tree.append_column(column) + self.tree.set_tooltip_column(column=6) + select = self.tree.get_selection() select.connect("changed", self.on_tree_selection_changed) @@ -286,7 +289,8 @@ class ViewInstalledWindow(ViewInstalledWindowBase): kmpdata['version'], kmpdata['packageID'], install_area, - get_keyman_dir(install_area).replace(os.path.expanduser('~'), '~'), + get_install_area_string(install_area), + path.replace(os.path.expanduser('~'), '~'), welcome_file, options_file]) -- GitLab From 3c4f2251ebd500c103d8b0e2404195677ac7ea37 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 2 Jun 2023 18:42:35 +0200 Subject: [PATCH 352/386] fix(core): Fix C API documentation generation --- core/doc/hotdoc.json | 10 +++++--- core/doc/meson.build | 2 +- core/include/keyman/keyboardprocessor.h | 31 ++++++++++++++++++------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/core/doc/hotdoc.json b/core/doc/hotdoc.json index 27a54a876f..4ece50d0f5 100644 --- a/core/doc/hotdoc.json +++ b/core/doc/hotdoc.json @@ -1,10 +1,14 @@ { "project_name": "@project_name@", "project_version": "@project_version@", - "sitemap": "@source_dir@/sitemap.txt", - "index": "@source_dir@/markdown_files/index.md", + "sitemap": "@doc_dir@/sitemap.txt", + "index": "@doc_dir@/markdown_files/index.md", "c_sources": [ - "@include_dir@/keyboardprocessor.h" + "@include_dir@/keyman/keyboardprocessor.h" + ], + "c_include_directories": [ + "@include_dir@", + "@doc_dir@/../../common/include" ], "c_smart_index" : true, "output": ".", diff --git a/core/doc/meson.build b/core/doc/meson.build index 57e96d3259..01fcb5c98a 100644 --- a/core/doc/meson.build +++ b/core/doc/meson.build @@ -12,7 +12,7 @@ if hotdoc.found() cfg = configuration_data() cfg.set('project_name', meson.project_name()) cfg.set('project_version', meson.project_version()) - cfg.set('source_dir', meson.current_source_dir()) + cfg.set('doc_dir', meson.current_source_dir()) cfg.set('include_dir', meson.current_source_dir() / '../include') configure_file(input: 'hotdoc.json', output: 'hotdoc.json', diff --git a/core/include/keyman/keyboardprocessor.h b/core/include/keyman/keyboardprocessor.h index 3513cdede2..429a8ae6cb 100644 --- a/core/include/keyman/keyboardprocessor.h +++ b/core/include/keyman/keyboardprocessor.h @@ -779,7 +779,7 @@ km_kbp_keyboard_get_key_list(km_kbp_keyboard const *keyboard, km_kbp_keyboard_key **out); -/* +/** ``` ### `km_kbp_keyboard_key_list_dispose` ##### Description: @@ -794,28 +794,41 @@ returned by `km_kbp_keyboard_get_key_list`. KMN_API void km_kbp_keyboard_key_list_dispose(km_kbp_keyboard_key *key_list); - /** - * Returns the list of IMX libraries and function names that are referenced by - * the keyboard. The matching dispose call needs to be called to free the memory. + * km_kbp_keyboard_get_imx_list: + * + * Returns: the list of IMX libraries and function names that are referenced by + * the keyboard.The matching dispose call needs to be called to free the memory. */ KMN_API -km_kbp_status km_kbp_keyboard_get_imx_list(km_kbp_keyboard const *keyboard, km_kbp_keyboard_imx** imx_list); +km_kbp_status km_kbp_keyboard_get_imx_list(km_kbp_keyboard const *keyboard, km_kbp_keyboard_imx **imx_list); /** + * km_kbp_keyboard_imx_list_dispose: + * * Disposes of the IMX list + * + * Returns: -- */ KMN_API void km_kbp_keyboard_imx_list_dispose(km_kbp_keyboard_imx *imx_list); /** + * km_kbp_state_imx_register_callback: + * * Register the IMX callback endpoint for the client. + * + * Returns: -- */ KMN_API void km_kbp_state_imx_register_callback(km_kbp_state *state, km_kbp_keyboard_imx_platform imx_callback, void *callback_object); /** + * km_kbp_state_imx_deregister_callback: + * * De-register IMX callback endpoint for the client. + * + * Returns: -- */ KMN_API void km_kbp_state_imx_deregister_callback(km_kbp_state *state); @@ -1042,6 +1055,8 @@ enum km_kbp_tech_value { }; /** + * km_kbp_event_flags: + * * Bit flags to be used with the event_flags parameter of km_kbp_process_event */ enum km_kbp_event_flags { @@ -1162,10 +1177,8 @@ km_kbp_event( ); enum km_kbp_event_code { - /** - * A keyboard has been activated by the user. The processor may use this - * event, for example, to switch caps lock state or provide other UX. - */ + // A keyboard has been activated by the user. The processor may use this + // event, for example, to switch caps lock state or provide other UX. KM_KBP_EVENT_KEYBOARD_ACTIVATED = 1, //future: KM_KBP_EVENT_KEYBOARD_DEACTIVATED = 2, }; -- GitLab From 36502ee9b420774c8aa5115433a690961e5d5883 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 2 Jun 2023 14:03:15 -0400 Subject: [PATCH 353/386] auto: increment master version to 17.0.117 --- HISTORY.md | 9 +++++++++ VERSION.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index e2c639b852..03c58a8485 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,14 @@ # Keyman Version History +## 17.0.116 alpha 2023-06-02 + +* feat(linux): Add column for installation location (#8897) +* chore(developer): verify kvks files and report errors (#8892) +* refactor(developer): rearrange kmcmplib interface source (#8899) +* refactor(developer): move filename consistency check to kmc (#8907) +* chore(developer): loadFile callback error check and optimization (#8908) +* chore(common): remove url module ref from common/web/types (#8914) + ## 17.0.115 alpha 2023-06-01 * refactor(developer): complete fs move out of kmcmplib (#8882) diff --git a/VERSION.md b/VERSION.md index ce5446d392..52e25d110a 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.116 \ No newline at end of file +17.0.117 \ No newline at end of file -- GitLab From 80e6c2888f85ff5b28eb7be80e5b80528ec2fa4a Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Sun, 4 Jun 2023 14:02:04 -0400 Subject: [PATCH 354/386] auto: increment master version to 17.0.118 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 03c58a8485..2b501ebd15 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 17.0.117 alpha 2023-06-04 + +* chore(developer): verify long lines compile correctly (#8915) + ## 17.0.116 alpha 2023-06-02 * feat(linux): Add column for installation location (#8897) diff --git a/VERSION.md b/VERSION.md index 52e25d110a..2398e0d103 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.117 \ No newline at end of file +17.0.118 \ No newline at end of file -- GitLab From 06fa0f05563018b8ec64b48de04e172035702fe2 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 5 Jun 2023 09:59:31 +0700 Subject: [PATCH 355/386] change(common/models): replaces 'virtualized' Node worker with actual Node worker --- .../predictive-text/src/node/mappedWorker.ts | 77 +++++++++++++++++ .../src/node/sourcemappedWorker.ts | 4 +- common/predictive-text/src/node/tsconfig.json | 4 +- .../src/node/virtualizedWorker.ts | 84 ------------------- common/predictive-text/src/node/worker.ts | 4 +- .../headless/worker-dummy-integration.js | 23 +++-- .../headless/worker-trie-integration.js | 20 +++-- 7 files changed, 114 insertions(+), 102 deletions(-) create mode 100644 common/predictive-text/src/node/mappedWorker.ts delete mode 100644 common/predictive-text/src/node/virtualizedWorker.ts diff --git a/common/predictive-text/src/node/mappedWorker.ts b/common/predictive-text/src/node/mappedWorker.ts new file mode 100644 index 0000000000..7e2297b45b --- /dev/null +++ b/common/predictive-text/src/node/mappedWorker.ts @@ -0,0 +1,77 @@ +// We use a subset of the Worker interface here; compiling directly against the true +// WebWorker type definitions would require us to implement more methods than we do. +/// + +// Defines types related to Node workers. +import * as worker from 'worker_threads'; +import { Buffer } from 'buffer'; +import { URL } from 'url'; + +/** + * Defines mappings from Node Worker signatures to WebWorker signatures + */ +const nodeWorkerToWebWorkerMappingSource = ` +import { parentPort } from 'worker_threads'; +import fs from 'fs'; +import vm from 'vm'; + +function postMessage(...args) { + parentPort.postMessage.call(parentPort, args); +} + +parentPort.on('message', (ev) => { + onmessage({data: ev}); +}); + +function importScripts(...args) { + function loadScriptInContext(scriptPath) { + let scriptStr = fs.readFileSync(scriptPath); + var script = new vm.Script(scriptStr, { filename: scriptPath }); + script.runInThisContext(); + } + + for(let arg of args) { + loadScriptInContext(arg); + } +} + +/* + * You'd think the method signature mapping would be implied from the first line, + * but all three lines must be explicitly specified or the emulation will fail. + */ +const self = globalThis; +self.postMessage = postMessage; +self.importScripts = importScripts; +`; + +/** + * Uses the Node version of Workers to provide proper, authentic separate-thread + * 'sandboxing'. Also intercepts and interprets certain WebWorker method signatures + * necessary to run the WebWorker-oriented worker code. + */ +export default class MappedWorker extends worker.Worker implements Worker { + constructor(scriptStr: string) { + const concatenatedScript = ` + ${nodeWorkerToWebWorkerMappingSource} + + ${scriptStr} + `; + const buffer = Buffer.from(concatenatedScript); + const dataSrc = "data:text/javascript;base64," + buffer.toString('base64'); + //@ts-ignore + super(new URL(dataSrc)); + + // WebWorkers have a defined `onmessage` function, rather than this.on('message', ...) + this.on('message', (ev) => { + if(this.onmessage) { + this.onmessage({data: ev[0]}); + } + }); + } + + /** + * Accepts a callback function that will receive messages sent from the `VirtualizedWorker`'s `postMessage` function, + * much like the standard `Worker.onmessage`. + */ + onmessage: (this: Worker, ev: MessageEvent) => any; +} \ No newline at end of file diff --git a/common/predictive-text/src/node/sourcemappedWorker.ts b/common/predictive-text/src/node/sourcemappedWorker.ts index 5fd8bb9ec4..aaaf79e0f6 100644 --- a/common/predictive-text/src/node/sourcemappedWorker.ts +++ b/common/predictive-text/src/node/sourcemappedWorker.ts @@ -1,4 +1,4 @@ -import VirtualizedWorker from "./virtualizedWorker.js"; +import MappedWorker from "./mappedWorker.js"; import unwrap from '../unwrap.js'; import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.js"; @@ -12,7 +12,7 @@ export default class SourcemappedWorker { // if(false) { scriptStr += '\n' + LMLayerWorkerSourcemapComment; // } - let worker = new VirtualizedWorker(scriptStr); + let worker = new MappedWorker(scriptStr); return worker as any as Worker; } diff --git a/common/predictive-text/src/node/tsconfig.json b/common/predictive-text/src/node/tsconfig.json index b9567ad113..48d64d22b6 100644 --- a/common/predictive-text/src/node/tsconfig.json +++ b/common/predictive-text/src/node/tsconfig.json @@ -8,7 +8,9 @@ "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", - "target": "es5", + // This build-configuration only targets Node and exists solely for unit tests. + // Also, we need ES6 in order to properly inherit from the Node Worker-type. + "target": "ES6", "lib": ["es6"], "types": ["node"], "baseUrl": "../", diff --git a/common/predictive-text/src/node/virtualizedWorker.ts b/common/predictive-text/src/node/virtualizedWorker.ts deleted file mode 100644 index f32d265c99..0000000000 --- a/common/predictive-text/src/node/virtualizedWorker.ts +++ /dev/null @@ -1,84 +0,0 @@ -// We use a subset of the Worker interface here; compiling directly against the true -// WebWorker type definitions would require us to implement more methods than we do. -/// - -import * as fs from 'fs'; -import * as vm from 'vm'; - -class VirtualizedWorkerContext { - // The LMLayerWorker installs itself to 'self', the expected Worker global, so we provide an alias. - self: VirtualizedWorkerContext; - - constructor() { - this.self = this; - } - - postMessage: (message: any) => void; - - importScripts(...scriptNames: string[]) { - /* Use of vm.createContext and script.runInContext allow us to avoid - * polluting the global scope with imports. When we throw away the - * context object, imported scripts will be automatically GC'd. - */ - for(let script of scriptNames) { - this.__importScriptString(fs.readFileSync(script, "UTF-8")); - } - } - - __importScriptString(scriptStr: string) { - let context = vm.createContext(this); - var script = new vm.Script(scriptStr); - script.runInContext(context); - } -} - -/** - * Note: this does not create an actual Worker, separate process, or thread. Everything will - * be executed in-line on a virtualized context. - * - * In the future, it might be nice to use Node's Worker Threads implementation. - */ -export default class VirtualizedWorker implements Worker { - private _workerContext: VirtualizedWorkerContext; - - constructor(scriptStr: string) { - this._workerContext = new VirtualizedWorkerContext(); - // Needs to exist before setting up the worker; must exist by `.install()`. - this._workerContext.postMessage = this.workerPostMessage.bind(this); - - // Initialize the "worker". - this._workerContext.__importScriptString(scriptStr); - } - - // Sends the worker's postMessage messages to the appropriate `onmessage` handler. - private workerPostMessage(message: unknown) { - if(this.onmessage) { - this.onmessage({data: message} as any as MessageEvent); - } - } - - /** - * Accepts a callback function that will receive messages sent from the `VirtualizedWorker`'s `postMessage` function, - * much like the standard `Worker.onmessage`. - */ - onmessage: (this: Worker, ev: MessageEvent) => any; - - postMessage(message: any) { - let msgObj = {data: message}; - let msgJSON = JSON.stringify(msgObj); - - /* - * Execute the command within the virtualized worker's scope. The worker's returned - * `postMessage` calls will still reach outside, as they have a reference to `this` via - * `postMessage` (which we've set to a bound `this.workerPostMessage`). - * - * Among other things, this will allow the worker to use its internal namespaces without issue. - */ - let msgCommand = "onmessage(" + msgJSON + ")"; - this._workerContext.__importScriptString(msgCommand); - } - - terminate(): void { - this._workerContext = null; - } -} \ No newline at end of file diff --git a/common/predictive-text/src/node/worker.ts b/common/predictive-text/src/node/worker.ts index 68f14b587b..5ad04e55d0 100644 --- a/common/predictive-text/src/node/worker.ts +++ b/common/predictive-text/src/node/worker.ts @@ -1,4 +1,4 @@ -import VirtualizedWorker from "./virtualizedWorker.js"; +import MappedWorker from "./mappedWorker.js"; import unwrap from '../unwrap.js'; import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.min.js"; @@ -8,7 +8,7 @@ export default class Worker { let scriptStr = unwrap(LMLayerWorkerCode); scriptStr += '\n' + LMLayerWorkerSourcemapComment; - let worker = new VirtualizedWorker(scriptStr); + let worker = new MappedWorker(scriptStr); return worker as any as Worker; } diff --git a/common/predictive-text/unit_tests/headless/worker-dummy-integration.js b/common/predictive-text/unit_tests/headless/worker-dummy-integration.js index 6e6adb426f..d546edb704 100644 --- a/common/predictive-text/unit_tests/headless/worker-dummy-integration.js +++ b/common/predictive-text/unit_tests/headless/worker-dummy-integration.js @@ -16,10 +16,23 @@ import { capabilities, iGotDistractedByHazel } from '@keymanapp/common-test-reso * of suggestions when loaded and return them sequentially. */ describe('LMLayer using dummy model', function () { + let lmLayer; + let worker; + + beforeEach(function() { + worker = Worker.constructInstance(); + lmLayer = new LMLayer(capabilities(), worker); + }); + + afterEach(function () { + // As we're using Node worker threads here, failure to terminate them will cause the + // headless test run to hang after completion. + lmLayer.shutdown(); + worker.terminate(); // should be covered by the former, but just in case... for CI stability. + }); + describe('Prediction', function () { it('will predict future suggestions (loaded from file)', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - var stripIDs = function(suggestions) { suggestions.forEach(function(suggestion) { delete suggestion.id; @@ -51,14 +64,11 @@ describe('LMLayer using dummy model', function () { }).then(function (suggestions) { stripIDs(suggestions); assert.deepEqual(suggestions, iGotDistractedByHazel()[3]); - lmLayer.shutdown(); return Promise.resolve(); }); }); it('will predict future suggestions (loaded from raw source)', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - var stripIDs = function(suggestions) { suggestions.forEach(function(suggestion) { delete suggestion.id; @@ -92,7 +102,6 @@ describe('LMLayer using dummy model', function () { }).then(function (suggestions) { stripIDs(suggestions); assert.deepEqual(suggestions, iGotDistractedByHazel()[3]); - lmLayer.shutdown(); return Promise.resolve(); }); }); @@ -100,8 +109,6 @@ describe('LMLayer using dummy model', function () { describe('Wordbreaking', function () { it('will perform (default) wordbreaking and return word at caret', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - // We're testing many as asynchronous messages in a row. // this would be cleaner using async/await syntax. // Not done yet, as this test case is a slightly-edited copy of the in-browser version. diff --git a/common/predictive-text/unit_tests/headless/worker-trie-integration.js b/common/predictive-text/unit_tests/headless/worker-trie-integration.js index 88ae133097..3850c086b6 100644 --- a/common/predictive-text/unit_tests/headless/worker-trie-integration.js +++ b/common/predictive-text/unit_tests/headless/worker-trie-integration.js @@ -9,12 +9,24 @@ import { capabilities } from '@keymanapp/common-test-resources/model-helpers.mjs /* * How to run the worlist */ -describe('LMLayer using the trie model', function () { +describe('LMLayer using the trie model', function () { let lmLayer; + let worker; + + beforeEach(function() { + worker = Worker.constructInstance(); + lmLayer = new LMLayer(capabilities(), worker); + }); + + afterEach(function () { + // As we're using Node worker threads here, failure to terminate them will cause the + // headless test run to hang after completion. + lmLayer.shutdown(); + worker.terminate(); // should be covered by the former, but just in case... for CI stability. + }); + describe('Prediction', function () { var EXPECTED_SUGGESTIONS = 3; it('will predict an empty buffer', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - // We're testing many as asynchronous messages in a row. // this would be cleaner using async/await syntax. // Not done yet, as this test case is a slightly-edited copy of the in-browser version. @@ -55,8 +67,6 @@ describe('LMLayer using the trie model', function () { // // https://community.software.sil.org/t/search-term-to-key-in-lexical-model-not-working-both-ways-by-default/3133 it('should use the default searchTermToKey()', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - return lmLayer.loadModel( // We're running headlessly, so the path can be relative to the npm root directory. require.resolve("@keymanapp/common-test-resources/models/naive-trie.js") -- GitLab From 6440eeb4ea1de784d5166e6418984c27a9b8360a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 5 Jun 2023 10:15:07 +0700 Subject: [PATCH 356/386] docs(common/models): on related package, inability to use Node Blob --- common/predictive-text/src/node/mappedWorker.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/common/predictive-text/src/node/mappedWorker.ts b/common/predictive-text/src/node/mappedWorker.ts index 7e2297b45b..aafbba5232 100644 --- a/common/predictive-text/src/node/mappedWorker.ts +++ b/common/predictive-text/src/node/mappedWorker.ts @@ -48,6 +48,17 @@ self.importScripts = importScripts; * Uses the Node version of Workers to provide proper, authentic separate-thread * 'sandboxing'. Also intercepts and interprets certain WebWorker method signatures * necessary to run the WebWorker-oriented worker code. + * + * Alternatively, only after writing this did I discover this package: + * https://github.com/developit/web-worker. They also ran one notable issue I did: + * Node 18.x, at least, does not support use of Node Blobs for construction of a + * Worker: https://github.com/developit/web-worker/pull/32... unlike Web Workers. + * + * So... Base64-encoded Data URLs it is. + * + * What we have here is perfectly fine for now, but if we need more complicated + * cross-platform Worker support in the future, it may be wise to swap to use of + * that package. */ export default class MappedWorker extends worker.Worker implements Worker { constructor(scriptStr: string) { -- GitLab From 78e86ac9f4f83eb2b7c6d465cfe792a355312a2a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 5 Jun 2023 11:44:17 +0700 Subject: [PATCH 357/386] chore(common/models): fixes a formatting nit --- .../unit_tests/headless/worker-trie-integration.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/predictive-text/unit_tests/headless/worker-trie-integration.js b/common/predictive-text/unit_tests/headless/worker-trie-integration.js index 3850c086b6..8cd257878d 100644 --- a/common/predictive-text/unit_tests/headless/worker-trie-integration.js +++ b/common/predictive-text/unit_tests/headless/worker-trie-integration.js @@ -9,7 +9,8 @@ import { capabilities } from '@keymanapp/common-test-resources/model-helpers.mjs /* * How to run the worlist */ -describe('LMLayer using the trie model', function () { let lmLayer; +describe('LMLayer using the trie model', function () { + let lmLayer; let worker; beforeEach(function() { -- GitLab From 351cedf25ff62380dedd1b57e758a906f7fdb451 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 5 Jun 2023 12:19:02 +0700 Subject: [PATCH 358/386] fix(common/web): fixes Node-worker-reliant input-processor integration tests --- .../src/text/prediction/predictionContext.ts | 2 +- .../tests/cases/inputProcessor.js | 46 ++++++------ .../tests/cases/languageProcessor.js | 32 ++++++--- .../tests/cases/predictionContext.js | 72 +++++++++++++------ common/web/utils/build.sh | 16 ++--- 5 files changed, 105 insertions(+), 63 deletions(-) diff --git a/common/web/input-processor/src/text/prediction/predictionContext.ts b/common/web/input-processor/src/text/prediction/predictionContext.ts index 3146b0b95a..7578545b98 100644 --- a/common/web/input-processor/src/text/prediction/predictionContext.ts +++ b/common/web/input-processor/src/text/prediction/predictionContext.ts @@ -170,7 +170,7 @@ export default class PredictionContext extends EventEmitter`; else, `null`. + * @returns if `suggestion` is a `Suggestion`, will return a `Promise`; else, `null`. */ public accept(suggestion: Suggestion): Promise | null { let _this = this; diff --git a/common/web/input-processor/tests/cases/inputProcessor.js b/common/web/input-processor/tests/cases/inputProcessor.js index f3f3ca6b04..a2ba363edc 100644 --- a/common/web/input-processor/tests/cases/inputProcessor.js +++ b/common/web/input-processor/tests/cases/inputProcessor.js @@ -35,26 +35,32 @@ describe('InputProcessor', function() { it('has expected default values after initialization', function () { // Can construct without the second parameter; if so, the final assertion - .mayPredict // will be invalidated. (No worker, no ability to predict.) - let core = new InputProcessor(device, Worker.constructInstance()); - - assert.isOk(core.keyboardProcessor); - assert.isDefined(core.keyboardProcessor.contextDevice); - assert.isOk(core.languageProcessor); - assert.isOk(core.keyboardInterface); - assert.isUndefined(core.activeKeyboard); // No keyboard should be loaded yet. - assert.isUndefined(core.activeModel); // Same for the model. - - // These checks are lifted from the keyboard-processor init checks found in - // common/web/keyboard-processor/tests/cases/basic-init.js. - assert.equal('us', core.keyboardProcessor.baseLayout, 'KeyboardProcessor has unexpected base layout') - assert.isNotNull(global.KeymanWeb, 'KeymanWeb global was not automatically installed'); - assert.equal('default', core.keyboardProcessor.layerId, 'Default layer is not set to "default"'); - assert.isUndefined(core.keyboardProcessor.activeKeyboard, 'Initialized with already-active keyboard'); - - // Lifted from languageProcessor.js - the core should not be changing these with its init. - assert.isUndefined(core.languageProcessor.activeModel); - assert.isFalse(core.languageProcessor.isActive); - assert.isTrue(core.languageProcessor.mayPredict); + let worker = Worker.constructInstance(); + + try { + let core = new InputProcessor(device, worker); + + assert.isOk(core.keyboardProcessor); + assert.isDefined(core.keyboardProcessor.contextDevice); + assert.isOk(core.languageProcessor); + assert.isOk(core.keyboardInterface); + assert.isUndefined(core.activeKeyboard); // No keyboard should be loaded yet. + assert.isUndefined(core.activeModel); // Same for the model. + + // These checks are lifted from the keyboard-processor init checks found in + // common/web/keyboard-processor/tests/cases/basic-init.js. + assert.equal('us', core.keyboardProcessor.baseLayout, 'KeyboardProcessor has unexpected base layout') + assert.isNotNull(global.KeymanWeb, 'KeymanWeb global was not automatically installed'); + assert.equal('default', core.keyboardProcessor.layerId, 'Default layer is not set to "default"'); + assert.isUndefined(core.keyboardProcessor.activeKeyboard, 'Initialized with already-active keyboard'); + + // Lifted from languageProcessor.js - the core should not be changing these with its init. + assert.isUndefined(core.languageProcessor.activeModel); + assert.isFalse(core.languageProcessor.isActive); + assert.isTrue(core.languageProcessor.mayPredict); + } finally { + worker.terminate(); + } }); }); diff --git a/common/web/input-processor/tests/cases/languageProcessor.js b/common/web/input-processor/tests/cases/languageProcessor.js index 06c017b426..f1934a5f6e 100644 --- a/common/web/input-processor/tests/cases/languageProcessor.js +++ b/common/web/input-processor/tests/cases/languageProcessor.js @@ -21,14 +21,24 @@ String.kmwEnableSupplementaryPlane(false); // Test the KeyboardProcessor interface. describe('LanguageProcessor', function() { + let worker; + + beforeEach(function() { + worker = LMWorker.constructInstance(); + }); + + afterEach(function() { + worker.terminate(); + }); + describe('[[constructor]]', function () { it('should initialize without errors', function () { - let lp = new LanguageProcessor(LMWorker.constructInstance()); + let lp = new LanguageProcessor(worker); assert.isNotNull(lp); }); it('has expected default values after initialization', function () { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); // These checks are lifted from the keyboard-processor init checks found in // common/web/keyboard-processor/tests/cases/basic-init.js. @@ -70,7 +80,7 @@ describe('LanguageProcessor', function() { }; it("successfully loads the model", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); languageProcessor.loadModel(modelSpec).then(function() { assert.isOk(languageProcessor.activeModel); // is only set after a successful load. @@ -81,7 +91,7 @@ describe('LanguageProcessor', function() { }); it("generates the expected prediction set", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -117,7 +127,7 @@ describe('LanguageProcessor', function() { describe("does not alter casing when input is lowercased", function() { it("when input is fully lowercased", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -136,7 +146,7 @@ describe('LanguageProcessor', function() { }); it("when input has non-initial uppercased letters", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("lI", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -156,7 +166,7 @@ describe('LanguageProcessor', function() { }); it("unless the suggestion has uppercased letters", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("i", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -177,7 +187,7 @@ describe('LanguageProcessor', function() { describe("uppercases suggestions when input is fully capitalized ", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("LI", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -197,7 +207,7 @@ describe('LanguageProcessor', function() { }); it("for precapitalized suggestions", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("I", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -219,7 +229,7 @@ describe('LanguageProcessor', function() { describe("initial-cases suggestions when input uses initial casing ", function() { describe("when input is a single capitalized letter", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("L", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -241,7 +251,7 @@ describe('LanguageProcessor', function() { describe("input length > 1", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("Li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); diff --git a/common/web/input-processor/tests/cases/predictionContext.js b/common/web/input-processor/tests/cases/predictionContext.js index 3db0e89b58..fd03dda639 100644 --- a/common/web/input-processor/tests/cases/predictionContext.js +++ b/common/web/input-processor/tests/cases/predictionContext.js @@ -66,8 +66,18 @@ const appleDummyModel = { }; describe("PredictionContext", () => { - it('receives predictions as they are generated', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let worker; + + beforeEach(function() { + worker = LMWorker.constructInstance(); + }); + + afterEach(function() { + worker.terminate(); + }); + + it('receives predictions as they are generated', async function () { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -107,8 +117,8 @@ describe("PredictionContext", () => { assert.equal(suggestions.find((obj) => obj.transform.deleteLeft != 0).displayAs, 'apps'); }); - it('sendUpdateState retrieves the most recent suggestion set', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + it('sendUpdateState retrieves the most recent suggestion set', async function() { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -133,8 +143,8 @@ describe("PredictionContext", () => { assert.sameOrderedMembers(suggestions, initialSuggestions); }); - it('suggestion application logic & triggered effects', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + it('suggestion application logic & triggered effects', async function () { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -167,6 +177,15 @@ describe("PredictionContext", () => { const suggestionApply = suggestions.find((obj) => obj.displayAs == 'apply'); assert.isOk(suggestionApply); + // For awaiting the suggestions generated upon applying our desired suggestion. + // We aren't given a direct Promise for that, but we can construct one this way. + let postApplySuggestions = new Promise((resolve) => { + predictiveContext.once('update', resolve); + }); + + // Apply the desired suggestion. Also passively generates new, post-acceptance + // suggestions, but this function itself don't provide a Promise for that... + // hence the previous block. let promiseForApplyReversion = predictiveContext.accept(suggestionApply); assert.equal(updateFake.callCount, 1); // No new 'update' has been raised yet. @@ -177,8 +196,7 @@ describe("PredictionContext", () => { assert.equal(textState.getText(), 'apply '); let reversion = await promiseForApplyReversion; - // We don't seem to need to additionally rig a wait for the triggered predict call; it - // always completes first. Neat. If test becomes unstable... yeah, rig up a wait for it. + await postApplySuggestions; // Check 2: a second 'update' - post-application predictions! assert.equal(updateFake.callCount, 2); @@ -193,8 +211,8 @@ describe("PredictionContext", () => { // All other reversion details are tested in the 'reversion application logic...' section defined below. }); - it('reversion application logic & triggered effects', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + it('reversion application logic & triggered effects', async function () { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -222,7 +240,15 @@ describe("PredictionContext", () => { assert.isOk(suggestionApply); let previousTextState = Mock.from(textState); + + // For awaiting the suggestions generated upon applying our desired suggestion. + // We aren't given a direct Promise for that, but we can construct one this way. + let postApplySuggestions = new Promise((resolve) => { + predictiveContext.once('update', resolve); + }); + let reversion = await predictiveContext.accept(suggestionApply); + await postApplySuggestions; // Test setup complete. @@ -243,6 +269,19 @@ describe("PredictionContext", () => { // Fire away! Time to apply the reversion. previousTextState = Mock.from(textState); + + // Since the test uses a separate thread via Worker, make sure to set up any important event handlers + // before we request the reversion. + let updateFake = sinon.fake(); + predictiveContext.on('update', updateFake); + + // Now, in order to synchronize... we rely on a Promise. The callback is indeed + // called synchronously. + let postRevertSuggestions = new Promise((resolve) => { + predictiveContext.once('update', resolve); + }); + + // And now, apply the reversion itself. let returnValue = predictiveContext.accept(reversion); // 'accepting' a reversion performs a rewind; there's no need for async ops here. @@ -256,17 +295,8 @@ describe("PredictionContext", () => { // Note: no space appended. assert.equal(textState.getText(), rewoundTextStateWithInput.getText()); - // Note: accepting a reversion will trigger a new prediction that completes - // asynchronously, and unfortunately... we have no handle for it. - - let updateFake = sinon.fake(); - predictiveContext.on('update', updateFake); - - // Now, in order to synchronize... we rely on a Promise. The callback is indeed - // called synchronously. - await new Promise((resolve) => { - predictiveContext.once('update', resolve); - }); + // Re-synchronize once we've received word of the new post-reversion predictions. + await postRevertSuggestions; assert.equal(updateFake.callCount, 1); const suggestionsPostReversion = updateFake.firstCall.args[0]; diff --git a/common/web/utils/build.sh b/common/web/utils/build.sh index f8daffb4a3..bd55e007d6 100755 --- a/common/web/utils/build.sh +++ b/common/web/utils/build.sh @@ -39,16 +39,12 @@ if builder_start_action clean; then fi if builder_start_action build; then - # Note: in a dependency build, we'll expect utils to be built by tsc -b - if builder_is_dep_build; then - builder_echo "skipping tsc -b; will be completed by $builder_dep_parent" - else - tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" - node build-bundler.js - - # So... tsc does declaration-bundling on its own pretty well, at least for local development. - tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts - fi + tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" + node build-bundler.js + + # So... tsc does declaration-bundling on its own pretty well, at least for local development. + tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts + builder_finish_action success build fi -- GitLab From 2a0ee25c4a93eac0496b02d6459abd56fb1028e6 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 5 Jun 2023 14:02:35 -0400 Subject: [PATCH 359/386] auto: increment master version to 17.0.119 --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 2b501ebd15..50d6433464 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 17.0.118 alpha 2023-06-05 + +* chore(ios): replace fv cert (#8900) +* fix(core): Fix compilation if hotdoc is installed (#8912) +* feat(linux): Rename column and add tooltip (#8918) + ## 17.0.117 alpha 2023-06-04 * chore(developer): verify long lines compile correctly (#8915) diff --git a/VERSION.md b/VERSION.md index 2398e0d103..7d14d88a17 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.118 \ No newline at end of file +17.0.119 \ No newline at end of file -- GitLab From 4fee258d01d4fbd29a471cf3af50bd8de6e0ec86 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Tue, 6 Jun 2023 08:40:03 +0700 Subject: [PATCH 360/386] chore(web): Apply suggestions from code review Co-authored-by: Marc Durdin --- web/src/app/browser/src/context/pageIntegrationHandlers.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web/src/app/browser/src/context/pageIntegrationHandlers.ts b/web/src/app/browser/src/context/pageIntegrationHandlers.ts index a8027d2cc6..a2cd64f156 100644 --- a/web/src/app/browser/src/context/pageIntegrationHandlers.ts +++ b/web/src/app/browser/src/context/pageIntegrationHandlers.ts @@ -37,7 +37,7 @@ export class PageIntegrationHandlers { */ private mobilePageTrailer: HTMLDivElement; - private rotationProcessor: RotationProcessor + private rotationProcessor: RotationProcessor; constructor(window: Window, engine: KeymanEngine) { this.window = window; @@ -223,9 +223,7 @@ export class PageIntegrationHandlers { eventTracker.detachDOMEvent(docBody, 'touchmove', this.touchMoveActivationHandler, false); eventTracker.detachDOMEvent(docBody, 'touchend', this.touchEndActivationHandler, false); - if(this.mobilePageTrailer) { - this.mobilePageTrailer.parentElement.removeChild(this.mobilePageTrailer); - } + this.mobilePageTrailer?.parentElement.removeChild(this.mobilePageTrailer); } eventTracker.detachDOMEvent(window, 'load', this._WindowLoad, false); -- GitLab From a8c6d4defc090ac2c6350597152f2c6b48b6fa5d Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Tue, 6 Jun 2023 08:54:26 +0700 Subject: [PATCH 361/386] chore(web): Apply suggestions from code review Co-authored-by: Marc Durdin --- web/src/app/browser/src/keymanEngine.ts | 2 +- web/src/app/browser/src/utilApiEndpoint.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index d205c37e4b..cda7fbf419 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -40,7 +40,7 @@ export default class KeymanEngine extends KeymanEngineBase { this.contextManager.restoreLastActiveTarget(); diff --git a/web/src/app/browser/src/utilApiEndpoint.ts b/web/src/app/browser/src/utilApiEndpoint.ts index 19c09efe62..a1c67b1b21 100644 --- a/web/src/app/browser/src/utilApiEndpoint.ts +++ b/web/src/app/browser/src/utilApiEndpoint.ts @@ -304,7 +304,7 @@ export class UtilApiEndpoint { } /** - * Function toNumber + * Function toFloat * Scope Public * @param {string} s numeric string * @param {number} dflt default value -- GitLab From e005ca4734d1092208ba73e2ab733cf5639b039b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 6 Jun 2023 12:48:34 +0700 Subject: [PATCH 362/386] fix(web): forgot to ensure that the design-iframe also fully loads --- .../test/auto/dom/cases/attachment/pageContextAttachment.js | 3 +++ web/src/test/auto/dom/cases/browser/contextManager.js | 1 + 2 files changed, 4 insertions(+) diff --git a/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js b/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js index abc1999e2c..5076432ef5 100644 --- a/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js +++ b/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js @@ -268,6 +268,7 @@ describe('KMW element-attachment logic', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); // Give the design-mode iframe a bit of time to set itself up properly. // Note: it is thus important that whatever sends the `install` command has also @@ -495,6 +496,7 @@ describe('KMW element-attachment logic', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); // Our mutation observers delay slightly here to ensure that any doc-internal handlers // have a chance to resolve before we attach. Currently: 10ms. @@ -639,6 +641,7 @@ describe('KMW element-attachment logic', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); await timedPromise(20); // for the design-iframe to set itself into design-mode. diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index 783db07d77..7d749bf30d 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -149,6 +149,7 @@ describe('app/browser: ContextManager', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); // Give the design-mode iframe a bit of time to set itself up properly. // Note: it is thus important that whatever sends the `install` command has also -- GitLab From 78dc7b5ffffcd2d245e715526037bb08d411248b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Jun 2023 09:05:21 +0700 Subject: [PATCH 363/386] fix(web): KMW handling of OSK-swapping, partially configured paths in internal stubs --- web/src/engine/main/src/keyboardInterface.ts | 11 ++- web/src/engine/main/src/keymanEngine.ts | 7 +- .../engine/package-cache/src/keyboardStub.ts | 68 +++++++++++-------- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/web/src/engine/main/src/keyboardInterface.ts b/web/src/engine/main/src/keyboardInterface.ts index b2035c5190..305373aea3 100644 --- a/web/src/engine/main/src/keyboardInterface.ts +++ b/web/src/engine/main/src/keyboardInterface.ts @@ -77,14 +77,19 @@ export default class KeyboardInterface { diff --git a/web/src/engine/package-cache/src/keyboardStub.ts b/web/src/engine/package-cache/src/keyboardStub.ts index 2a65cc277f..e36314bf89 100644 --- a/web/src/engine/package-cache/src/keyboardStub.ts +++ b/web/src/engine/package-cache/src/keyboardStub.ts @@ -18,6 +18,36 @@ export type KeyboardAPISpec = (APISimpleKeyboard | APICompoundKeyboard) & { export interface RawKeyboardStub extends KeyboardStub {}; +/* + * Get keyboard path (relative or absolute) + * KeymanWeb 2 revised keyboard location specification: + * (a) absolute URL (includes ':') - load from specified URL + * (b) relative URL (starts with /, ./, ../) - load with respect to current page + * (c) filename only (anything else) - prepend keyboards option to URL + * (e.g. default keyboards option will be set by Cloud) + * + * So, to fully interpret the following regex, it detects the following patterns (at minimum): + * ../file (but not .../file) + * ./file + * /file + * http:// (on the colon) + * hello:world (on the colon) - that one miiiight be less intentional, though. Would 'fall + * over' on attempted use anyway, since it's not a valid path. + * + * Alternative clearer version - '^(\.{0,2}/)|(:)'? + * Unless backslashes should be able to replace dots? + */ +const REGEX_FOR_PRECONFIGURED_PATH=RegExp('^(([\\.]/)|([\\.][\\.]/)|(/))|(:)'); + +function configureFilePathing(path: string, configurationBasePath: string) { + configurationBasePath = configurationBasePath || ''; + if(path && !REGEX_FOR_PRECONFIGURED_PATH.test(path)) { + return configurationBasePath + path; + } else { + return path; + } +} + export default class KeyboardStub extends KeyboardProperties { KR: string; KRC: string; @@ -25,7 +55,9 @@ export default class KeyboardStub extends KeyboardProperties { KP?: string; - public constructor(rawStub: RawKeyboardStub); + // For the first flavor of constructor, note that Developer relies on KMW's path config to complete the paths... + // even though supplying an 'internal'-style stub. + public constructor(rawStub: RawKeyboardStub, keyboardBaseUri?: string, fontBaseUri?: string); public constructor(apiSpec: APISimpleKeyboard & { filename: string }, keyboardBaseUri?: string, fontBaseUri?: string); public constructor(kbdId: string, lngId: string); constructor(arg0: string | RawKeyboardStub | (APISimpleKeyboard & { filename: string }), arg1?: string, arg2?: string) { @@ -34,43 +66,19 @@ export default class KeyboardStub extends KeyboardProperties { let apiSpec = arg0 as APISimpleKeyboard & { filename: string }; apiSpec.id = prefixed(apiSpec.id); super(apiSpec, arg2); - this.KF = apiSpec.filename; + this.KF = configureFilePathing(apiSpec.filename, arg1); this.mapRegion(apiSpec.languages); - - /* - * Get keyboard path (relative or absolute) - * KeymanWeb 2 revised keyboard location specification: - * (a) absolute URL (includes ':') - load from specified URL - * (b) relative URL (starts with /, ./, ../) - load with respect to current page - * (c) filename only (anything else) - prepend keyboards option to URL - * (e.g. default keyboards option will be set by Cloud) - * - * So, to fully interpret the following regex, it detects the following patterns (at minimum): - * ../file (but not .../file) - * ./file - * /file - * http:// (on the colon) - * hello:world (on the colon) - that one miiiight be less intentional, though. Would 'fall - * over' on attempted use anyway, since it's not a valid path. - * - * Alternative clearer version - '^(\.{0,2}/)|(:)'? - * Unless backslashes should be able to replace dots? - */ - let rx=RegExp('^(([\\.]/)|([\\.][\\.]/)|(/))|(:)'); - - arg1 = arg1 || ''; - if(this.KF && !rx.test(this.KF)) { - this.KF = arg1 + this.KF; - } } else { let rawStub = arg0 as RawKeyboardStub; rawStub.KI = prefixed(rawStub.KI); - super(rawStub); + super(rawStub, arg2); - this.KF = rawStub.KF; + this.KF = configureFilePathing(rawStub.KF, arg1); this.KP = rawStub.KP; this.KR = rawStub.KR; this.KRC = rawStub.KRC; + + return; } } else { -- GitLab From f1c07a27750fd4710a27afde1cd339913899dd4d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Jun 2023 09:05:59 +0700 Subject: [PATCH 364/386] fix(developer): updates the internal KMW endpoints used by the test-host --- developer/src/server/src/site/chargrid.js | 15 ++++----------- developer/src/server/src/site/test.js | 3 +-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/developer/src/server/src/site/chargrid.js b/developer/src/server/src/site/chargrid.js index 1ce9206bae..f6c31f2df3 100644 --- a/developer/src/server/src/site/chargrid.js +++ b/developer/src/server/src/site/chargrid.js @@ -73,17 +73,10 @@ function updateLogCursor() { var i, selStart, selLength, selDirection; - if(keyman.isPositionSynthesized()) { // this is an internal function - // For touch devices, we need to ask KMW - selStart = 0; - selLength = 0; - selDirection = 'forward'; - } else { - // For desktop devices, we use the position reported by the textarea control - selStart = ta1.selectionStart; - selLength = ta1.selectionEnd - ta1.selectionStart; - selDirection = ta1.selectionDirection; - } + // We use the position reported by the textarea control + selStart = ta1.selectionStart; + selLength = ta1.selectionEnd - ta1.selectionStart; + selDirection = ta1.selectionDirection; selLength = calculateLengthByCodepoint(ta1.value, selStart, selLength); selStart = calculateLengthByCodepoint(ta1.value, 0, selStart); diff --git a/developer/src/server/src/site/test.js b/developer/src/server/src/site/test.js index de27deccc5..a154446100 100644 --- a/developer/src/server/src/site/test.js +++ b/developer/src/server/src/site/test.js @@ -238,11 +238,10 @@ window.onload = function() { keyman.addEventListener('keyboardchange', function(keyboardProperties) { if(newOSK) { keyman.osk = newOSK; - newOSK.activeKeyboard = keyman.core.activeKeyboard; + newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; } keyboardDropdown.set(keyboardProperties.internalName); window.sessionStorage.setItem('current-keyboard', keyboardProperties.internalName); - keyman.alignInputs(); }); } -- GitLab From bcbe31c7f6a632b66c6f91337bec63637b5d78eb Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Jun 2023 09:50:36 +0700 Subject: [PATCH 365/386] feat(web): adds unit test to catch partial-stub problem early --- .../src/keyboards/keyboardProperties.ts | 1 + .../auto/headless/packages/keyboardStub.js | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts b/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts index 8495d6253e..e83d1a137f 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts +++ b/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts @@ -142,6 +142,7 @@ export default class KeyboardProperties implements KeyboardInternalPropertySpec this.KN = other.KN; this.KL = other.KL; this.KLC = other.KLC; + // Do NOT apply fontPath here; the mobile apps will have font issues if you do! this.KFont = other.KFont; this.KOskFont = other.KOskFont; this._displayName = (other instanceof KeyboardProperties) ? other._displayName : other.displayName; diff --git a/web/src/test/auto/headless/packages/keyboardStub.js b/web/src/test/auto/headless/packages/keyboardStub.js index 4e9d08500d..af6d3bb683 100644 --- a/web/src/test/auto/headless/packages/keyboardStub.js +++ b/web/src/test/auto/headless/packages/keyboardStub.js @@ -38,6 +38,48 @@ describe("KeyboardStub", () => { }; } + it('construction from internal stub format, partially configured paths', () => { + const rawStub = { + KI: 'dummy', + KN: 'test dummy', + KL: 'English', + KLC: 'en', + KF: 'dummy.js', + // The way font paths are currently handled feels pretty rough and unclear. + // Their paths aren't updated in the same way as the KF entry. + // So... leaving font stuff out of the test for now. + // (Also, Developer doesn't seem to bother specifying font files in its stubs, so it's + // less criitcal.) + }; + + const stub = new KeyboardStub(rawStub, 'http://localhost/keyboards/', 'http://localhost/fonts/'); + + assert.equal(stub.KF, 'http://localhost/keyboards/dummy.js'); + }); + + it('construction from internal stub format, pre-configured paths', () => { + // Based on actual font pathing as hosted by the Android app. + const absolutePath = '/data/user/0/com.tavultesoft.kmapro.debug/app_data/packages/dummy/dummy.ttf'; + + const rawStub = { + KI: 'dummy', + KN: 'test dummy', + KL: 'English', + KLC: 'en', + KF: absolutePath, + // The way font paths are currently handled feels pretty rough and unclear. + // Their paths aren't updated in the same way as the KF entry. + // So... leaving font stuff out of the test for now. + // (Also, Developer doesn't seem to bother specifying font files in its stubs, so it's + // less criitcal.) + }; + + // These components... are not, but that's OK - the test is to ignore them. + const stub = new KeyboardStub(rawStub, 'http://localhost/keyboards/', 'http://localhost/fonts/'); + + assert.equal(stub.KF, absolutePath); + }); + it('merge(): barebones stub + fetched sil_euro_latin@no', async () => { const query = performMockedRequest(`${__dirname}/../../resources/query-mock-results/sil_euro_latin@no_sv.js.fixture`); await query.promise; -- GitLab From 49788c936415fc78593d9a7886bb7f9b87d1e782 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Jun 2023 12:39:37 +0700 Subject: [PATCH 366/386] change(web): double-encoded -> single-encoded --- common/predictive-text/src/unwrap.ts | 4 ++-- common/web/lm-worker/build-wrap-and-minify.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/predictive-text/src/unwrap.ts b/common/predictive-text/src/unwrap.ts index 4199a3d760..fc0769e5bd 100644 --- a/common/predictive-text/src/unwrap.ts +++ b/common/predictive-text/src/unwrap.ts @@ -6,6 +6,6 @@ * @param fn The function whose body will be returned. */ export default function unwrap(encodedSrc: string): string { - let wrapper = JSON.parse(encodedSrc); - return wrapper; + // There used to be more to this, but now it's a pretty simple passthrough! + return encodedSrc; } \ No newline at end of file diff --git a/common/web/lm-worker/build-wrap-and-minify.js b/common/web/lm-worker/build-wrap-and-minify.js index e5aef61060..bf50537d9b 100644 --- a/common/web/lm-worker/build-wrap-and-minify.js +++ b/common/web/lm-worker/build-wrap-and-minify.js @@ -68,13 +68,13 @@ const srcMapString = `//# sourceMappingURL=data:application/json;charset=utf-8;b let rawScript = workerConcatenation.script.toString(); // Two layers of encoding: one for the raw source (parsed by the JS engine), // one to 'unwrap' it from a string _within_ that source. -let jsonDoubleEncoded = JSON.stringify(JSON.stringify(rawScript)); +let jsonEncoded = JSON.stringify(rawScript); let wrapper = ` // Autogenerated code. Do not modify! // --START:LMLayerWorkerCode-- -export var LMLayerWorkerCode = ${jsonDoubleEncoded}; +export var LMLayerWorkerCode = ${jsonEncoded}; ${MINIFY && "// Sourcemaps have been omitted for this release build."} export var LMLayerWorkerSourcemapComment = "${DEBUG ? srcMapString : ''}"; -- GitLab From be7c5d092a872d476029c91e04f7c7f89cf03976 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Jun 2023 15:24:50 +0700 Subject: [PATCH 367/386] chore(web): cleanup per PR review --- .../testing/bulk_rendering/build-bundler.js | 1 - .../testing/bulk_rendering/renderer_core.ts | 22 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/web/src/tools/testing/bulk_rendering/build-bundler.js b/web/src/tools/testing/bulk_rendering/build-bundler.js index b7a10a0223..a9cbb011b7 100644 --- a/web/src/tools/testing/bulk_rendering/build-bundler.js +++ b/web/src/tools/testing/bulk_rendering/build-bundler.js @@ -6,7 +6,6 @@ */ import esbuild from 'esbuild'; -import { spawn } from 'child_process'; await esbuild.build({ bundle: true, diff --git a/web/src/tools/testing/bulk_rendering/renderer_core.ts b/web/src/tools/testing/bulk_rendering/renderer_core.ts index d663717f17..3348b72f16 100644 --- a/web/src/tools/testing/bulk_rendering/renderer_core.ts +++ b/web/src/tools/testing/bulk_rendering/renderer_core.ts @@ -6,7 +6,8 @@ import type { FloatingOSKView } from 'keyman/engine/osk'; declare var keyman: KeymanEngine; -type KeyboardMap = {[id: string]: any}; +type KeyboardData = ReturnType; +type KeyboardMap = {[id: string]: KeyboardData}; export class BatchRenderer { static divMaster: HTMLDivElement; @@ -50,7 +51,7 @@ export class BatchRenderer { private filterKeyboards(): KeyboardMap { let kbds = keyman.getKeyboards(); - let keyboardMap = []; + let keyboardMap = {}; for(var i = 0; i < kbds.length; i++) { let id: string = kbds[i].InternalName; @@ -94,20 +95,20 @@ export class BatchRenderer { return capture(); } - createKeyboardHeader(kbd, loaded: boolean): HTMLDivElement { + createKeyboardHeader(kbd: KeyboardData, loaded: boolean): HTMLDivElement { let divHeader = document.createElement('div'); let eleName = document.createElement('h2'); - eleName.textContent = 'ID: ' + kbd['InternalName']; + eleName.textContent = 'ID: ' + kbd.InternalName; divHeader.appendChild(eleName); let eleDescription = document.createElement('p'); if(loaded) { - eleDescription.appendChild(document.createTextNode('Name: ' + kbd['Name'])); + eleDescription.appendChild(document.createTextNode('Name: ' + kbd.Name)); eleDescription.appendChild(document.createElement('br')); - eleDescription.appendChild(document.createTextNode('Font: ' + window['keyman'].core.activeKeyboard._legacyLayoutSpec.F)); + eleDescription.appendChild(document.createTextNode('Font: ' + keyman.core.activeKeyboard['_legacyLayoutSpec'].F)); } else { eleDescription.appendChild(document.createTextNode('Unable to load this keyboard!')); @@ -118,15 +119,14 @@ export class BatchRenderer { return divHeader; } - private processKeyboard(kbd) { - let p: Promise = keyman.setActiveKeyboard(kbd['InternalName']); + private processKeyboard(kbd: KeyboardData) { + let p: Promise = keyman.setActiveKeyboard(kbd.InternalName); let isMobile = keyman.config.hostDevice.formFactor != 'desktop'; // Establish common keyboard header info. let divSummary = document.createElement('div'); // Establishes a linkable target for this keyboard's data. - divSummary.id = "summary-" + kbd['InternalName']; - + divSummary.id = "summary-" + kbd.InternalName; BatchRenderer.divMaster.insertAdjacentElement('afterbegin', divSummary); // A nice, closure-friendly reference for use in our callbacks. @@ -194,7 +194,7 @@ export class BatchRenderer { // The resulting Promise will only call it's `.then()` once all of this keyboard's renders have been completed. return renderer.arrayPromiseIteration(renderLayer, Object.keys(layers).length); }).catch(function() { - console.log("Failed to load the \"" + kbd['InternalName'] + "\" keyboard for rendering!"); + console.log("Failed to load the \"" + kbd.InternalName + "\" keyboard for rendering!"); divSummary.appendChild(renderer.createKeyboardHeader(kbd, false)); return Promise.resolve(); }); -- GitLab From f4ba29926df75af574b737db0d7902854961fa28 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 8 Jun 2023 11:57:35 +0700 Subject: [PATCH 368/386] chore(developer): apply suggestions from code review Co-authored-by: Marc Durdin --- developer/src/server/build.sh | 2 +- developer/src/server/src/site/test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index a4fff54af8..c735e26fec 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -124,7 +124,7 @@ fi if (( build_keymanweb )); then pushd "$KEYMAN_ROOT/web/" - ./build.sh build + ./build.sh build --debug popd fi diff --git a/developer/src/server/src/site/test.js b/developer/src/server/src/site/test.js index a154446100..60a27cc484 100644 --- a/developer/src/server/src/site/test.js +++ b/developer/src/server/src/site/test.js @@ -238,7 +238,7 @@ window.onload = function() { keyman.addEventListener('keyboardchange', function(keyboardProperties) { if(newOSK) { keyman.osk = newOSK; - newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; + newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; // Private API refs on both sides } keyboardDropdown.set(keyboardProperties.internalName); window.sessionStorage.setItem('current-keyboard', keyboardProperties.internalName); -- GitLab From 29c85c552c11894a72b584d7f468e22e769fd5cb Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 13:47:19 +0700 Subject: [PATCH 369/386] chore(web): minor esbuild version bump to get 'alias' option, adds tslib --- package-lock.json | 337 ++++++++++++++++++++++++++-------------------- package.json | 21 +-- 2 files changed, 202 insertions(+), 156 deletions(-) diff --git a/package-lock.json b/package-lock.json index aaa0fa58ea..8eeac938a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,15 +35,16 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.59.1", "chai": "^4.3.4", + "esbuild": "^0.15.16", "eslint": "^8.39.0", "eslint-config-standard-with-typescript": "^34.0.1", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", - "esbuild": "^0.15.15", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", "ts-node": "^10.9.1", + "tslib": "^2.5.2", "typescript": "^4.9.5" } }, @@ -348,6 +349,14 @@ "typescript": "^4.9.5" } }, + "common/web/tslib": { + "name": "@keymanapp/tslib", + "license": "MIT", + "dependencies": { + "tslib": "^2.5.2", + "typescript": "^4.9.5" + } + }, "common/web/types": { "name": "@keymanapp/common-types", "license": "MIT", @@ -2176,6 +2185,38 @@ "node": ">=12" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2249,38 +2290,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.15.tgz", - "integrity": "sha512-JJjZjJi2eBL01QJuWjfCdZxcIgot+VoK6Fq7eKF9w4YHm9hwl7nhBR1o2Wnt/WcANk5l9SkpvrldW1PLuXxcbw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.15.tgz", - "integrity": "sha512-lhz6UNPMDXUhtXSulw8XlFAtSYO26WmHQnCi2Lg2p+/TMiJKNLtZCYUxV4wG6rZMzXmr8InGpNwk+DLT2Hm0PA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -2450,6 +2459,10 @@ "resolved": "common/tools/sourcemap-path-remapper", "link": true }, + "node_modules/@keymanapp/tslib": { + "resolved": "common/web/tslib", + "link": true + }, "node_modules/@keymanapp/web-sentry-manager": { "resolved": "common/web/sentry-manager", "link": true @@ -2697,6 +2710,11 @@ "node": ">=6" } }, + "node_modules/@sentry/browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/cli": { "version": "2.2.0", "dev": true, @@ -2731,6 +2749,11 @@ "node": ">=6" } }, + "node_modules/@sentry/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/hub": { "version": "5.30.0", "license": "BSD-3-Clause", @@ -2743,6 +2766,11 @@ "node": ">=6" } }, + "node_modules/@sentry/hub/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/minimal": { "version": "5.30.0", "license": "BSD-3-Clause", @@ -2755,6 +2783,11 @@ "node": ">=6" } }, + "node_modules/@sentry/minimal/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/node": { "version": "6.19.6", "license": "BSD-3-Clause", @@ -2828,6 +2861,11 @@ "node": ">=6" } }, + "node_modules/@sentry/node/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/types": { "version": "5.30.0", "license": "BSD-3-Clause", @@ -2846,6 +2884,11 @@ "node": ">=6" } }, + "node_modules/@sentry/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "license": "MIT", @@ -4840,9 +4883,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.15.tgz", - "integrity": "sha512-TEw/lwK4Zzld9x3FedV6jy8onOUHqcEX3ADFk4k+gzPUwrxn8nWV62tH0udo8jOtjFodlEfc4ypsqX3e+WWO6w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", "dev": true, "hasInstallScript": true, "bin": { @@ -4852,34 +4895,34 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.15.15", - "@esbuild/linux-loong64": "0.15.15", - "esbuild-android-64": "0.15.15", - "esbuild-android-arm64": "0.15.15", - "esbuild-darwin-64": "0.15.15", - "esbuild-darwin-arm64": "0.15.15", - "esbuild-freebsd-64": "0.15.15", - "esbuild-freebsd-arm64": "0.15.15", - "esbuild-linux-32": "0.15.15", - "esbuild-linux-64": "0.15.15", - "esbuild-linux-arm": "0.15.15", - "esbuild-linux-arm64": "0.15.15", - "esbuild-linux-mips64le": "0.15.15", - "esbuild-linux-ppc64le": "0.15.15", - "esbuild-linux-riscv64": "0.15.15", - "esbuild-linux-s390x": "0.15.15", - "esbuild-netbsd-64": "0.15.15", - "esbuild-openbsd-64": "0.15.15", - "esbuild-sunos-64": "0.15.15", - "esbuild-windows-32": "0.15.15", - "esbuild-windows-64": "0.15.15", - "esbuild-windows-arm64": "0.15.15" + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" } }, "node_modules/esbuild-android-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.15.tgz", - "integrity": "sha512-F+WjjQxO+JQOva3tJWNdVjouFMLK6R6i5gjDvgUthLYJnIZJsp1HlF523k73hELY20WPyEO8xcz7aaYBVkeg5Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", "cpu": [ "x64" ], @@ -4893,9 +4936,9 @@ } }, "node_modules/esbuild-android-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.15.tgz", - "integrity": "sha512-attlyhD6Y22jNyQ0fIIQ7mnPvDWKw7k6FKnsXlBvQE6s3z6s6cuEHcSgoirquQc7TmZgVCK5fD/2uxmRN+ZpcQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", "cpu": [ "arm64" ], @@ -4909,9 +4952,9 @@ } }, "node_modules/esbuild-darwin-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.15.tgz", - "integrity": "sha512-ohZtF8W1SHJ4JWldsPVdk8st0r9ExbAOSrBOh5L+Mq47i696GVwv1ab/KlmbUoikSTNoXEhDzVpxUR/WIO19FQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "cpu": [ "x64" ], @@ -4925,9 +4968,9 @@ } }, "node_modules/esbuild-darwin-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.15.tgz", - "integrity": "sha512-P8jOZ5zshCNIuGn+9KehKs/cq5uIniC+BeCykvdVhx/rBXSxmtj3CUIKZz4sDCuESMbitK54drf/2QX9QHG5Ag==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", "cpu": [ "arm64" ], @@ -4941,9 +4984,9 @@ } }, "node_modules/esbuild-freebsd-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.15.tgz", - "integrity": "sha512-KkTg+AmDXz1IvA9S1gt8dE24C8Thx0X5oM0KGF322DuP+P3evwTL9YyusHAWNsh4qLsR80nvBr/EIYs29VSwuA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", "cpu": [ "x64" ], @@ -4957,9 +5000,9 @@ } }, "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.15.tgz", - "integrity": "sha512-FUcML0DRsuyqCMfAC+HoeAqvWxMeq0qXvclZZ/lt2kLU6XBnDA5uKTLUd379WYEyVD4KKFctqWd9tTuk8C/96g==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", "cpu": [ "arm64" ], @@ -4973,9 +5016,9 @@ } }, "node_modules/esbuild-linux-32": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.15.tgz", - "integrity": "sha512-q28Qn5pZgHNqug02aTkzw5sW9OklSo96b5nm17Mq0pDXrdTBcQ+M6Q9A1B+dalFeynunwh/pvfrNucjzwDXj+Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", "cpu": [ "ia32" ], @@ -4989,9 +5032,9 @@ } }, "node_modules/esbuild-linux-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.15.tgz", - "integrity": "sha512-217KPmWMirkf8liO+fj2qrPwbIbhNTGNVtvqI1TnOWJgcMjUWvd677Gq3fTzXEjilkx2yWypVnTswM2KbXgoAg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", "cpu": [ "x64" ], @@ -5005,9 +5048,9 @@ } }, "node_modules/esbuild-linux-arm": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.15.tgz", - "integrity": "sha512-RYVW9o2yN8yM7SB1yaWr378CwrjvGCyGybX3SdzPHpikUHkME2AP55Ma20uNwkNyY2eSYFX9D55kDrfQmQBR4w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", "cpu": [ "arm" ], @@ -5021,9 +5064,9 @@ } }, "node_modules/esbuild-linux-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.15.tgz", - "integrity": "sha512-/ltmNFs0FivZkYsTzAsXIfLQX38lFnwJTWCJts0IbCqWZQe+jjj0vYBNbI0kmXLb3y5NljiM5USVAO1NVkdh2g==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", "cpu": [ "arm64" ], @@ -5037,9 +5080,9 @@ } }, "node_modules/esbuild-linux-mips64le": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.15.tgz", - "integrity": "sha512-PksEPb321/28GFFxtvL33yVPfnMZihxkEv5zME2zapXGp7fA1X2jYeiTUK+9tJ/EGgcNWuwvtawPxJG7Mmn86A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", "cpu": [ "mips64el" ], @@ -5053,9 +5096,9 @@ } }, "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.15.tgz", - "integrity": "sha512-ek8gJBEIhcpGI327eAZigBOHl58QqrJrYYIZBWQCnH3UnXoeWMrMZLeeZL8BI2XMBhP+sQ6ERctD5X+ajL/AIA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", "cpu": [ "ppc64" ], @@ -5069,9 +5112,9 @@ } }, "node_modules/esbuild-linux-riscv64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.15.tgz", - "integrity": "sha512-H5ilTZb33/GnUBrZMNJtBk7/OXzDHDXjIzoLXHSutwwsLxSNaLxzAaMoDGDd/keZoS+GDBqNVxdCkpuiRW4OSw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", "cpu": [ "riscv64" ], @@ -5085,9 +5128,9 @@ } }, "node_modules/esbuild-linux-s390x": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.15.tgz", - "integrity": "sha512-jKaLUg78mua3rrtrkpv4Or2dNTJU7bgHN4bEjT4OX4GR7nLBSA9dfJezQouTxMmIW7opwEC5/iR9mpC18utnxQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", "cpu": [ "s390x" ], @@ -5101,9 +5144,9 @@ } }, "node_modules/esbuild-netbsd-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.15.tgz", - "integrity": "sha512-aOvmF/UkjFuW6F36HbIlImJTTx45KUCHJndtKo+KdP8Dhq3mgLRKW9+6Ircpm8bX/RcS3zZMMmaBLkvGY06Gvw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", "cpu": [ "x64" ], @@ -5117,9 +5160,9 @@ } }, "node_modules/esbuild-openbsd-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.15.tgz", - "integrity": "sha512-HFFX+WYedx1w2yJ1VyR1Dfo8zyYGQZf1cA69bLdrHzu9svj6KH6ZLK0k3A1/LFPhcEY9idSOhsB2UyU0tHPxgQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", "cpu": [ "x64" ], @@ -5133,9 +5176,9 @@ } }, "node_modules/esbuild-sunos-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.15.tgz", - "integrity": "sha512-jOPBudffG4HN8yJXcK9rib/ZTFoTA5pvIKbRrt3IKAGMq1EpBi4xoVoSRrq/0d4OgZLaQbmkHp8RO9eZIn5atA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", "cpu": [ "x64" ], @@ -5149,9 +5192,9 @@ } }, "node_modules/esbuild-windows-32": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.15.tgz", - "integrity": "sha512-MDkJ3QkjnCetKF0fKxCyYNBnOq6dmidcwstBVeMtXSgGYTy8XSwBeIE4+HuKiSsG6I/mXEb++px3IGSmTN0XiA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", "cpu": [ "ia32" ], @@ -5165,9 +5208,9 @@ } }, "node_modules/esbuild-windows-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.15.tgz", - "integrity": "sha512-xaAUIB2qllE888SsMU3j9nrqyLbkqqkpQyWVkfwSil6BBPgcPk3zOFitTTncEKCLTQy3XV9RuH7PDj3aJDljWA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", "cpu": [ "x64" ], @@ -5181,9 +5224,9 @@ } }, "node_modules/esbuild-windows-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.15.tgz", - "integrity": "sha512-ttuoCYCIJAFx4UUKKWYnFdrVpoXa3+3WWkXVI6s09U+YjhnyM5h96ewTq/WgQj9LFSIlABQvadHSOQyAVjW5xQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", "cpu": [ "arm64" ], @@ -6473,11 +6516,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/google-closure-compiler-java": { - "version": "20200224.0.0", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6806,20 +6844,6 @@ "version": "2.0.4", "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/inline-source-map": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", @@ -6838,6 +6862,20 @@ "node": ">=0.10.0" } }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "1.4.0", "dev": true, @@ -7639,18 +7677,18 @@ "version": "4.4.2", "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, "node_modules/lodash.memoize": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.set": { "version": "4.3.2", "license": "MIT" @@ -10191,8 +10229,9 @@ } }, "node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -10209,6 +10248,12 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tunnel": { "version": "0.0.6", "license": "MIT", @@ -10993,7 +11038,6 @@ "@keymanapp/web-sentry-manager": "*", "@sentry/cli": "2.2.0", "chai": "^4.3.4", - "google-closure-compiler-java": "^20200224.0.0", "karma": "^6.4.1", "karma-browserstack-launcher": "^1.6.0", "karma-chai": "^0.1.0", @@ -11011,6 +11055,7 @@ "mocha": "^10.0.0", "modernizr": "^3.11.7", "ts-node": "^10.9.1", + "tslib": "^2.5.2", "typescript": "^4.9.5" } } diff --git a/package.json b/package.json index 11fa521e18..787fe68e5a 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,19 @@ "name": "root", "private": true, "devDependencies": { - "chai": "^4.3.4", - "esbuild": "^0.15.15", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5", "@typescript-eslint/eslint-plugin": "^5.59.1", + "chai": "^4.3.4", + "esbuild": "^0.15.16", "eslint": "^8.39.0", "eslint-config-standard-with-typescript": "^34.0.1", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.1.1" + "eslint-plugin-promise": "^6.1.1", + "mocha": "^10.0.0", + "mocha-teamcity-reporter": "^4.0.0", + "ts-node": "^10.9.1", + "tslib": "^2.5.2", + "typescript": "^4.9.5" }, "scripts": {}, "workspaces": [ @@ -37,10 +38,10 @@ "web" ], "dependencies": { + "@keymanapp/common-types": "file:common/web/types", + "@keymanapp/developer-test-helpers": "file:developer/src/common/web/test-helpers", "@keymanapp/hextobin": "file:common/tools/hextobin", "@keymanapp/keyman-version": "file:common/web/keyman-version", - "@keymanapp/common-types": "file:common/web/types", - "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml", - "@keymanapp/developer-test-helpers": "file:developer/src/common/web/test-helpers" + "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" } } -- GitLab From 06434953a8b1541e88a99e5d3fc8824779f1eaff Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 14:13:57 +0700 Subject: [PATCH 370/386] chore(web): KMW use of importHelpers --- common/predictive-text/build-bundler.js | 3 ++ common/predictive-text/src/node/tsconfig.json | 1 + common/predictive-text/src/tsconfig.json | 1 + common/predictive-text/src/web/tsconfig.json | 1 + common/predictive-text/tsconfig.all.json | 1 + common/web/input-processor/build-bundler.js | 6 ++++ common/web/input-processor/tsconfig.json | 1 + .../web/keyboard-processor/build-bundler.js | 15 +++++++++ .../src/keyboards/loaders/tsconfig.dom.json | 1 + .../keyboard-processor/tsconfig.common.json | 1 + common/web/keyboard-processor/tsconfig.json | 1 + common/web/tslib/README.md | 15 +++++++++ common/web/tslib/build.sh | 31 +++++++++++++++++++ common/web/tslib/package.json | 25 +++++++++++++++ common/web/tslib/src/index.ts | 3 ++ common/web/tslib/tsconfig.json | 21 +++++++++++++ common/web/utils/build-bundler.js | 6 ++++ common/web/utils/tsconfig.json | 1 + package-lock.json | 4 +-- web/package.json | 3 +- web/src/app/browser/build-bundler.js | 28 ++++++++--------- web/src/app/browser/tsconfig.json | 1 + web/src/app/webview/build-bundler.js | 6 ++++ web/src/app/webview/tsconfig.json | 1 + web/src/engine/attachment/build-bundler.js | 3 ++ .../attachment/src/pageContextAttachment.ts | 2 +- web/src/engine/attachment/tsconfig.json | 1 + web/src/engine/device-detect/build-bundler.js | 3 ++ web/src/engine/device-detect/tsconfig.json | 1 + web/src/engine/dom-utils/tsconfig.json | 1 + .../engine/element-wrappers/build-bundler.js | 3 ++ web/src/engine/element-wrappers/tsconfig.json | 1 + web/src/engine/events/build-bundler.js | 3 ++ web/src/engine/events/tsconfig.json | 1 + web/src/engine/main/build-bundler.js | 3 ++ web/src/engine/main/tsconfig.json | 1 + web/src/engine/osk/build-bundler.js | 3 ++ web/src/engine/osk/tsconfig.json | 1 + web/src/engine/package-cache/build-bundler.js | 9 ++++++ .../src/keyboardRequisitioner.ts | 2 +- web/src/engine/package-cache/tsconfig.json | 1 + web/src/engine/paths/build-bundler.js | 3 ++ web/src/engine/paths/tsconfig.json | 1 + 43 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 common/web/tslib/README.md create mode 100755 common/web/tslib/build.sh create mode 100644 common/web/tslib/package.json create mode 100644 common/web/tslib/src/index.ts create mode 100644 common/web/tslib/tsconfig.json diff --git a/common/predictive-text/build-bundler.js b/common/predictive-text/build-bundler.js index 2b2bff8713..a666e008fd 100644 --- a/common/predictive-text/build-bundler.js +++ b/common/predictive-text/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/common/predictive-text/src/node/tsconfig.json b/common/predictive-text/src/node/tsconfig.json index 48d64d22b6..dacf350335 100644 --- a/common/predictive-text/src/node/tsconfig.json +++ b/common/predictive-text/src/node/tsconfig.json @@ -5,6 +5,7 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", diff --git a/common/predictive-text/src/tsconfig.json b/common/predictive-text/src/tsconfig.json index 40fb9f45cd..f6fe671043 100644 --- a/common/predictive-text/src/tsconfig.json +++ b/common/predictive-text/src/tsconfig.json @@ -6,6 +6,7 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", diff --git a/common/predictive-text/src/web/tsconfig.json b/common/predictive-text/src/web/tsconfig.json index 9e1f43978e..bcb94fca10 100644 --- a/common/predictive-text/src/web/tsconfig.json +++ b/common/predictive-text/src/web/tsconfig.json @@ -5,6 +5,7 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", diff --git a/common/predictive-text/tsconfig.all.json b/common/predictive-text/tsconfig.all.json index 33fe749e21..5cb3fdf96b 100644 --- a/common/predictive-text/tsconfig.all.json +++ b/common/predictive-text/tsconfig.all.json @@ -10,6 +10,7 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", diff --git a/common/web/input-processor/build-bundler.js b/common/web/input-processor/build-bundler.js index 34614fa680..f16b207454 100644 --- a/common/web/input-processor/build-bundler.js +++ b/common/web/input-processor/build-bundler.js @@ -11,6 +11,9 @@ import { spawn } from 'child_process'; // Bundled ES module version esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, external: ['fs', 'vm'], @@ -29,6 +32,9 @@ esbuild.buildSync({ // Bundled CommonJS (classic Node) module version esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, external: ['fs', 'vm'], diff --git a/common/web/input-processor/tsconfig.json b/common/web/input-processor/tsconfig.json index 1a03881a16..6e04340f4d 100644 --- a/common/web/input-processor/tsconfig.json +++ b/common/web/input-processor/tsconfig.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/keyboard-processor/build-bundler.js b/common/web/keyboard-processor/build-bundler.js index 5c3ecb05a9..87625dd19c 100644 --- a/common/web/keyboard-processor/build-bundler.js +++ b/common/web/keyboard-processor/build-bundler.js @@ -10,6 +10,9 @@ import { spawn } from 'child_process'; /** @type {esbuild.BuildOptions} */ const commonConfig = { + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", @@ -23,6 +26,9 @@ const commonConfig = { // Bundled ES module version esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/index.js'], outfile: "build/lib/index.mjs", format: "esm", @@ -31,6 +37,9 @@ esbuild.buildSync({ // Bundled CommonJS (classic Node) module version esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/index.js'], outfile: 'build/lib/index.cjs', bundle: true, @@ -42,6 +51,9 @@ esbuild.buildSync({ esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/keyboards/loaders/dom-keyboard-loader.js'], outfile: 'build/lib/dom-keyboard-loader.mjs', format: "esm", @@ -50,6 +62,9 @@ esbuild.buildSync({ // The node-based keyboard loader needs an extra parameter due to Node-built-in imports: esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/keyboards/loaders/node-keyboard-loader.js'], outfile: 'build/lib/node-keyboard-loader.mjs', format: "esm", diff --git a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json index 5120d36a6c..e5ab52729e 100644 --- a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json +++ b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/keyboard-processor/tsconfig.common.json b/common/web/keyboard-processor/tsconfig.common.json index 9ed8c35d54..30fcd19eda 100644 --- a/common/web/keyboard-processor/tsconfig.common.json +++ b/common/web/keyboard-processor/tsconfig.common.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/keyboard-processor/tsconfig.json b/common/web/keyboard-processor/tsconfig.json index df6ac8969b..95071c36a7 100644 --- a/common/web/keyboard-processor/tsconfig.json +++ b/common/web/keyboard-processor/tsconfig.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/tslib/README.md b/common/web/tslib/README.md new file mode 100644 index 0000000000..4c87474f62 --- /dev/null +++ b/common/web/tslib/README.md @@ -0,0 +1,15 @@ +The default import setup for the `tslib` package is unfortunately incompatible with `esbuild` when in ES5 mode. But... +with a little elbow grease, we can fix that with _this_ package by importing its ES5-compatible file and exporting it +as _this_ package's default export for use in anything looking to `"importHelpers"`. + +To utilize this with `esbuild` while enabling the `"importHelpers"` compilation option in your tsconfig.json, you'll want +to set the following in your `esbuild` config: + +```javascript + alias: { + 'tslib': '@keymanapp/tslib' + }, +``` + +Note that esbuild 0.15.16 is the minimum required version to utilize the 'alias' feature necessary to replace `tslib` for +`tsc`-generated `import { /* */ } from 'tslib'` statements that result from enabling `"importHelpers"` in a tsconfig. \ No newline at end of file diff --git a/common/web/tslib/build.sh b/common/web/tslib/build.sh new file mode 100755 index 0000000000..d674ce9fb0 --- /dev/null +++ b/common/web/tslib/build.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# +# Compiles common TS-based utility functions for use among Keyman's codebase + +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +cd "$THIS_SCRIPT_PATH" + +################################ Main script ################################ + +builder_describe \ + "A ES5 + esbuild compatibility wrapper for the 'tslib' package." \ + clean configure build + +builder_describe_outputs \ + configure "/node_modules" \ + build "/common/web/tslib/build/index.js" + +builder_parse "$@" + +builder_run_action configure verify_npm_setup +builder_run_action clean rm -rf build/ +builder_run_action build tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" \ No newline at end of file diff --git a/common/web/tslib/package.json b/common/web/tslib/package.json new file mode 100644 index 0000000000..7de002c017 --- /dev/null +++ b/common/web/tslib/package.json @@ -0,0 +1,25 @@ +{ + "name": "@keymanapp/tslib", + "description": "An ES5 + esbuild-compatible wrapper for the 'tslib' library", + "main": "./build/index.js", + "scripts": { + "build": "gosh ./build.sh", + "clean": "tsc -b --clean", + "tsc": "tsc" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + }, + "author": "SIL International", + "license": "MIT", + "bugs": { + "url": "https://github.com/keymanapp/keyman/issues" + }, + "homepage": "https://github.com/keymanapp/keyman#readme", + "dependencies": { + "tslib": "^2.5.2", + "typescript": "^4.9.5" + }, + "type": "module" +} diff --git a/common/web/tslib/src/index.ts b/common/web/tslib/src/index.ts new file mode 100644 index 0000000000..2f173ad83f --- /dev/null +++ b/common/web/tslib/src/index.ts @@ -0,0 +1,3 @@ +export * from '../../../../node_modules/tslib/tslib.js'; + +export * as tslib from '../../../../node_modules/tslib/tslib.js'; \ No newline at end of file diff --git a/common/web/tslib/tsconfig.json b/common/web/tslib/tsconfig.json new file mode 100644 index 0000000000..459f336baa --- /dev/null +++ b/common/web/tslib/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig-base.json", + "compilerOptions": { + "allowJs": true, + "module": "es6", + "moduleResolution": "Node", + "inlineSources": true, + "sourceMap": true, + "declaration": true, + "target": "es5", + "tsBuildInfoFile": "./build/tsconfig.tsbuildinfo", + "types": ["node"], + "lib": ["es6"], + "baseUrl": "./src", + "outDir": "./build/", + "rootDir": "./src" + }, + "include": [ + "./src/index.ts" + ] +} diff --git a/common/web/utils/build-bundler.js b/common/web/utils/build-bundler.js index 72df8e0ae1..cda215474f 100644 --- a/common/web/utils/build-bundler.js +++ b/common/web/utils/build-bundler.js @@ -8,6 +8,9 @@ import { spawn } from 'child_process'; // Bundles to a compact ESModule esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, //minify: true, // No need to minify a module. @@ -24,6 +27,9 @@ esbuild.buildSync({ // Bundles to a compact CommonJS (classic Node) module esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, //minify: true, // No need to minify a module. diff --git a/common/web/utils/tsconfig.json b/common/web/utils/tsconfig.json index 838a704dfb..e19eadb2d3 100644 --- a/common/web/utils/tsconfig.json +++ b/common/web/utils/tsconfig.json @@ -4,6 +4,7 @@ "allowJs": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "declaration": true, diff --git a/package-lock.json b/package-lock.json index 8eeac938a3..749aeee411 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11031,7 +11031,8 @@ "@keymanapp/recorder-core": "*", "@keymanapp/web-utils": "*", "@types/node": "^11.9.4", - "eventemitter3": "^4.0.0" + "eventemitter3": "^4.0.0", + "tslib": "^2.5.2" }, "devDependencies": { "@keymanapp/resources-gosh": "*", @@ -11055,7 +11056,6 @@ "mocha": "^10.0.0", "modernizr": "^3.11.7", "ts-node": "^10.9.1", - "tslib": "^2.5.2", "typescript": "^4.9.5" } } diff --git a/web/package.json b/web/package.json index 9c06e3d5b0..8e145576e0 100644 --- a/web/package.json +++ b/web/package.json @@ -108,7 +108,8 @@ "@keymanapp/recorder-core": "*", "@keymanapp/web-utils": "*", "@types/node": "^11.9.4", - "eventemitter3": "^4.0.0" + "eventemitter3": "^4.0.0", + "tslib": "^2.5.2" }, "type": "module" } diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index ba49bccc5a..38ff1d875d 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -50,7 +50,10 @@ let es5ClassAnnotationAsPurePlugin = { } } -await esbuild.build({ +const commonConfig = { + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "iife", @@ -60,14 +63,16 @@ await esbuild.build({ }, outfile: '../../../build/app/browser/debug/keymanweb.js', plugins: [ es5ClassAnnotationAsPurePlugin ], + pure: ['__asyncDelegator'], target: "es5", treeShaking: true, tsconfig: './tsconfig.json' -}); +}; + +await esbuild.build(commonConfig); let result = await esbuild.build({ - bundle: true, - sourcemap: true, + ...commonConfig, minifyWhitespace: true, minifySyntax: true, minifyIdentifiers: true, @@ -77,10 +82,6 @@ let result = await esbuild.build({ 'index': '../../../build/app/browser/obj/release-main.js', }, outfile: '../../../build/app/browser/release/keymanweb.js', - plugins: [ es5ClassAnnotationAsPurePlugin ], - target: "es5", - treeShaking: true, - tsconfig: './tsconfig.json', // Enables source-file output size profiling! metafile: true }); @@ -96,17 +97,14 @@ if(EMIT_FILESIZE_PROFILE) { } await esbuild.build({ - bundle: true, - sourcemap: true, - minify: false, + ...commonConfig, + alias: { + 'tslib': '@keymanapp/tslib' + }, format: "esm", - nodePaths: ['../../../../node_modules'], entryPoints: { 'index': '../../../build/app/browser/obj/test-index.js', }, outfile: '../../../build/app/browser/lib/index.mjs', - plugins: [ es5ClassAnnotationAsPurePlugin ], - target: "es5", - treeShaking: true, tsconfig: './tsconfig.json' }); \ No newline at end of file diff --git a/web/src/app/browser/tsconfig.json b/web/src/app/browser/tsconfig.json index 2d4a1a481c..4d2cce1a12 100644 --- a/web/src/app/browser/tsconfig.json +++ b/web/src/app/browser/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": false, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/app/webview/build-bundler.js b/web/src/app/webview/build-bundler.js index 0f166cd5d8..be77ad2f91 100644 --- a/web/src/app/webview/build-bundler.js +++ b/web/src/app/webview/build-bundler.js @@ -31,6 +31,9 @@ let es5ClassAnnotationAsPurePlugin = { } await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "iife", @@ -46,6 +49,9 @@ await esbuild.build({ }); await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, minifyWhitespace: true, diff --git a/web/src/app/webview/tsconfig.json b/web/src/app/webview/tsconfig.json index 2a35140eb7..8dbfa3c46c 100644 --- a/web/src/app/webview/tsconfig.json +++ b/web/src/app/webview/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": false, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/attachment/build-bundler.js b/web/src/engine/attachment/build-bundler.js index ecce53bb0d..ab199c8f50 100644 --- a/web/src/engine/attachment/build-bundler.js +++ b/web/src/engine/attachment/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/attachment/src/pageContextAttachment.ts b/web/src/engine/attachment/src/pageContextAttachment.ts index ce115a9ddc..7fc8b33c80 100644 --- a/web/src/engine/attachment/src/pageContextAttachment.ts +++ b/web/src/engine/attachment/src/pageContextAttachment.ts @@ -103,7 +103,7 @@ export class PageContextAttachment extends EventEmitter { (flattenedInputList, pageInputList) => flattenedInputList.concat(pageInputList), [] ); - return [...this._inputList, ...embeddedInputs]; + return [].concat(this._inputList).concat(embeddedInputs); } // Useful for `moveToNext` operations: order matters. diff --git a/web/src/engine/attachment/tsconfig.json b/web/src/engine/attachment/tsconfig.json index fc3f2cba9e..3bb8e7b846 100644 --- a/web/src/engine/attachment/tsconfig.json +++ b/web/src/engine/attachment/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/device-detect/build-bundler.js b/web/src/engine/device-detect/build-bundler.js index 272419cc57..d0326e975b 100644 --- a/web/src/engine/device-detect/build-bundler.js +++ b/web/src/engine/device-detect/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/device-detect/tsconfig.json b/web/src/engine/device-detect/tsconfig.json index 7c4ab9a952..de87fd1cdc 100644 --- a/web/src/engine/device-detect/tsconfig.json +++ b/web/src/engine/device-detect/tsconfig.json @@ -4,6 +4,7 @@ "compilerOptions": { "allowJs": false, "declaration": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/dom-utils/tsconfig.json b/web/src/engine/dom-utils/tsconfig.json index e73aa1f76f..fb4942d7ca 100644 --- a/web/src/engine/dom-utils/tsconfig.json +++ b/web/src/engine/dom-utils/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/element-wrappers/build-bundler.js b/web/src/engine/element-wrappers/build-bundler.js index 899fe228b2..6263557c99 100644 --- a/web/src/engine/element-wrappers/build-bundler.js +++ b/web/src/engine/element-wrappers/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/element-wrappers/tsconfig.json b/web/src/engine/element-wrappers/tsconfig.json index 3dea240aed..d7712d72c3 100644 --- a/web/src/engine/element-wrappers/tsconfig.json +++ b/web/src/engine/element-wrappers/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/events/build-bundler.js b/web/src/engine/events/build-bundler.js index 6c6f45c869..d2b56e237f 100644 --- a/web/src/engine/events/build-bundler.js +++ b/web/src/engine/events/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/events/tsconfig.json b/web/src/engine/events/tsconfig.json index 83a3238008..b7afb88032 100644 --- a/web/src/engine/events/tsconfig.json +++ b/web/src/engine/events/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/main/build-bundler.js b/web/src/engine/main/build-bundler.js index 8731bb66a3..b96243c75a 100644 --- a/web/src/engine/main/build-bundler.js +++ b/web/src/engine/main/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/main/tsconfig.json b/web/src/engine/main/tsconfig.json index 29446647d7..9eb7f53c35 100644 --- a/web/src/engine/main/tsconfig.json +++ b/web/src/engine/main/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/osk/build-bundler.js b/web/src/engine/osk/build-bundler.js index ae92eb04cf..b1858b3f95 100644 --- a/web/src/engine/osk/build-bundler.js +++ b/web/src/engine/osk/build-bundler.js @@ -1,6 +1,9 @@ import esbuild from 'esbuild'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/osk/tsconfig.json b/web/src/engine/osk/tsconfig.json index fe6a12bdce..0edc3b5c34 100644 --- a/web/src/engine/osk/tsconfig.json +++ b/web/src/engine/osk/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/package-cache/build-bundler.js b/web/src/engine/package-cache/build-bundler.js index 1069209c9f..f2340a1b01 100644 --- a/web/src/engine/package-cache/build-bundler.js +++ b/web/src/engine/package-cache/build-bundler.js @@ -1,6 +1,9 @@ import esbuild from 'esbuild'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", @@ -15,6 +18,9 @@ await esbuild.build({ }); await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", @@ -29,6 +35,9 @@ await esbuild.build({ }); await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/package-cache/src/keyboardRequisitioner.ts b/web/src/engine/package-cache/src/keyboardRequisitioner.ts index 043b3a0362..6a3f060510 100644 --- a/web/src/engine/package-cache/src/keyboardRequisitioner.ts +++ b/web/src/engine/package-cache/src/keyboardRequisitioner.ts @@ -214,7 +214,7 @@ export default class KeyboardRequisitioner { } } - return [...errorStubs, ...completeStubs]; + return [].concat(errorStubs).concat(completeStubs); }); } diff --git a/web/src/engine/package-cache/tsconfig.json b/web/src/engine/package-cache/tsconfig.json index 84dc705d64..8d36452a68 100644 --- a/web/src/engine/package-cache/tsconfig.json +++ b/web/src/engine/package-cache/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", diff --git a/web/src/engine/paths/build-bundler.js b/web/src/engine/paths/build-bundler.js index dfb7bd9a36..3ab7b2c85e 100644 --- a/web/src/engine/paths/build-bundler.js +++ b/web/src/engine/paths/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/web/src/engine/paths/tsconfig.json b/web/src/engine/paths/tsconfig.json index 272527be3e..7c84cc975c 100644 --- a/web/src/engine/paths/tsconfig.json +++ b/web/src/engine/paths/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": false, + "importHelpers": true, "inlineSources": true, "allowSyntheticDefaultImports": true, "module": "es6", -- GitLab From 754da7aa99ce871daa63d7c69881f4b2cfb51645 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 31 May 2023 16:27:01 +0700 Subject: [PATCH 371/386] chore(web): cleanup, array-spread -> concat conversion --- package-lock.json | 1 + web/package.json | 1 + web/src/app/browser/build-bundler.js | 3 --- web/src/app/browser/src/keymanEngine.ts | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 749aeee411..b0ccb18f59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11029,6 +11029,7 @@ "@keymanapp/lexical-model-layer": "*", "@keymanapp/models-types": "*", "@keymanapp/recorder-core": "*", + "@keymanapp/tslib": "*", "@keymanapp/web-utils": "*", "@types/node": "^11.9.4", "eventemitter3": "^4.0.0", diff --git a/web/package.json b/web/package.json index 8e145576e0..21b17525a6 100644 --- a/web/package.json +++ b/web/package.json @@ -106,6 +106,7 @@ "@keymanapp/lexical-model-layer": "*", "@keymanapp/models-types": "*", "@keymanapp/recorder-core": "*", + "@keymanapp/tslib": "*", "@keymanapp/web-utils": "*", "@types/node": "^11.9.4", "eventemitter3": "^4.0.0", diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 38ff1d875d..a510fac277 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -98,9 +98,6 @@ if(EMIT_FILESIZE_PROFILE) { await esbuild.build({ ...commonConfig, - alias: { - 'tslib': '@keymanapp/tslib' - }, format: "esm", entryPoints: { 'index': '../../../build/app/browser/obj/test-index.js', diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index dd994f6a75..4f43ef3e44 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -295,9 +295,9 @@ export default class KeymanEngine extends KeymanEngineBase Date: Thu, 1 Jun 2023 09:25:21 +0700 Subject: [PATCH 372/386] chore(web): build dependency links --- common/predictive-text/build.sh | 1 + common/web/keyboard-processor/build.sh | 1 + common/web/utils/build.sh | 1 + web/src/engine/package-cache/build.sh | 1 + web/src/engine/paths/build.sh | 1 + 5 files changed, 5 insertions(+) diff --git a/common/predictive-text/build.sh b/common/predictive-text/build.sh index 56680781f8..4fe5a744cb 100755 --- a/common/predictive-text/build.sh +++ b/common/predictive-text/build.sh @@ -26,6 +26,7 @@ cd "$(dirname "$THIS_SCRIPT")" builder_describe "Builds the lm-layer module" \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ "@/common/web/lm-worker" \ "clean" \ "configure" \ diff --git a/common/web/keyboard-processor/build.sh b/common/web/keyboard-processor/build.sh index fc7bf1f1a6..01939b9e62 100755 --- a/common/web/keyboard-processor/build.sh +++ b/common/web/keyboard-processor/build.sh @@ -21,6 +21,7 @@ builder_describe \ "Compiles the web-oriented utility function module." \ "@/common/web/recorder test" \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ "@/common/web/utils" \ configure \ clean \ diff --git a/common/web/utils/build.sh b/common/web/utils/build.sh index bd55e007d6..d4ff9ef8f3 100755 --- a/common/web/utils/build.sh +++ b/common/web/utils/build.sh @@ -19,6 +19,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe \ "Compiles the web-oriented utility function module." \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ clean configure build test \ "--ci For use with action ${BUILDER_TERM_START}test${BUILDER_TERM_END} - emits CI-friendly test reports" diff --git a/web/src/engine/package-cache/build.sh b/web/src/engine/package-cache/build.sh index 2f54efd2f8..0048fc4bc3 100755 --- a/web/src/engine/package-cache/build.sh +++ b/web/src/engine/package-cache/build.sh @@ -16,6 +16,7 @@ cd "$THIS_SCRIPT_PATH" # ################################ Main script ################################ builder_describe "Builds Keyman Engine modules for keyboard cloud-querying & caching + model caching." \ + "@/common/web/tslib" \ "@/common/web/input-processor build" \ "@/web/src/engine/paths" \ "clean" \ diff --git a/web/src/engine/paths/build.sh b/web/src/engine/paths/build.sh index 9904958767..58064a0333 100755 --- a/web/src/engine/paths/build.sh +++ b/web/src/engine/paths/build.sh @@ -16,6 +16,7 @@ cd "$THIS_SCRIPT_PATH" # ################################ Main script ################################ builder_describe "Builds configuration subclasses used by the Keyman Engine for Web (KMW)." \ + "@/common/web/tslib" \ "@/web/src/engine/osk build" \ "clean" \ "configure" \ -- GitLab From 77fc750186e844b3979af8533facd165a63a504d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 13:06:07 +0700 Subject: [PATCH 373/386] chore(web): cleanup per PR review --- common/web/tslib/build.sh | 4 ---- common/web/tslib/package.json | 10 ---------- web/src/app/browser/build-bundler.js | 1 - 3 files changed, 15 deletions(-) diff --git a/common/web/tslib/build.sh b/common/web/tslib/build.sh index d674ce9fb0..92435e5704 100755 --- a/common/web/tslib/build.sh +++ b/common/web/tslib/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# -# Compiles common TS-based utility functions for use among Keyman's codebase - -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary diff --git a/common/web/tslib/package.json b/common/web/tslib/package.json index 7de002c017..164a5257ec 100644 --- a/common/web/tslib/package.json +++ b/common/web/tslib/package.json @@ -7,16 +7,6 @@ "clean": "tsc -b --clean", "tsc": "tsc" }, - "repository": { - "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" - }, - "author": "SIL International", - "license": "MIT", - "bugs": { - "url": "https://github.com/keymanapp/keyman/issues" - }, - "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { "tslib": "^2.5.2", "typescript": "^4.9.5" diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index a510fac277..93b1b01c06 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -63,7 +63,6 @@ const commonConfig = { }, outfile: '../../../build/app/browser/debug/keymanweb.js', plugins: [ es5ClassAnnotationAsPurePlugin ], - pure: ['__asyncDelegator'], target: "es5", treeShaking: true, tsconfig: './tsconfig.json' -- GitLab From 39f30e4385febc8a64aa6e0ff60e201875c1d8e1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 6 Jun 2023 10:15:16 +0700 Subject: [PATCH 374/386] fix(web): restores element text scrolling --- .../app/browser/src/context/focusAssistant.ts | 22 ++--- .../src/context/pageIntegrationHandlers.ts | 6 +- web/src/app/browser/src/contextManager.ts | 12 +-- web/src/engine/element-wrappers/src/input.ts | 79 ++++++++--------- .../element-wrappers/src/outputTarget.ts | 10 +++ .../engine/element-wrappers/src/textarea.ts | 86 +++++++------------ 6 files changed, 101 insertions(+), 114 deletions(-) diff --git a/web/src/app/browser/src/context/focusAssistant.ts b/web/src/app/browser/src/context/focusAssistant.ts index bda60dee5d..c0ae8a6bdb 100644 --- a/web/src/app/browser/src/context/focusAssistant.ts +++ b/web/src/app/browser/src/context/focusAssistant.ts @@ -41,6 +41,18 @@ interface EventMap { export class FocusAssistant extends EventEmitter { private _maintainingFocus: boolean = false; // ActivatingKeymanWebUI - Does the OSK have active focus / an active interaction? + /** + * Returns `true` only when the active target has an active `forceScroll` method/state, which deliberately + * blurs and then refocuses the same element in order to force a browser-default page scroll to keep the + * element and text-caret visible. + */ + readonly isTargetForcingScroll: () => boolean; + + constructor(isTargetForcingScroll: () => boolean) { + super(); + this.isTargetForcingScroll = isTargetForcingScroll; + } + /* * Long-term idea here: about all of the relevant OSK events that would interact with this have "enter" and * "leave" variants - we could take a stack of `Promise`s. On a `Promise` fulfillment, remove it from the @@ -109,16 +121,6 @@ export class FocusAssistant extends EventEmitter { */ _IgnoreNextSelChange = 0; - /** - * JH (2023-04-24): Set only by the OutputTarget `forceScroll` method, which deliberately blurs and - * then refocuses the same element in order to force a browser-default page scroll to keep the element - * visible. - * - * While it feels like this should be possible to merge with the other class fields in some form... it - * doesn't seem as safe to do on first glance. - */ - _IgnoreBlurFocus: boolean = false; - /** * Is used as a time-delayed async `restoringFocus` or `maintainingFocus` - could be modeled decently as a Promise. * Probably more the latter, as it's a touch-OSK interaction like the other `maintainingFocus` cases. diff --git a/web/src/app/browser/src/context/pageIntegrationHandlers.ts b/web/src/app/browser/src/context/pageIntegrationHandlers.ts index a2cd64f156..30b91cb102 100644 --- a/web/src/app/browser/src/context/pageIntegrationHandlers.ts +++ b/web/src/app/browser/src/context/pageIntegrationHandlers.ts @@ -67,8 +67,10 @@ export class PageIntegrationHandlers { } private suppressFocusCheck: (e: FocusEvent) => boolean = (e) => { - if(this.focusAssistant._IgnoreBlurFocus) { - // Prevent triggering other blur-handling events (as possible) + if(this.focusAssistant.isTargetForcingScroll()) { + // Prevent triggering other blur-handling events (as possible) - this blur + // is programmatic in order to force a browser scroll-position update. + // All focus changes should be prevented at this time. e.stopPropagation(); e.cancelBubble = true; } diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index e0f3cd76a2..42e7edd729 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -43,7 +43,7 @@ function _SetTargDir(Ptarg: HTMLElement, activeKeyboard: Keyboard) { export default class ContextManager extends ContextManagerBase { private _activeKeyboard: {keyboard: Keyboard, metadata: KeyboardStub}; private cookieManager = new CookieSerializer('KeymanWeb_Keyboard'); - readonly focusAssistant = new FocusAssistant(); + readonly focusAssistant = new FocusAssistant(() => this.activeTarget?.isForcingScroll()); readonly page: PageContextAttachment; private mostRecentTarget: OutputTarget; private currentTarget: OutputTarget; @@ -231,6 +231,11 @@ export default class ContextManager extends ContextManagerBase void, - /** * This event will be raised when a newline is received by wrapped elements not of * the 'search' or 'submit' types. @@ -74,23 +41,19 @@ export default class Input extends OutputTarget { */ private processedSelectionEnd: number; + /** + * Set, then unset within the `forceScroll` method in order to facilitate the + * `isForcingScroll` flag. + */ + private _activeForcedScroll: boolean; + constructor(ele: HTMLInputElement) { super(); this.root = ele; this._cachedSelectionStart = -1; - - // Intended to facilitate reimplmentation of the old `forceScroll` as an event handler - // defined externally, but automatically set on class construction. - Input.constructorExtensions(this); } - /** - * This may be set to define additional construction behaviors to perform, such as - * automatically setting handlers for defined events. - */ - public static constructorExtensions: (constructingInstance: Input) => void = () => {}; - get isSynthetic(): boolean { return false; } @@ -146,11 +109,39 @@ export default class Input extends OutputTarget { this.processedSelectionStart = start; this.processedSelectionEnd = end; - this.events.emit('scrollfocusrequest', this.root); + this.forceScroll(); this.root.setSelectionRange(domStart, domEnd, direction); } + forceScroll() { + // Only executes when com.keyman.DOMEventHandlers is defined. + // + // We bypass this whenever operating in the embedded format. + const element = this.getElement(); + + let selectionStart = element.selectionStart; + let selectionEnd = element.selectionEnd; + + this._activeForcedScroll = true; + + try { + //Forces scrolling; the re-focus triggers the scroll, at least. + element.blur(); + element.focus(); + } finally { + // On Edge, it appears that the blur/focus combination will reset the caret position + // under certain scenarios during unit tests. So, we re-set it afterward. + element.selectionStart = selectionStart; + element.selectionEnd = selectionEnd; + this._activeForcedScroll = false; + } + } + + isForcingScroll(): boolean { + return this._activeForcedScroll; + } + getSelectionDirection(): "forward" | "backward" | "none" { return this.root.selectionDirection; } diff --git a/web/src/engine/element-wrappers/src/outputTarget.ts b/web/src/engine/element-wrappers/src/outputTarget.ts index e7640b36d5..fcc35da1b7 100644 --- a/web/src/engine/element-wrappers/src/outputTarget.ts +++ b/web/src/engine/element-wrappers/src/outputTarget.ts @@ -23,6 +23,16 @@ export default abstract class OutputTarget void, -} - -export default class TextArea extends OutputTarget { +export default class TextArea extends OutputTarget<{}> { root: HTMLTextAreaElement; /** @@ -56,31 +21,18 @@ export default class TextArea extends OutputTarget { private processedSelectionEnd: number; /** - * Used to temporarily store the y-axis scroll coordinate. + * Set, then unset within the `forceScroll` method in order to facilitate the + * `isForcingScroll` flag. */ - private scrollTop?: number; - - /** - * Used to temporarily store the x-axis scroll coordinate. - */ - private scrollLeft?: number; + private _activeForcedScroll: boolean; constructor(ele: HTMLTextAreaElement) { super(); this.root = ele; this._cachedSelectionStart = -1; - // Intended to facilitate reimplmentation of the old `forceScroll` as an event handler - // defined externally, but automatically set on class construction. - TextArea.constructorExtensions(this); } - /** - * This may be set to define additional construction behaviors to perform, such as - * automatically setting handlers for defined events. - */ - public static constructorExtensions: (constructingInstance: TextArea) => void = () => {}; - get isSynthetic(): boolean { return false; } @@ -136,11 +88,39 @@ export default class TextArea extends OutputTarget { this.processedSelectionStart = start; this.processedSelectionEnd = end; - this.events.emit('scrollfocusrequest', this.root); + this.forceScroll(); this.root.setSelectionRange(domStart, domEnd, direction); } + forceScroll() { + // Only executes when com.keyman.DOMEventHandlers is defined. + // + // We bypass this whenever operating in the embedded format. + const element = this.getElement(); + + let selectionStart = element.selectionStart; + let selectionEnd = element.selectionEnd; + + this._activeForcedScroll = true; + + try { + //Forces scrolling; the re-focus triggers the scroll, at least. + element.blur(); + element.focus(); + } finally { + // On Edge, it appears that the blur/focus combination will reset the caret position + // under certain scenarios during unit tests. So, we re-set it afterward. + element.selectionStart = selectionStart; + element.selectionEnd = selectionEnd; + this._activeForcedScroll = false; + } + } + + isForcingScroll(): boolean { + return this._activeForcedScroll; + } + getSelectionDirection(): "forward" | "backward" | "none" { return this.root.selectionDirection; } -- GitLab From 19325e42345f8bdcc96bbc1320d7b545cdbaa2eb Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 25 May 2023 13:47:19 +0700 Subject: [PATCH 375/386] chore(web): minor esbuild version bump to get 'alias' option, adds tslib --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index b0ccb18f59..aea4d14f43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11057,6 +11057,7 @@ "mocha": "^10.0.0", "modernizr": "^3.11.7", "ts-node": "^10.9.1", + "tslib": "^2.5.2", "typescript": "^4.9.5" } } -- GitLab From b66c173fc2cde52b30650d81e2f20c8778088294 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Jun 2023 12:22:28 +0700 Subject: [PATCH 376/386] feat(web): prototype manual treeshake for tslib (in web/browser build-bundler) --- web/src/app/browser/build-bundler.js | 122 ++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 93b1b01c06..bc0ea61695 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -29,6 +29,104 @@ if(process.argv.length > 2) { } } +// Component #1: Detect all `tslib` helpers we actually want to use. +const tslibHelperNames = [ + "__extends", + "__assign", + "__rest", + "__decorate", + "__param", + "__metadata", + "__awaiter", + "__generator", + "__exportStar", + "__createBinding", + "__values", + "__read", + "__spread", + "__spreadArrays", + "__await", + "__asyncGenerator", + "__asyncDelegator", + "__asyncValues", + "__makeTemplateObject", + "__importStar", + "__importDefault", + "__classPrivateFieldGet", + "__classPrivateFieldSet" +]; + +const detectedHelpers = []; + +let tslibHelperDetectionPlugin = { + name: 'tslib helper use detection', + setup(build) { + build.onLoad({filter: /\.js$/}, async (args) => { + // + if(/tslib.js$/.test(args.path)) { + return; + } + + let source = await fs.promises.readFile(args.path, 'utf8'); + + for(let helper of tslibHelperNames) { + if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { + detectedHelpers.push(helper); + } + } + + return; + }); + } +} +// + +// Component #2: when we've actually determined which ones are safe to remove, this plugin +// can remove their code. +let tslibForcedTreeshakingPlugin = { + name: 'tslib helpers - forced treeshaking', + setup(build) { + build.onLoad({filter: /tslib.js$/}, async (args) => { + let source = await fs.promises.readFile(args.path, 'utf8'); + + // TODO: transformations to eliminate the stuff we don't want. + for(let unusedHelper of unusedHelpers) { + // Removes the 'exporter' line used to actually export it from the tslib source. + source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); + + // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. + let definitionStart = source.indexOf(`${unusedHelper} = function`); + if(definitionStart == -1) { + console.error("tslib has likely been updated recently; could not erase definition for helper " + unusedHelper); + continue; + } + let scopeDepth = 0; + let i = definitionStart; + let char = source.charAt(i); + while(char != '}' || --scopeDepth != 0) { + if(char == '{') { + scopeDepth++; + } + i++; + char = source.charAt(i); + } + i++; // we want to erase it, too. + + source = source.replace(source.substring(definitionStart, i), ''); + + // The top-level var declaration is auto-removed by esbuild when no references to it remain. + + } + + return { + contents: source, + loader: 'js' + }; + }); + } +} +// + /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - * the `@class` emit comment-annotation is designed to facilitate tree-shaking for ES5-targeted @@ -62,12 +160,34 @@ const commonConfig = { 'index': '../../../build/app/browser/obj/debug-main.js', }, outfile: '../../../build/app/browser/debug/keymanweb.js', - plugins: [ es5ClassAnnotationAsPurePlugin ], + plugins: [ tslibForcedTreeshakingPlugin, es5ClassAnnotationAsPurePlugin ], target: "es5", treeShaking: true, tsconfig: './tsconfig.json' }; +// tslib tree-shake phase 1 - detecting which helpers are safe to remove. +await esbuild.build({ + ...commonConfig, + plugins: [ tslibHelperDetectionPlugin ], + write: false +}); + +// Logs on the tree-shaking decisions +console.log("Detected helpers from tslib: "); +console.log(detectedHelpers.sort()); + +console.log(); +console.log("Unused helpers: "); +const unusedHelpers = []; +tslibHelperNames.forEach((entry) => { + if(!detectedHelpers.find((detected) => detected == entry)) { + unusedHelpers.push(entry); + } +}); +console.log(unusedHelpers); + +// From here, the builds are configured to do phase 2 from the preprocessing data done 'til now. await esbuild.build(commonConfig); let result = await esbuild.build({ -- GitLab From 8deddfbab2b092b7693d4bbf50325afbc52e1a1d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 7 Jun 2023 16:11:58 +0700 Subject: [PATCH 377/386] refactor(web): refactored unused helper detection aspect --- web/src/app/browser/build-bundler.js | 222 +++++++++++++++------------ 1 file changed, 120 insertions(+), 102 deletions(-) diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index bc0ea61695..06024965b4 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -29,103 +29,87 @@ if(process.argv.length > 2) { } } -// Component #1: Detect all `tslib` helpers we actually want to use. -const tslibHelperNames = [ - "__extends", - "__assign", - "__rest", - "__decorate", - "__param", - "__metadata", - "__awaiter", - "__generator", - "__exportStar", - "__createBinding", - "__values", - "__read", - "__spread", - "__spreadArrays", - "__await", - "__asyncGenerator", - "__asyncDelegator", - "__asyncValues", - "__makeTemplateObject", - "__importStar", - "__importDefault", - "__classPrivateFieldGet", - "__classPrivateFieldSet" -]; - -const detectedHelpers = []; - -let tslibHelperDetectionPlugin = { - name: 'tslib helper use detection', - setup(build) { - build.onLoad({filter: /\.js$/}, async (args) => { - // - if(/tslib.js$/.test(args.path)) { - return; - } +async function determineNeededDowncompileHelpers(config, log) { + // Component #1: Detect all `tslib` helpers we actually want to use. + const tslibHelperNames = [ + "__extends", + "__assign", + "__rest", + "__decorate", + "__param", + "__metadata", + "__awaiter", + "__generator", + "__exportStar", + "__createBinding", + "__values", + "__read", + "__spread", + "__spreadArrays", + "__await", + "__asyncGenerator", + "__asyncDelegator", + "__asyncValues", + "__makeTemplateObject", + "__importStar", + "__importDefault", + "__classPrivateFieldGet", + "__classPrivateFieldSet" + ]; + + const detectedHelpers = []; + + let tslibHelperDetectionPlugin = { + name: 'tslib helper use detection', + setup(build) { + build.onLoad({filter: /\.js$/}, async (args) => { + // + if(/tslib.js$/.test(args.path)) { + return; + } - let source = await fs.promises.readFile(args.path, 'utf8'); + let source = await fs.promises.readFile(args.path, 'utf8'); - for(let helper of tslibHelperNames) { - if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { - detectedHelpers.push(helper); + for(let helper of tslibHelperNames) { + if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { + detectedHelpers.push(helper); + } } - } - return; - }); + return; + }); + } } -} -// -// Component #2: when we've actually determined which ones are safe to remove, this plugin -// can remove their code. -let tslibForcedTreeshakingPlugin = { - name: 'tslib helpers - forced treeshaking', - setup(build) { - build.onLoad({filter: /tslib.js$/}, async (args) => { - let source = await fs.promises.readFile(args.path, 'utf8'); + // tslib tree-shake phase 1 - detecting which helpers are safe to remove. + await esbuild.build({ + ...config, + plugins: [ tslibHelperDetectionPlugin, ...config.plugins ], + write: false + }); - // TODO: transformations to eliminate the stuff we don't want. - for(let unusedHelper of unusedHelpers) { - // Removes the 'exporter' line used to actually export it from the tslib source. - source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); + // At this point, we can determine what's unused. + detectedHelpers.sort(); - // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. - let definitionStart = source.indexOf(`${unusedHelper} = function`); - if(definitionStart == -1) { - console.error("tslib has likely been updated recently; could not erase definition for helper " + unusedHelper); - continue; - } - let scopeDepth = 0; - let i = definitionStart; - let char = source.charAt(i); - while(char != '}' || --scopeDepth != 0) { - if(char == '{') { - scopeDepth++; - } - i++; - char = source.charAt(i); - } - i++; // we want to erase it, too. - - source = source.replace(source.substring(definitionStart, i), ''); - - // The top-level var declaration is auto-removed by esbuild when no references to it remain. + const unusedHelpers = []; + tslibHelperNames.forEach((entry) => { + if(!detectedHelpers.find((detected) => detected == entry)) { + unusedHelpers.push(entry); + } + }); - } + if(log) { + // Logs on the tree-shaking decisions + console.log("Detected helpers from tslib: "); + console.log(); - return { - contents: source, - loader: 'js' - }; - }); + console.log(); + console.log("Unused helpers: "); + console.log(unusedHelpers); } + + return unusedHelpers; } -// /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - @@ -160,32 +144,66 @@ const commonConfig = { 'index': '../../../build/app/browser/obj/debug-main.js', }, outfile: '../../../build/app/browser/debug/keymanweb.js', - plugins: [ tslibForcedTreeshakingPlugin, es5ClassAnnotationAsPurePlugin ], + plugins: [ es5ClassAnnotationAsPurePlugin ], target: "es5", treeShaking: true, tsconfig: './tsconfig.json' }; // tslib tree-shake phase 1 - detecting which helpers are safe to remove. -await esbuild.build({ - ...commonConfig, - plugins: [ tslibHelperDetectionPlugin ], - write: false -}); +const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig); -// Logs on the tree-shaking decisions -console.log("Detected helpers from tslib: "); -console.log(detectedHelpers.sort()); +// Component #2: when we've actually determined which ones are safe to remove, this plugin +// can remove their code. +let tslibForcedTreeshakingPlugin = { + name: 'tslib helpers - forced treeshaking', + setup(build) { + build.onLoad({filter: /tslib.js$/}, async (args) => { + let source = await fs.promises.readFile(args.path, 'utf8'); -console.log(); -console.log("Unused helpers: "); -const unusedHelpers = []; -tslibHelperNames.forEach((entry) => { - if(!detectedHelpers.find((detected) => detected == entry)) { - unusedHelpers.push(entry); + // TODO: transformations to eliminate the stuff we don't want. + for(let unusedHelper of unusedHelpers) { + // Removes the 'exporter' line used to actually export it from the tslib source. + source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); + + // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. + let definitionStart = source.indexOf(`${unusedHelper} = function`); + if(definitionStart == -1) { + if(unusedHelper == '__createBinding') { + console.warn("Currently unable to force-treeshake the __createBinding tslib helper"); + } else { + console.error("tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper); + } + continue; + } + + let scopeDepth = 0; + let i = definitionStart; + let char = source.charAt(i); + while(char != '}' || --scopeDepth != 0) { + if(char == '{') { + scopeDepth++; + } + i++; + char = source.charAt(i); + } + i++; // we want to erase it, too. + + source = source.replace(source.substring(definitionStart, i), ''); + + // The top-level var declaration is auto-removed by esbuild when no references to it remain. + + } + + return { + contents: source, + loader: 'js' + }; + }); } -}); -console.log(unusedHelpers); +} + +commonConfig.plugins = [tslibForcedTreeshakingPlugin, ...commonConfig.plugins]; // From here, the builds are configured to do phase 2 from the preprocessing data done 'til now. await esbuild.build(commonConfig); -- GitLab From 06803300280853a113d0ab68ec59208a0d22039e Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 08:59:51 +0700 Subject: [PATCH 378/386] chore(web): prep to formalize the tslib treeshaker, integration with esbuild logs --- web/src/app/browser/build-bundler.js | 166 ++++++++++++++++++--------- 1 file changed, 110 insertions(+), 56 deletions(-) diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 06024965b4..98ac54e2c5 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -6,7 +6,6 @@ */ import esbuild from 'esbuild'; -import { spawn } from 'child_process'; import fs from 'fs'; let EMIT_FILESIZE_PROFILE = false; @@ -29,8 +28,8 @@ if(process.argv.length > 2) { } } +// Component #1: Detect all `tslib` helpers we actually want to use. async function determineNeededDowncompileHelpers(config, log) { - // Component #1: Detect all `tslib` helpers we actually want to use. const tslibHelperNames = [ "__extends", "__assign", @@ -65,6 +64,8 @@ async function determineNeededDowncompileHelpers(config, log) { build.onLoad({filter: /\.js$/}, async (args) => { // if(/tslib.js$/.test(args.path)) { + // Returning `undefined` makes this 'pass-through' - it doesn't prevent other + // configured plugins from working. return; } @@ -76,6 +77,8 @@ async function determineNeededDowncompileHelpers(config, log) { } } + // Returning `undefined` makes this 'pass-through' - it doesn't prevent other + // configured plugins from working. return; }); } @@ -84,6 +87,9 @@ async function determineNeededDowncompileHelpers(config, log) { // tslib tree-shake phase 1 - detecting which helpers are safe to remove. await esbuild.build({ ...config, + // `tslibHelperDetectionPlugin` is pass-through, and so has no net effect on + // the build. We just use this run to scan for any utilized `tslib` helper funcs, + // not to manipulate the actual source in any way. plugins: [ tslibHelperDetectionPlugin, ...config.plugins ], write: false }); @@ -111,6 +117,105 @@ async function determineNeededDowncompileHelpers(config, log) { return unusedHelpers; } +// Component #2: when we've actually determined which ones are safe to remove, this plugin +// can remove their code. +function configuredDowncompileTreeshakePlugin(unusedHelpers) { + function indexToSourcePosition(source, index) { + let priorText = source.substring(0, index); + let lineNum = priorText.split('\n').length; + let lastLineBreakIndex = priorText.lastIndexOf('\n'); + let colNum = index - lastLineBreakIndex; + let nextLineBreakIndex = source.indexOf('\n', lastLineBreakIndex+1); + + return { + line: lineNum, + column: colNum, + lineText: source.substring(lastLineBreakIndex+1, nextLineBreakIndex) + } + } + + return { + name: 'tslib forced treeshaking', + setup(build) { + build.onLoad({filter: /tslib.js$/}, async (args) => { + const trueSource = await fs.promises.readFile(args.path, 'utf8'); + let source = trueSource; + + let warnings = []; + let errors = []; + + for(let unusedHelper of unusedHelpers) { + // Removes the 'exporter' line used to actually export it from the tslib source. + source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); + + // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. + let definitionStart = source.indexOf(`${unusedHelper} = function`); + + // Emission of warnings & errors + if(definitionStart == -1) { + let matchString = `${unusedHelper} =` + let bestGuessIndex = trueSource.indexOf(matchString); + if(bestGuessIndex == -1) { + matchString = `var ${unusedHelper}` + bestGuessIndex = trueSource.indexOf(matchString); + } + + if(bestGuessIndex == -1) { + matchString = ''; + } + + let logLocation = indexToSourcePosition(trueSource, bestGuessIndex); + + let location = { + file: args.path, + line: logLocation.line, + column: logLocation.column, + length: matchString.length, + lineText: logLocation.lineText + } + + if(unusedHelper == '__createBinding') { + warnings.push({ + text: "Currently unable to force-treeshake the __createBinding tslib helper", + location: location + }); + } else { + warnings.push({ + text: "tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper, + location: location + }); + } + continue; + } + + let scopeDepth = 0; + let i = definitionStart; + let char = source.charAt(i); + while(char != '}' || --scopeDepth != 0) { + if(char == '{') { + scopeDepth++; + } + i++; + char = source.charAt(i); + } + i++; // we want to erase it, too. + + source = source.replace(source.substring(definitionStart, i), ''); + + // The top-level var declaration is auto-removed by esbuild when no references to it remain. + } + + return { + contents: source, + loader: 'js', + warnings: warnings, + errors: errors + }; + }); + } + }; +}; + /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - * the `@class` emit comment-annotation is designed to facilitate tree-shaking for ES5-targeted @@ -150,62 +255,11 @@ const commonConfig = { tsconfig: './tsconfig.json' }; -// tslib tree-shake phase 1 - detecting which helpers are safe to remove. +// Prepare the needed setup for `tslib` treeshaking. const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig); +commonConfig.plugins = [configuredDowncompileTreeshakePlugin(unusedHelpers), ...commonConfig.plugins]; -// Component #2: when we've actually determined which ones are safe to remove, this plugin -// can remove their code. -let tslibForcedTreeshakingPlugin = { - name: 'tslib helpers - forced treeshaking', - setup(build) { - build.onLoad({filter: /tslib.js$/}, async (args) => { - let source = await fs.promises.readFile(args.path, 'utf8'); - - // TODO: transformations to eliminate the stuff we don't want. - for(let unusedHelper of unusedHelpers) { - // Removes the 'exporter' line used to actually export it from the tslib source. - source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); - - // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. - let definitionStart = source.indexOf(`${unusedHelper} = function`); - if(definitionStart == -1) { - if(unusedHelper == '__createBinding') { - console.warn("Currently unable to force-treeshake the __createBinding tslib helper"); - } else { - console.error("tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper); - } - continue; - } - - let scopeDepth = 0; - let i = definitionStart; - let char = source.charAt(i); - while(char != '}' || --scopeDepth != 0) { - if(char == '{') { - scopeDepth++; - } - i++; - char = source.charAt(i); - } - i++; // we want to erase it, too. - - source = source.replace(source.substring(definitionStart, i), ''); - - // The top-level var declaration is auto-removed by esbuild when no references to it remain. - - } - - return { - contents: source, - loader: 'js' - }; - }); - } -} - -commonConfig.plugins = [tslibForcedTreeshakingPlugin, ...commonConfig.plugins]; - -// From here, the builds are configured to do phase 2 from the preprocessing data done 'til now. +// And now... do the actual builds. await esbuild.build(commonConfig); let result = await esbuild.build({ -- GitLab From a37d3236ac769dd143253ac2ea4dba083827fbd7 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 09:47:13 +0700 Subject: [PATCH 379/386] refactor(web): extracts treeshaker into tslib helper-project --- common/web/tslib/package.json | 13 ++ common/web/tslib/src/esbuild-tools.ts | 219 ++++++++++++++++++++++++++ common/web/tslib/tsconfig.json | 5 +- package-lock.json | 3 + web/src/app/browser/build-bundler.js | 191 +--------------------- 5 files changed, 240 insertions(+), 191 deletions(-) create mode 100644 common/web/tslib/src/esbuild-tools.ts diff --git a/common/web/tslib/package.json b/common/web/tslib/package.json index 164a5257ec..84eed17970 100644 --- a/common/web/tslib/package.json +++ b/common/web/tslib/package.json @@ -2,6 +2,16 @@ "name": "@keymanapp/tslib", "description": "An ES5 + esbuild-compatible wrapper for the 'tslib' library", "main": "./build/index.js", + "exports": { + ".": { + "types": "./build/index.d.ts", + "import": "./build/index.js" + }, + "./esbuild-tools": { + "types": "./build/esbuild-tools.d.ts", + "import": "./build/esbuild-tools.js" + } + }, "scripts": { "build": "gosh ./build.sh", "clean": "tsc -b --clean", @@ -11,5 +21,8 @@ "tslib": "^2.5.2", "typescript": "^4.9.5" }, + "devDependencies": { + "esbuild": "^0.15.16" + }, "type": "module" } diff --git a/common/web/tslib/src/esbuild-tools.ts b/common/web/tslib/src/esbuild-tools.ts new file mode 100644 index 0000000000..81856d09e3 --- /dev/null +++ b/common/web/tslib/src/esbuild-tools.ts @@ -0,0 +1,219 @@ +import esbuild from 'esbuild'; +import fs from 'fs'; + +// Note: this package is intended 100% as a dev-tool, hence why esbuild is just a dev-dependency. + +// Component #1: Detect all `tslib` helpers we actually want to use. +export async function determineNeededDowncompileHelpers(config: esbuild.BuildOptions, log: boolean) { + const tslibHelperNames = [ + "__extends", + "__assign", + "__rest", + "__decorate", + "__param", + "__metadata", + "__awaiter", + "__generator", + "__exportStar", + "__createBinding", + "__values", + "__read", + "__spread", + "__spreadArrays", + "__await", + "__asyncGenerator", + "__asyncDelegator", + "__asyncValues", + "__makeTemplateObject", + "__importStar", + "__importDefault", + "__classPrivateFieldGet", + "__classPrivateFieldSet" + ]; + + const detectedHelpers: string[] = []; + + let tslibHelperDetectionPlugin = { + name: 'tslib helper use detection', + setup(build) { + build.onLoad({filter: /\.js$/}, async (args) => { + // + if(/tslib.js$/.test(args.path)) { + // Returning `undefined` makes this 'pass-through' - it doesn't prevent other + // configured plugins from working. + return; + } + + let source = await fs.promises.readFile(args.path, 'utf8'); + + for(let helper of tslibHelperNames) { + if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { + detectedHelpers.push(helper); + } + } + + // Returning `undefined` makes this 'pass-through' - it doesn't prevent other + // configured plugins from working. + return; + }); + } + } + + // tslib tree-shake phase 1 - detecting which helpers are safe to remove. + await esbuild.build({ + ...config, + // `tslibHelperDetectionPlugin` is pass-through, and so has no net effect on + // the build. We just use this run to scan for any utilized `tslib` helper funcs, + // not to manipulate the actual source in any way. + plugins: [ tslibHelperDetectionPlugin, ...(config.plugins ?? []) ], + write: false + }); + + // At this point, we can determine what's unused. + detectedHelpers.sort(); + + const unusedHelpers: string[] = []; + tslibHelperNames.forEach((entry) => { + if(!detectedHelpers.find((detected) => detected == entry)) { + unusedHelpers.push(entry); + } + }); + + if(log) { + // Logs on the tree-shaking decisions + console.log("Detected helpers from tslib: "); + console.log(); + + console.log(); + console.log("Unused helpers: "); + console.log(unusedHelpers); + } + + return unusedHelpers; +} + +function indexToSourcePosition(source: string, index: number) { + let priorText = source.substring(0, index); + let lineNum = priorText.split('\n').length; + let lastLineBreakIndex = priorText.lastIndexOf('\n'); + let colNum = index - lastLineBreakIndex; + let nextLineBreakIndex = source.indexOf('\n', lastLineBreakIndex+1); + + return { + line: lineNum, + column: colNum, + lineText: source.substring(lastLineBreakIndex+1, nextLineBreakIndex) + } +} + +// Ugh. https://github.com/evanw/esbuild/issues/2656#issuecomment-1304013941 +function wrapClassPlugin(instancePlugin: esbuild.Plugin) { + return { + name: instancePlugin.name, + setup: instancePlugin.setup.bind(instancePlugin) + }; +} + +// Component #2: when we've actually determined which ones are safe to remove, this plugin +// can remove their code. +class TslibTreeshaker implements esbuild.Plugin { + public readonly name = 'tslib forced treeshaking'; + + private unusedHelpers: string[]; + + constructor(unusedHelpers: string[]) { + this.unusedHelpers = unusedHelpers; + } + + setup(build: esbuild.PluginBuild) { + build.onLoad({filter: /tslib.js$/}, async (args) => { + const trueSource = await fs.promises.readFile(args.path, 'utf8'); + let source = trueSource; + + let warnings: esbuild.Message[] = []; + let errors: esbuild.Message[] = []; + + for(let unusedHelper of this.unusedHelpers) { + // Removes the 'exporter' line used to actually export it from the tslib source. + source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); + + // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. + let definitionStart = source.indexOf(`${unusedHelper} = function`); + + // Emission of warnings & errors + if(definitionStart == -1) { + let matchString = `${unusedHelper} =` + let bestGuessIndex = trueSource.indexOf(matchString); + if(bestGuessIndex == -1) { + matchString = `var ${unusedHelper}` + bestGuessIndex = trueSource.indexOf(matchString); + } + + if(bestGuessIndex == -1) { + matchString = ''; + } + + let logLocation = indexToSourcePosition(trueSource, bestGuessIndex); + + let location: esbuild.Location = { + file: args.path, + line: logLocation.line, + column: logLocation.column, + length: matchString.length, + lineText: logLocation.lineText, + namespace: '', + suggestion: '' + } + + if(unusedHelper == '__createBinding') { + warnings.push({ + id: '', + notes: [], + detail: '', + pluginName: this.name, + text: "Currently unable to force-treeshake the __createBinding tslib helper", + location: location + }); + } else { + warnings.push({ + id: '', + notes: [], + detail: '', + pluginName: this.name, + text: "tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper, + location: location + }); + } + continue; + } + + let scopeDepth = 0; + let i = definitionStart; + let char = source.charAt(i); + while(char != '}' || --scopeDepth != 0) { + if(char == '{') { + scopeDepth++; + } + i++; + char = source.charAt(i); + } + i++; // we want to erase it, too. + + source = source.replace(source.substring(definitionStart, i), ''); + + // The top-level var declaration is auto-removed by esbuild when no references to it remain. + } + + return { + contents: source, + loader: 'js', + warnings: warnings, + errors: errors + }; + }); + } +}; + +export function buildTslibTreeshaker(unusedHelpers: string[]) { + return wrapClassPlugin(new TslibTreeshaker(unusedHelpers)); +}; \ No newline at end of file diff --git a/common/web/tslib/tsconfig.json b/common/web/tslib/tsconfig.json index 459f336baa..c141af3d3c 100644 --- a/common/web/tslib/tsconfig.json +++ b/common/web/tslib/tsconfig.json @@ -4,18 +4,19 @@ "allowJs": true, "module": "es6", "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, "inlineSources": true, "sourceMap": true, "declaration": true, "target": "es5", "tsBuildInfoFile": "./build/tsconfig.tsbuildinfo", "types": ["node"], - "lib": ["es6"], + "lib": ["es6", "DOM"], // The latter is b/c esbuild expects the type. "baseUrl": "./src", "outDir": "./build/", "rootDir": "./src" }, "include": [ - "./src/index.ts" + "./src/*.ts" ] } diff --git a/package-lock.json b/package-lock.json index aea4d14f43..5ffd15651b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -355,6 +355,9 @@ "dependencies": { "tslib": "^2.5.2", "typescript": "^4.9.5" + }, + "devDependencies": { + "esbuild": "^0.15.16" } }, "common/web/types": { diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 98ac54e2c5..743f80c451 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -7,6 +7,7 @@ import esbuild from 'esbuild'; import fs from 'fs'; +import { determineNeededDowncompileHelpers, buildTslibTreeshaker } from '@keymanapp/tslib/esbuild-tools'; let EMIT_FILESIZE_PROFILE = false; @@ -28,194 +29,6 @@ if(process.argv.length > 2) { } } -// Component #1: Detect all `tslib` helpers we actually want to use. -async function determineNeededDowncompileHelpers(config, log) { - const tslibHelperNames = [ - "__extends", - "__assign", - "__rest", - "__decorate", - "__param", - "__metadata", - "__awaiter", - "__generator", - "__exportStar", - "__createBinding", - "__values", - "__read", - "__spread", - "__spreadArrays", - "__await", - "__asyncGenerator", - "__asyncDelegator", - "__asyncValues", - "__makeTemplateObject", - "__importStar", - "__importDefault", - "__classPrivateFieldGet", - "__classPrivateFieldSet" - ]; - - const detectedHelpers = []; - - let tslibHelperDetectionPlugin = { - name: 'tslib helper use detection', - setup(build) { - build.onLoad({filter: /\.js$/}, async (args) => { - // - if(/tslib.js$/.test(args.path)) { - // Returning `undefined` makes this 'pass-through' - it doesn't prevent other - // configured plugins from working. - return; - } - - let source = await fs.promises.readFile(args.path, 'utf8'); - - for(let helper of tslibHelperNames) { - if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { - detectedHelpers.push(helper); - } - } - - // Returning `undefined` makes this 'pass-through' - it doesn't prevent other - // configured plugins from working. - return; - }); - } - } - - // tslib tree-shake phase 1 - detecting which helpers are safe to remove. - await esbuild.build({ - ...config, - // `tslibHelperDetectionPlugin` is pass-through, and so has no net effect on - // the build. We just use this run to scan for any utilized `tslib` helper funcs, - // not to manipulate the actual source in any way. - plugins: [ tslibHelperDetectionPlugin, ...config.plugins ], - write: false - }); - - // At this point, we can determine what's unused. - detectedHelpers.sort(); - - const unusedHelpers = []; - tslibHelperNames.forEach((entry) => { - if(!detectedHelpers.find((detected) => detected == entry)) { - unusedHelpers.push(entry); - } - }); - - if(log) { - // Logs on the tree-shaking decisions - console.log("Detected helpers from tslib: "); - console.log(); - - console.log(); - console.log("Unused helpers: "); - console.log(unusedHelpers); - } - - return unusedHelpers; -} - -// Component #2: when we've actually determined which ones are safe to remove, this plugin -// can remove their code. -function configuredDowncompileTreeshakePlugin(unusedHelpers) { - function indexToSourcePosition(source, index) { - let priorText = source.substring(0, index); - let lineNum = priorText.split('\n').length; - let lastLineBreakIndex = priorText.lastIndexOf('\n'); - let colNum = index - lastLineBreakIndex; - let nextLineBreakIndex = source.indexOf('\n', lastLineBreakIndex+1); - - return { - line: lineNum, - column: colNum, - lineText: source.substring(lastLineBreakIndex+1, nextLineBreakIndex) - } - } - - return { - name: 'tslib forced treeshaking', - setup(build) { - build.onLoad({filter: /tslib.js$/}, async (args) => { - const trueSource = await fs.promises.readFile(args.path, 'utf8'); - let source = trueSource; - - let warnings = []; - let errors = []; - - for(let unusedHelper of unusedHelpers) { - // Removes the 'exporter' line used to actually export it from the tslib source. - source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); - - // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. - let definitionStart = source.indexOf(`${unusedHelper} = function`); - - // Emission of warnings & errors - if(definitionStart == -1) { - let matchString = `${unusedHelper} =` - let bestGuessIndex = trueSource.indexOf(matchString); - if(bestGuessIndex == -1) { - matchString = `var ${unusedHelper}` - bestGuessIndex = trueSource.indexOf(matchString); - } - - if(bestGuessIndex == -1) { - matchString = ''; - } - - let logLocation = indexToSourcePosition(trueSource, bestGuessIndex); - - let location = { - file: args.path, - line: logLocation.line, - column: logLocation.column, - length: matchString.length, - lineText: logLocation.lineText - } - - if(unusedHelper == '__createBinding') { - warnings.push({ - text: "Currently unable to force-treeshake the __createBinding tslib helper", - location: location - }); - } else { - warnings.push({ - text: "tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper, - location: location - }); - } - continue; - } - - let scopeDepth = 0; - let i = definitionStart; - let char = source.charAt(i); - while(char != '}' || --scopeDepth != 0) { - if(char == '{') { - scopeDepth++; - } - i++; - char = source.charAt(i); - } - i++; // we want to erase it, too. - - source = source.replace(source.substring(definitionStart, i), ''); - - // The top-level var declaration is auto-removed by esbuild when no references to it remain. - } - - return { - contents: source, - loader: 'js', - warnings: warnings, - errors: errors - }; - }); - } - }; -}; - /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - * the `@class` emit comment-annotation is designed to facilitate tree-shaking for ES5-targeted @@ -257,7 +70,7 @@ const commonConfig = { // Prepare the needed setup for `tslib` treeshaking. const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig); -commonConfig.plugins = [configuredDowncompileTreeshakePlugin(unusedHelpers), ...commonConfig.plugins]; +commonConfig.plugins = [buildTslibTreeshaker(unusedHelpers), ...commonConfig.plugins]; // And now... do the actual builds. await esbuild.build(commonConfig); -- GitLab From 9a6a2f88bf20779a9d3ac99828e3ab53f28d0b78 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 11:50:04 +0700 Subject: [PATCH 380/386] feat(web): extends capabilities of the tslib treeshaker components, finalizes coverage --- common/web/tslib/src/esbuild-tools.ts | 225 +++++++++++++++++++------- web/src/app/browser/build-bundler.js | 2 +- 2 files changed, 166 insertions(+), 61 deletions(-) diff --git a/common/web/tslib/src/esbuild-tools.ts b/common/web/tslib/src/esbuild-tools.ts index 81856d09e3..7eccc64d65 100644 --- a/common/web/tslib/src/esbuild-tools.ts +++ b/common/web/tslib/src/esbuild-tools.ts @@ -4,7 +4,7 @@ import fs from 'fs'; // Note: this package is intended 100% as a dev-tool, hence why esbuild is just a dev-dependency. // Component #1: Detect all `tslib` helpers we actually want to use. -export async function determineNeededDowncompileHelpers(config: esbuild.BuildOptions, log: boolean) { +export async function determineNeededDowncompileHelpers(config: esbuild.BuildOptions, ignoreFilePattern?: RegExp, log?: boolean) { const tslibHelperNames = [ "__extends", "__assign", @@ -19,6 +19,7 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt "__values", "__read", "__spread", + "__spreadArray", "__spreadArrays", "__await", "__asyncGenerator", @@ -26,9 +27,15 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt "__asyncValues", "__makeTemplateObject", "__importStar", + "__setModuleDefault", // only used by the previous entry! "__importDefault", "__classPrivateFieldGet", - "__classPrivateFieldSet" + "__classPrivateFieldSet", + "__classPrivateFieldIn", + "__runInitializers", + "__setFunctionName", + "__propKey", + "__esDecorate" ]; const detectedHelpers: string[] = []; @@ -37,18 +44,47 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt name: 'tslib helper use detection', setup(build) { build.onLoad({filter: /\.js$/}, async (args) => { - // - if(/tslib.js$/.test(args.path)) { - // Returning `undefined` makes this 'pass-through' - it doesn't prevent other + let source = await fs.promises.readFile(args.path, 'utf8'); + + let warnings: esbuild.Message[] = []; + + if(/tslib.js$/.test(args.path) || ignoreFilePattern?.test(args.path)) { + let declarationRegex = /var (__[a-zA-Z0-9]+)/g; + let results = source.match(declarationRegex); + + for(let result of results) { + let capture = result.substring(4); + + if(!tslibHelperNames.find((entry) => entry == capture)) { + // TODO: integrate as esbuild log message. + console.log("Missing? " + capture); + + // Match: `result` itself. Grab index, build location, build message. + let index = source.indexOf(result); + + warnings.push(buildMessage( + 'tslib helper use detection', + 'Probable `tslib` helper `' + capture + '` has not been validated for tree-shaking potential', + indexToSourceLocation(source, index, result.length, args.path) + )); + } + } + + // Returning `undefined` for `content` makes this 'pass-through' - it doesn't prevent other // configured plugins from working. - return; + return { + warnings: warnings + }; } - let source = await fs.promises.readFile(args.path, 'utf8'); for(let helper of tslibHelperNames) { if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { detectedHelpers.push(helper); + + if(helper == '__importStar') { + detectedHelpers.push('__setModuleDefault'); + } } } @@ -92,7 +128,7 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt return unusedHelpers; } -function indexToSourcePosition(source: string, index: number) { +function indexToSourceLocation(source: string, index: number, matchLength: number, file: string): esbuild.Location { let priorText = source.substring(0, index); let lineNum = priorText.split('\n').length; let lastLineBreakIndex = priorText.lastIndexOf('\n'); @@ -100,9 +136,24 @@ function indexToSourcePosition(source: string, index: number) { let nextLineBreakIndex = source.indexOf('\n', lastLineBreakIndex+1); return { + file: file, line: lineNum, column: colNum, - lineText: source.substring(lastLineBreakIndex+1, nextLineBreakIndex) + lineText: source.substring(lastLineBreakIndex+1, nextLineBreakIndex), + namespace: '', + suggestion: '', + length: matchLength + } +} + +function buildMessage(pluginName: string, message: string, location: esbuild.Location): esbuild.Message { + return { + id: '', + notes: [], + detail: '', + pluginName: pluginName, + text: message, + location: location } } @@ -125,6 +176,67 @@ class TslibTreeshaker implements esbuild.Plugin { this.unusedHelpers = unusedHelpers; } + treeshakeDefinition(source: string, start: number, expectTernary: boolean, name: string, location: esbuild.Location) { + let scopeDepth = 0; + let parenDepth = 0; + let topLevelTernaryActive = 0; + let i = start; + let char = source.charAt(i); + while(char != '}' || --scopeDepth != 0 || topLevelTernaryActive) { + if(char == '{') { + scopeDepth++; + } else if(char == '(') { + parenDepth++; + } else if(char == ')') { + parenDepth--; + } else if(char == '?' && scopeDepth == 0) { + if(!expectTernary) { + return { + source: source, + error: buildMessage(this.name, `Unexpected conditional for definition of ${name}`, location) + } + } + topLevelTernaryActive++; + } else if(char == ':' && topLevelTernaryActive && scopeDepth == 0) { + topLevelTernaryActive--; + } + i++; + + if(i > source.length) { + return { + source: source, + error: buildMessage(this.name, `Failed to determine end of definition for ${name}`, location) + }; + } + + char = source.charAt(i); + } + i++; // we want to erase the final '}', too. + + // The functions may be wrapped with parens. + if(parenDepth == 1) { + let nextOpen = source.indexOf('(', i); + let nextClosed = source.indexOf(')', i); + + if(nextOpen < nextClosed) { + return { + source: source, + error: buildMessage(this.name, `Failed to determine end of definition for ${name}`, location) + }; + } else { + i = nextClosed + 1; + } + } + + if(source.charAt(i) == ';') { + i++; + } + + return { + source: source.replace(source.substring(start, i), '') + }; + } + setup(build: esbuild.PluginBuild) { build.onLoad({filter: /tslib.js$/}, async (args) => { const trueSource = await fs.promises.readFile(args.path, 'utf8'); @@ -138,70 +250,63 @@ class TslibTreeshaker implements esbuild.Plugin { source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. - let definitionStart = source.indexOf(`${unusedHelper} = function`); + let matchString = `${unusedHelper} = function`; + let expectTernary = false; + + // A special case - it has two different versions depending on if Object.create exists or not. + // This is established via a ternary conditional. Just adds a bit to the parsing. + if(unusedHelper == '__createBinding') { + matchString = `${unusedHelper} = `; + expectTernary = true; + } else if(unusedHelper == '__setModuleDefault') { + matchString = `var ${unusedHelper} = `; // is inlined, not declared at top! + expectTernary = true; + } + let definitionStart = source.indexOf(matchString); + + if(definitionStart > -1) { + const result = this.treeshakeDefinition( + source, + definitionStart, + expectTernary, + unusedHelper, + indexToSourceLocation(trueSource, trueSource.indexOf(matchString), matchString.length, args.path) + ); + + if(result.error) { + errors.push(result.error); + } else { + source = result.source; + } + continue; + } - // Emission of warnings & errors + // If we reached this point, we couldn't treeshake the unused helper appropriately. if(definitionStart == -1) { - let matchString = `${unusedHelper} =` + // Matches the standard definition pattern's left-hand assignment component. + matchString = `${unusedHelper} =` let bestGuessIndex = trueSource.indexOf(matchString); + if(bestGuessIndex == -1) { + // Failing the above, we match the declaration higher up within the file. matchString = `var ${unusedHelper}` bestGuessIndex = trueSource.indexOf(matchString); } + // Failing THAT, we just give up and go start-of-file. if(bestGuessIndex == -1) { matchString = ''; + bestGuessIndex = 0; } - let logLocation = indexToSourcePosition(trueSource, bestGuessIndex); - - let location: esbuild.Location = { - file: args.path, - line: logLocation.line, - column: logLocation.column, - length: matchString.length, - lineText: logLocation.lineText, - namespace: '', - suggestion: '' - } - - if(unusedHelper == '__createBinding') { - warnings.push({ - id: '', - notes: [], - detail: '', - pluginName: this.name, - text: "Currently unable to force-treeshake the __createBinding tslib helper", - location: location - }); - } else { - warnings.push({ - id: '', - notes: [], - detail: '', - pluginName: this.name, - text: "tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper, - location: location - }); - } - continue; + warnings.push( + buildMessage( + this.name, + "tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper, + indexToSourceLocation(trueSource, bestGuessIndex, matchString.length, args.path) + ) + ); } - - let scopeDepth = 0; - let i = definitionStart; - let char = source.charAt(i); - while(char != '}' || --scopeDepth != 0) { - if(char == '{') { - scopeDepth++; - } - i++; - char = source.charAt(i); - } - i++; // we want to erase it, too. - - source = source.replace(source.substring(definitionStart, i), ''); - - // The top-level var declaration is auto-removed by esbuild when no references to it remain. } return { diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 743f80c451..6c21fed255 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -69,7 +69,7 @@ const commonConfig = { }; // Prepare the needed setup for `tslib` treeshaking. -const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig); +const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig, /worker-main\.wrapped\.(?:min\.).js?/); commonConfig.plugins = [buildTslibTreeshaker(unusedHelpers), ...commonConfig.plugins]; // And now... do the actual builds. -- GitLab From fed4618d027afca1916ca2dd081b7e89a5836808 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 14:39:55 +0700 Subject: [PATCH 381/386] feat(common/models): tslib use, treeshaking in lm-worker --- common/models/templates/build.sh | 5 +++-- common/models/templates/tsconfig.json | 1 + common/models/wordbreakers/src/default/index.ts | 8 +++----- common/models/wordbreakers/tsconfig.json | 1 + common/web/lm-worker/build-bundler.js | 11 ++++++++++- common/web/lm-worker/build.sh | 1 + common/web/lm-worker/tsconfig.json | 3 ++- 7 files changed, 21 insertions(+), 9 deletions(-) diff --git a/common/models/templates/build.sh b/common/models/templates/build.sh index b5ae2a8c23..0fe305ca5a 100755 --- a/common/models/templates/build.sh +++ b/common/models/templates/build.sh @@ -18,8 +18,9 @@ cd "$(dirname "$THIS_SCRIPT")" ################################ Main script ################################ builder_describe "Builds the predictive-text model template implementation module" \ - "@../../web/keyman-version" \ - "@../wordbreakers" \ + "@/common/web/keyman-version" \ + "@/common/web/tslib" \ + "@/common/models/wordbreakers" \ "clean" \ "configure" \ "build" \ diff --git a/common/models/templates/tsconfig.json b/common/models/templates/tsconfig.json index 9284bddd20..7ff603ef29 100644 --- a/common/models/templates/tsconfig.json +++ b/common/models/templates/tsconfig.json @@ -7,6 +7,7 @@ "moduleResolution": "node16", "sourceMap": true, "sourceRoot": "/common/models/templates/src", + "importHelpers": true, "inlineSources": true, "strict": true, "lib": ["es6"], diff --git a/common/models/wordbreakers/src/default/index.ts b/common/models/wordbreakers/src/default/index.ts index 65dbd0faf0..20e159ed19 100644 --- a/common/models/wordbreakers/src/default/index.ts +++ b/common/models/wordbreakers/src/default/index.ts @@ -431,7 +431,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } // Do not break across certain punctuation // WB6: (Don't break before apostrophes in contractions) - const SET_ALL_MIDLETTER = [WordBreakProperty.MidLetter, ...SET_MIDNUMLETQ]; + const SET_ALL_MIDLETTER = [WordBreakProperty.MidLetter].concat(SET_MIDNUMLETQ); if(state.match(null, SET_AHLETTER, SET_ALL_MIDLETTER, SET_AHLETTER)) { continue; } @@ -474,7 +474,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } // Do not break within sequences, such as 3.2, 3,456.789 // WB11 - const SET_ALL_MIDNUM = [WordBreakProperty.MidNum, ...SET_MIDNUMLETQ]; + const SET_ALL_MIDNUM = [WordBreakProperty.MidNum].concat(SET_MIDNUMLETQ); if(state.match([WordBreakProperty.Numeric], SET_ALL_MIDNUM, [WordBreakProperty.Numeric], null)) { continue; } @@ -488,9 +488,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } // Do not break from extenders (e.g., U+202F NARROW NO-BREAK SPACE) // WB13a - const SET_NUM_KAT_LET = [WordBreakProperty.Katakana, - WordBreakProperty.Numeric, - ...SET_AHLETTER]; + const SET_NUM_KAT_LET = [WordBreakProperty.Katakana, WordBreakProperty.Numeric].concat(SET_AHLETTER); if(state.match(null, SET_NUM_KAT_LET, [WordBreakProperty.ExtendNumLet], null)) { continue; } diff --git a/common/models/wordbreakers/tsconfig.json b/common/models/wordbreakers/tsconfig.json index 6c029da40a..549b7410d6 100644 --- a/common/models/wordbreakers/tsconfig.json +++ b/common/models/wordbreakers/tsconfig.json @@ -8,6 +8,7 @@ "moduleResolution": "Node16", "declaration": true, "sourceMap": true, + "importHelpers": true, "inlineSources": true, "sourceRoot": "/common/models/wordbreakers/src", "strict": true, diff --git a/common/web/lm-worker/build-bundler.js b/common/web/lm-worker/build-bundler.js index 605ff3cd40..eb7bb5ea41 100644 --- a/common/web/lm-worker/build-bundler.js +++ b/common/web/lm-worker/build-bundler.js @@ -6,8 +6,8 @@ */ import esbuild from 'esbuild'; -import { spawn } from 'child_process'; import fs from 'fs'; +import { determineNeededDowncompileHelpers, buildTslibTreeshaker } from '@keymanapp/tslib/esbuild-tools'; /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - @@ -92,7 +92,12 @@ await esbuild.build({ target: "es5" }); +// The one that's actually a component of our releases. + const embeddedWorkerBuildOptions = { + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "iife", @@ -106,6 +111,10 @@ const embeddedWorkerBuildOptions = { target: "es5" } +// Prepare the needed setup for `tslib` treeshaking. +const unusedHelpers = await determineNeededDowncompileHelpers(embeddedWorkerBuildOptions); +embeddedWorkerBuildOptions.plugins = [buildTslibTreeshaker(unusedHelpers), ...embeddedWorkerBuildOptions.plugins]; + // Direct-use version await esbuild.build(embeddedWorkerBuildOptions); diff --git a/common/web/lm-worker/build.sh b/common/web/lm-worker/build.sh index 75a72df12d..82fba9bb39 100755 --- a/common/web/lm-worker/build.sh +++ b/common/web/lm-worker/build.sh @@ -28,6 +28,7 @@ WORKER_OUTPUT_FILENAME=build/lib/worker-main.js builder_describe \ "Compiles the Language Modeling Layer for common use in predictive text and autocorrective applications." \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ "@/common/models/wordbreakers" \ "@/common/models/templates" \ "@/common/tools/sourcemap-path-remapper" \ diff --git a/common/web/lm-worker/tsconfig.json b/common/web/lm-worker/tsconfig.json index 551f1645a9..2fdda0135a 100644 --- a/common/web/lm-worker/tsconfig.json +++ b/common/web/lm-worker/tsconfig.json @@ -5,7 +5,8 @@ "allowJs": false, "declaration": true, "module": "es6", - "moduleResolution": "node", + "moduleResolution": "node16", + "importHelpers": true, "inlineSourceMap": true, "inlineSources": true, "sourceRoot": "/common/web/lm-worker/src", -- GitLab From 734ebee46cb7cd6b06775fe424ec4a46b79f1a5b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 14:54:48 +0700 Subject: [PATCH 382/386] chore(web): cleans up console msg --- common/web/tslib/src/esbuild-tools.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/web/tslib/src/esbuild-tools.ts b/common/web/tslib/src/esbuild-tools.ts index 7eccc64d65..7e9c0dd191 100644 --- a/common/web/tslib/src/esbuild-tools.ts +++ b/common/web/tslib/src/esbuild-tools.ts @@ -56,9 +56,6 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt let capture = result.substring(4); if(!tslibHelperNames.find((entry) => entry == capture)) { - // TODO: integrate as esbuild log message. - console.log("Missing? " + capture); - // Match: `result` itself. Grab index, build location, build message. let index = source.indexOf(result); -- GitLab From db4f0a3664f310d8a53ce4349fadad70eff5ae81 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 15:35:34 +0700 Subject: [PATCH 383/386] fix(web): fixed wrapped-worker helper-detection ignore --- common/web/tslib/src/esbuild-tools.ts | 5 ++++- web/src/app/browser/build-bundler.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/common/web/tslib/src/esbuild-tools.ts b/common/web/tslib/src/esbuild-tools.ts index 7e9c0dd191..1bbc4f2a63 100644 --- a/common/web/tslib/src/esbuild-tools.ts +++ b/common/web/tslib/src/esbuild-tools.ts @@ -48,7 +48,7 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt let warnings: esbuild.Message[] = []; - if(/tslib.js$/.test(args.path) || ignoreFilePattern?.test(args.path)) { + if(/tslib.js$/.test(args.path)) { let declarationRegex = /var (__[a-zA-Z0-9]+)/g; let results = source.match(declarationRegex); @@ -74,6 +74,9 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt }; } + if(ignoreFilePattern?.test(args.path)) { + return; + } for(let helper of tslibHelperNames) { if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 6c21fed255..cc81f7c402 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -69,7 +69,7 @@ const commonConfig = { }; // Prepare the needed setup for `tslib` treeshaking. -const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig, /worker-main\.wrapped\.(?:min\.).js?/); +const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig, /worker-main\.wrapped(?:\.min)?\.js/); commonConfig.plugins = [buildTslibTreeshaker(unusedHelpers), ...commonConfig.plugins]; // And now... do the actual builds. -- GitLab From d5b2e278a9cafac3b2452b4d26e30d281331547a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Jun 2023 15:51:08 +0700 Subject: [PATCH 384/386] fix(web): noticed an extra dep or two among the helpers --- common/web/tslib/src/esbuild-tools.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/web/tslib/src/esbuild-tools.ts b/common/web/tslib/src/esbuild-tools.ts index 1bbc4f2a63..9e25899253 100644 --- a/common/web/tslib/src/esbuild-tools.ts +++ b/common/web/tslib/src/esbuild-tools.ts @@ -14,7 +14,7 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt "__metadata", "__awaiter", "__generator", - "__exportStar", + "__exportStar", // uses __createBinding "__createBinding", "__values", "__read", @@ -84,6 +84,9 @@ export async function determineNeededDowncompileHelpers(config: esbuild.BuildOpt if(helper == '__importStar') { detectedHelpers.push('__setModuleDefault'); + detectedHelpers.push('__createBinding'); + } else if(helper == '__exportStar') { + detectedHelpers.push('__createBinding'); } } } -- GitLab From a5358d1f8da274d57e857fa37fd64ab7658486d1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 9 Jun 2023 08:11:20 +0700 Subject: [PATCH 385/386] chore(common/web): cleanup per PR review --- common/web/tslib/build.sh | 2 +- common/web/tslib/package.json | 6 +++--- package-lock.json | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/common/web/tslib/build.sh b/common/web/tslib/build.sh index 92435e5704..411e32d8bb 100755 --- a/common/web/tslib/build.sh +++ b/common/web/tslib/build.sh @@ -24,4 +24,4 @@ builder_parse "$@" builder_run_action configure verify_npm_setup builder_run_action clean rm -rf build/ -builder_run_action build tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" \ No newline at end of file +builder_run_action build tsc --build \ No newline at end of file diff --git a/common/web/tslib/package.json b/common/web/tslib/package.json index 84eed17970..ceec8c6409 100644 --- a/common/web/tslib/package.json +++ b/common/web/tslib/package.json @@ -13,15 +13,15 @@ } }, "scripts": { - "build": "gosh ./build.sh", - "clean": "tsc -b --clean", - "tsc": "tsc" + "build": "gosh ./build.sh build", + "clean": "gosh ./build.sh clean" }, "dependencies": { "tslib": "^2.5.2", "typescript": "^4.9.5" }, "devDependencies": { + "@keymanapp/resources-gosh": "*", "esbuild": "^0.15.16" }, "type": "module" diff --git a/package-lock.json b/package-lock.json index 5ffd15651b..960e9a3047 100644 --- a/package-lock.json +++ b/package-lock.json @@ -351,12 +351,12 @@ }, "common/web/tslib": { "name": "@keymanapp/tslib", - "license": "MIT", "dependencies": { "tslib": "^2.5.2", "typescript": "^4.9.5" }, "devDependencies": { + "@keymanapp/resources-gosh": "*", "esbuild": "^0.15.16" } }, @@ -11060,7 +11060,6 @@ "mocha": "^10.0.0", "modernizr": "^3.11.7", "ts-node": "^10.9.1", - "tslib": "^2.5.2", "typescript": "^4.9.5" } } -- GitLab From 9aea971ac53366fa617bcb1cd6f5fee4490b39ed Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 9 Jun 2023 08:11:20 +0700 Subject: [PATCH 386/386] chore(common/web): cleanup per PR review --- common/web/tslib/build.sh | 2 +- common/web/tslib/package.json | 9 ++++++--- package-lock.json | 5 ++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/common/web/tslib/build.sh b/common/web/tslib/build.sh index 92435e5704..411e32d8bb 100755 --- a/common/web/tslib/build.sh +++ b/common/web/tslib/build.sh @@ -24,4 +24,4 @@ builder_parse "$@" builder_run_action configure verify_npm_setup builder_run_action clean rm -rf build/ -builder_run_action build tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" \ No newline at end of file +builder_run_action build tsc --build \ No newline at end of file diff --git a/common/web/tslib/package.json b/common/web/tslib/package.json index 164a5257ec..ac0c0d79cb 100644 --- a/common/web/tslib/package.json +++ b/common/web/tslib/package.json @@ -3,13 +3,16 @@ "description": "An ES5 + esbuild-compatible wrapper for the 'tslib' library", "main": "./build/index.js", "scripts": { - "build": "gosh ./build.sh", - "clean": "tsc -b --clean", - "tsc": "tsc" + "build": "gosh ./build.sh build", + "clean": "gosh ./build.sh clean" }, "dependencies": { "tslib": "^2.5.2", "typescript": "^4.9.5" }, + "devDependencies": { + "@keymanapp/resources-gosh": "*", + "esbuild": "^0.15.16" + }, "type": "module" } diff --git a/package-lock.json b/package-lock.json index b0ccb18f59..960e9a3047 100644 --- a/package-lock.json +++ b/package-lock.json @@ -351,10 +351,13 @@ }, "common/web/tslib": { "name": "@keymanapp/tslib", - "license": "MIT", "dependencies": { "tslib": "^2.5.2", "typescript": "^4.9.5" + }, + "devDependencies": { + "@keymanapp/resources-gosh": "*", + "esbuild": "^0.15.16" } }, "common/web/types": { -- GitLab