diff --git a/.github/workflows/auto-merge-keyman-server-pr.yml b/.github/workflows/auto-merge-keyman-server-pr.yml
index 6b93e716fa..0c920c2650 100644
--- a/.github/workflows/auto-merge-keyman-server-pr.yml
+++ b/.github/workflows/auto-merge-keyman-server-pr.yml
@@ -17,12 +17,12 @@ jobs:
if: ${{ github.repository == 'keymanapp/keyman' && github.actor == 'keyman-server' && startsWith(github.event.pull_request.title, 'auto:') }}
steps:
- name: auto approve PR from keyman-server
- uses: hmarr/auto-approve-action@v3
+ uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: auto merge PR from keyman-server
uses: "pascalgn/automerge-action@7854d3bd607dccdaf0b2c134b699a812c8960213"
env:
GITHUB_TOKEN: "${{ secrets.AUTOINC_GITHUB_TOKEN }}"
- MERGE_LABELS: ""
+ MERGE_LABELS: "automerge"
MERGE_FORKS: false
diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml
index aaeb1c6306..a06d7ed3cd 100644
--- a/.github/workflows/deb-packaging.yml
+++ b/.github/workflows/deb-packaging.yml
@@ -127,7 +127,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- dist: [jammy, noble, oracular]
+ dist: [jammy, noble, plucky]
steps:
- name: Checkout
@@ -153,7 +153,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- dist: [plucky]
+ dist: [questing]
steps:
- name: Checkout
diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml
new file mode 100644
index 0000000000..236970f141
--- /dev/null
+++ b/.github/workflows/pr-build-status.yml
@@ -0,0 +1,208 @@
+# GENERATED FILE - DO NOT EDIT!
+#
+# Keyman is copyright (C) SIL Global. MIT License.
+#
+# Do not modify the script in .github/workflows/pr-build-status.yml directly;
+# instead work on the sources in resources/build/pr-build-status and use the
+# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from
+# them.
+#
+# build.sh will append the relevant portions of
+# resources/build/pr-build-status/pr-build-status.mjs to the content in
+# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github
+# workflow file.
+#
+name: Keyman Build Summary
+on:
+ status:
+ push:
+ branches-ignore:
+ - master
+ - beta
+ - stable-*
+ workflow_dispatch:
+ inputs:
+ commit:
+ description: 'Commit sha'
+ required: true
+ type: string
+jobs:
+ run_pr_build_status:
+ name: Summarize build status checks
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check PR build status
+ id: run_pr_build_status_script
+ uses: actions/github-script@v7
+ with:
+ script: |
+ // This code is copied out of resources/build/pr-build-status/pr-build-status.mjs
+ // where it is tested. It is copied inline here in order to avoid requiring the
+ // repository to be checked out, which dramatically reduces the run time of the
+ // check.
+ //
+ // Note: we don't currently look at check runs, only statuses
+ //
+ // Verify the following statuses:
+ // 'user_testing'
+ // 'API Verification' (github-actions[bot])
+ //
+ // At least 1 of the following statuses must be found:
+ // 'Test*' (keyman-server), e.g. 'Test Build (Keyman)'
+ // 'Ubuntu Packaging' (github-actions[bot])
+ //
+ // Ignore the following statuses:
+ // check/web/file-size
+ //
+
+ function reduceStatuses(statuses) {
+ const filtered_statuses = statuses.reduce((o, status) => {
+ if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) {
+ if(!o[status.context]) o[status.context] = {type: 'build', state: status.state};
+ } else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') {
+ if(!o[status.context]) o[status.context] = {type: 'user-test', state: status.state};;
+ } else if(status.context == 'API Verification') {
+ if(!o[status.context]) o[status.context] = {type: 'check', state: status.state};
+ } else if(status.context == 'Ubuntu Packaging') {
+ if(!o[status.context]) o[status.context] = {type: 'build', state: status.state};
+ } else if(status.context == 'check/web/file-size') {
+ // Ignore check/web/file-size -- we won't block automerge for this at this point
+ } else {
+ // We fail with an 'unknown status' response if we get a new status check
+ // so we can be sure we are not skipping known status checks
+ o[status.context] = {type: 'unknown', state: status.state};
+ }
+ return o;
+
+ }, {});
+ return filtered_statuses;
+ }
+
+ //
+ // Given the collection of status checks we care about, return
+ // an aggregate status -- error, failed, pending, or success,
+ // and a summary description
+ //
+ function calculateFinalStatus(filtered_statuses) {
+ const counts = {};
+ let hasBuilds = false;
+ for(const context of Object.keys(filtered_statuses)) {
+ const { state, type } = filtered_statuses[context];
+ if(type == 'unknown') {
+ // We special-case for unknown status checks, and never permit them
+ return [
+ 'error', `An unknown context ${context} was found, cannot calculate build status.`
+ ];
+ }
+ if(type == 'build') {
+ hasBuilds = true;
+ }
+ counts[state] = counts[state] ? counts[state] + 1 : 1;
+ }
+
+ // If we do not have any statuses yet, we wait
+ if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) {
+ return ['pending', 'Builds have not yet been triggered ⌛'];
+ }
+
+ const state =
+ counts.error ? 'error' :
+ counts.failed ? 'failed' :
+ counts.pending ? 'pending' :
+ 'success';
+
+ let description = '';
+ function appendDescription(count, state) {
+ if(!count) return;
+ if(description != '') description += '; ';
+ description += `${count} check${count == 1 ? '' : 's'} ${state}`;
+ }
+ appendDescription(counts.error, 'in an error state ❌');
+ appendDescription(counts.failed, 'failed ❌');
+ appendDescription(counts.pending, 'pending ⌛');
+ appendDescription(counts.success, 'completed successfully ✅');
+
+ return [ state, description ];
+ }
+
+ async function getCommitStatuses(github, owner, repo, sha) {
+ const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', {
+ owner,
+ repo,
+ sha,
+ headers: {
+ 'X-GitHub-Api-Version': '2022-11-28'
+ }
+ });
+ return statuses;
+ }
+
+ async function getCommitCheckRuns(github, owner, repo, sha) {
+ const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', {
+ owner,
+ repo,
+ sha,
+ headers: {
+ 'X-GitHub-Api-Version': '2022-11-28'
+ }
+ });
+ return statuses;
+ }
+
+ function calculateCheckResult(statuses) {
+ if(!Array.isArray(statuses)) {
+ return ['error', 'Failed to retrieve status checks from GitHub ❌'];
+ }
+
+ const filtered_statuses = reduceStatuses(statuses);
+
+ const result = calculateFinalStatus(filtered_statuses);
+ return result;
+ }
+
+ async function test(github, owner, repo, sha) {
+ // Get statuses from sha
+ const statuses = await getCommitStatuses(github, owner, repo, sha);
+ return calculateCheckResult(statuses);
+ }
+
+ async function createCheck(github, owner, repo, sha) {
+ const check = await github.rest.checks.create({
+ owner,
+ repo,
+ head_sha: sha,
+ name: 'Build Outcome',
+ status: 'in_progress',
+ });
+ return check.data.id;
+ }
+
+ async function updateCheck(github, owner, repo, checkRunId, status, description) {
+ const checkStatus = status == 'pending' ? 'in_progress' : 'completed';
+ const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure');
+
+ await github.rest.checks.update({
+ owner,
+ repo,
+ check_run_id: checkRunId,
+ status: checkStatus,
+ conclusion,
+ output: {
+ title: description,
+ summary: ''
+ }
+ });
+ }
+
+ const { owner, repo } = context.repo;
+ const sha =
+ context.payload?.check_suite?.sha || /* check run completed */
+ context.payload?.inputs?.commit || /* manual run */
+ context.payload?.after || /* push */
+ context.payload?.commit?.sha || /* status */
+ context.sha; /* probably 'master'! */
+
+ const checkRunId = await createCheck(github, owner, repo, sha);
+ const res = await test(github, owner, repo, sha);
+ await updateCheck(github, owner, repo, checkRunId, res[0], res[1]);
+
diff --git a/HISTORY.md b/HISTORY.md
index daf9a20257..d774cd3454 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,194 @@
# Keyman Version History
+## 19.0.94 alpha 2025-08-01
+
+* maint(common): use `cpp_std=c++17` in meson project default_options (#14433)
+
+## 19.0.93 alpha 2025-07-29
+
+* test(developer): add unit tests for `isValidUtf8` (#14381)
+* maint(linux): fix stopping of test server (#14403)
+
+## 19.0.92 alpha 2025-07-28
+
+* build(deps): bump multer from 2.0.1 to 2.0.2 (#14373)
+* refactor(linux): remove shellcheck warnings (#14409)
+* refactor(linux): move dbus defines to common include (#14407)
+* docs(linux): update readme for test service (#14408)
+* fix(windows): add check a fix for registry datatypes (#14358)
+
+## 19.0.91 alpha 2025-07-26
+
+* maint(windows): fix calls of renamed functions (#14413)
+
+## 19.0.90 alpha 2025-07-25
+
+* maint(common): fix standalone use of `resources/builder.inc.sh` (#14399)
+* maint(resources): use `tc_` prefix for all `tc-*.inc.sh` functions (#14267)
+
+## 19.0.89 alpha 2025-07-24
+
+* docs(common): Document minimum version of Chrome 95.0 for Android (#14385)
+* refactor(android): Update minimum target Chrome version to 95.0 and refactor to use constants (#14391)
+
+## 19.0.88 alpha 2025-07-23
+
+* maint(windows): consider `GO_FAST` env variable in `add_zip_files` (#14338)
+* maint(common): add unit tests for `locate_emscripten.inc.sh` (#14346)
+* refactor(android): create functions for actions (#14351)
+* maint(linux): fix `locate_emscripten` if `$EMSCRIPTEN_BASE` dir missing (#14347)
+* maint(android): consolidate creating zip file (#14353)
+
+## 19.0.87 alpha 2025-07-22
+
+* refactor(common): use test framework for zip tests (#14380)
+* maint(common): fix use of `jq.inc.sh` in git hooks (#14382)
+* test(common): improve zip tests (#14386)
+* maint(common): fix `add_zip_files` if flags come before zip filename (#14387)
+
+## 19.0.86 alpha 2025-07-21
+
+* maint(common): fail build if adding relative file to zip (#14352)
+* maint(common): set C++17 in standard.meson.build (#14355)
+* fix(linux): add `python3-pkg-resources` as explicit dependency (#14356)
+* docs(developer): add ansi and iso context and image, change img tags, and polish for readability (#14243)
+* maint(mac): consolidate `write-download_info` (#14325)
+* maint(common): add simple unit testing framework for bash (#14345)
+* maint(common): consolidate functions to determine OS (#14324)
+* fix(android): use correct path to zip.inc.sh for CI (#14377)
+* test(common): add unit test for reporting test failures (#14379)
+
+## 19.0.85 alpha 2025-07-18
+
+* refactor(common): remove warnings (#14368)
+* refactor(developer): replace deprecated std::codecvt_utf8_utf16 (#14369)
+* refactor(linux): move defines to common header files (#14372)
+
+## 19.0.84 alpha 2025-07-15
+
+* maint(common): add support for inline file exclusions to `add_zip_files` and unit tests (#14333)
+* maint(developer): use `add_zip_files` function (#14334)
+
+## 19.0.83 alpha 2025-07-14
+
+* maint(resources): add `build.sh` to run all `resource/build/test` tests (#14335)
+* maint(developer): consolidate creating KMC_ZIP file (#14309)
+
+## 19.0.82 alpha 2025-07-10
+
+* maint(android): fix Android release build (#14329)
+* maint(mac): fix end of blocks (#14330)
+* maint(mac): don't run `build.sh publish` twice (#14331)
+
+## 19.0.81 alpha 2025-07-09
+
+* docs(developer): explanation of the Description field added in Details section. (#14304)
+* fix(ios): restore keyboard scale map for iPad Air 2 (#14305)
+* maint(linux): remove Oracular, add Questing (#14319)
+* maint(android): remove `--ci` parameter from Android release builds (#14320)
+* maint(android): use absolute paths (#14308)
+* maint(windows): fix platform name for Windows (#14321)
+* maint(developer): include missing shellHelperFunctions.sh file (#14322)
+* maint(windows): replace hardcoded `symbols` paths with parameters (#14323)
+
+## 19.0.80 alpha 2025-07-08
+
+* maint(common): remove include of deleted `tc-download-info.inc.sh` (#14311)
+* maint(resources): fix triggering of builds (#14314)
+
+## 19.0.79 alpha 2025-07-07
+
+* maint(android): use `--release` for release builds (#14307)
+* refactor(common): some reformattings in `shellHelperFunctions.sh` (#14269)
+* maint(common): consolidate `write_download_info` functions (#14270)
+
+## 19.0.78 alpha 2025-07-04
+
+* docs(developer): Fix links in gestures guide (#14297)
+
+## 19.0.77 alpha 2025-07-04
+
+* maint(mac): rename `_do_publish` function to `do_publish` to fix release build (#14273)
+* maint(ios): remove extra `cd` command from ios release builds (#14272)
+* maint(android): remove `--ci` parameter from android builds (#14264)
+* refactor(resources): replace shebang for TC include files (#14265)
+* fix(core): fix failing `common-test-core.sh` script (#14271)
+* maint(common): match OSTYPE correctly on macOS (#14277)
+* fix(common): fix triggering builds if no builds to start (#14274)
+* maint(common): trigger common builds for resources/teamcity/common changes (#14279)
+
+## 19.0.76 alpha 2025-07-03
+
+* maint(common): move TC configuration for common tests (#14205)
+* refactor(common): improve downloads include script (#14253)
+* maint(web): move rsync code to shared functions (#14254)
+* maint(developer): replace Powershell scripts (#14255)
+* maint(windows): move TC configuration for Keyman Windows to scripts (#14186)
+* feat(windows): human text message for 413 error (#14055)
+* refactor(web): move helper functions to beginning of function (#14252)
+* maint(android): add TC build scripts for Keyman for Android (#14222)
+* maint(linux): use `tc_rsync_upload` and `write_download_info` functions (#14258)
+* maint(ios): add TC build scripts for iOS (#14229)
+* maint(mac): add TC build scripts for macOS (#14239)
+
+## 19.0.75 alpha 2025-07-02
+
+* feat(resources): support ignoring unknown options in builder (#14216)
+* maint(common): consolidate 7Z_HOME, SEVENZ_HOME, SEVEN_Z_HOME env vars (#14162)
+
+## 19.0.74 alpha 2025-07-01
+
+* maint(web): adjust rsync path (#14249)
+
+## 19.0.73 alpha 2025-07-01
+
+* fix(web): deploy `web/build/publish` folder (#14237)
+* fix(web): change env variable name (#14244)
+
+## 19.0.72 alpha 2025-07-01
+
+* maint(web): cleanup rsync call from bash for Windows agents and refactor (#14236)
+
+## 19.0.71 alpha 2025-06-30
+
+* refactor(linux): rename linux agent specific functions (#14231)
+
+## 19.0.70 alpha 2025-06-27
+
+* docs(developer): Add details on Template choices (#14228)
+* chore(common,developer): expand 'x' to 'compileContext' (#14218)
+
+## 19.0.69 alpha 2025-06-25
+
+* maint(resources): support additional workflow events for pr-build-status (#14219)
+* chore: add tips and caution of gestures for Keyman Developer + update license + removal of span tag (#14204)
+
+## 19.0.68 alpha 2025-06-24
+
+* maint(resources): Build-bot now available to control test builds (#14125)
+* maint(developer): support buildLevel (#14196)
+* fix(resources): allow branch names in run-required-test-builds shortcut call (#14217)
+* maint(resources): add pr-build-status GitHub Action to summarize build status (#14199)
+
+## 19.0.67 alpha 2025-06-22
+
+* chore(developer): ldml: additional line numbers (#14104)
+
+## 19.0.66 alpha 2025-06-16
+
+* fix(windows): add values wucUpdateAvailable and wucNotChecked to TRemoteUpdateCheckResult enum (#14123)
+
+## 19.0.65 alpha 2025-06-13
+
+* maint(developer): fix developer release build (#14185)
+* maint(developer,web): use double quotes inside `--rsync-path` again (#14194)
+
+## 19.0.64 alpha 2025-06-13
+
+* fix(windows): fix download failing when cache.json missing (#14095)
+* maint(resources): only automatically merge keyman-server PRs with automerge label (#14171)
+* maint(developer,web): use braces for variables in powershell scripts (#14184)
+
## 19.0.63 alpha 2025-06-12
* maint(web): add some more arguments for web release build script (#14167)
diff --git a/VERSION.md b/VERSION.md
index efcfb4b252..d0f6206d48 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-19.0.64
\ No newline at end of file
+19.0.95
\ No newline at end of file
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
index 3eec444fbd..40b41b6176 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
@@ -864,6 +864,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
private void displayUpdateChrome() {
// TextView's default string is to update Chrome
+ TextView textView = (TextView)findViewById(R.id.kmWebViewChromeTextView);
+ textView.setText(String.format(getString(R.string.text_require_chrome_version),
+ WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME));
Button button = (Button)findViewById(R.id.webViewChromeButton);
button.setText(getString(R.string.button_update_chrome));
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml
index 09716bdd8a..ea5a9220d5 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/check_chrome_webview_layout.xml
@@ -15,7 +15,7 @@
android:background="@android:color/white"
android:gravity="start"
android:layout_gravity="center_vertical"
- android:text="@string/text_require_chrome_57"
+ android:text="@string/text_require_chrome_version"
android:layout_marginStart="@dimen/button_playstore_margin"
android:textSize="@dimen/update_chrome_label_textsize" />
diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
index a0447d0f89..32cdf07f1a 100644
--- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
@@ -30,10 +30,13 @@
Version: %1$s
-
+
Keyman requires Chrome version 57 or newer.
+
+ Keyman requires Chrome version %1$s or newer.
+
Update Chrome
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
index 75b0443537..5553d256b0 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
@@ -495,6 +495,11 @@ public final class KMManager {
didCopyAssets = true;
}
+ calculateDefaultKeyboardHeights(context);
+ SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
+ KeyboardHeight_Context_Portrait_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightPortrait, KMManager.KeyboardHeight_Context_Portrait_Default);
+ KeyboardHeight_Context_Landscape_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightLandscape, KMManager.KeyboardHeight_Context_Landscape_Default);
+
if (keyboardType == KeyboardType.KEYBOARD_TYPE_UNDEFINED) {
String msg = "Cannot initialize: Invalid keyboard type";
KMLog.LogError(TAG, msg);
@@ -508,12 +513,6 @@ public final class KMManager {
migrateCloudKeyboards(appContext);
CloudDownloadMgr.getInstance().initialize(appContext);
-
- calculateDefaultKeyboardHeights(context);
- SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
- KeyboardHeight_Context_Portrait_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightPortrait, KMManager.KeyboardHeight_Context_Portrait_Default);
- KeyboardHeight_Context_Landscape_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightLandscape, KMManager.KeyboardHeight_Context_Landscape_Default);
-
}
public static void executeResourceUpdate(Context aContext)
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/WebViewUtils.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/WebViewUtils.java
index de8b37f73e..0586ecbcc8 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/util/WebViewUtils.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/WebViewUtils.java
@@ -32,6 +32,13 @@ public final class WebViewUtils {
FULL; // WebView installed and enabled
}
+ // Min version of Chrome for Keyman for Android for EngineWebViewVersionStatus.DEGRADED
+ public static final String KEYMAN_MIN_TARGET_VERSION_DEGRADED_ANDROID_CHROME = "37.0";
+
+ // Min version of Chrome for Keyman for Android (EngineWebViewVersionStatus.FULL)
+ // TODO: Keep this version in sync with resources/build/minimum-versions.inc.sh
+ public static final String KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME = "95.0";
+
private static final String CHROME_INSTALL_PATTERN_FORMATSTR = "^.*Chrome/([\\d.]+).*$";
private static final Pattern installPattern = Pattern.compile(CHROME_INSTALL_PATTERN_FORMATSTR);
@@ -52,9 +59,11 @@ public final class WebViewUtils {
chromeVersion = getChromeVersion(context, webView);
}
- if (FileUtils.compareVersions("37.0", chromeVersion) == FileUtils.VERSION_GREATER) {
+ if (FileUtils.compareVersions(KEYMAN_MIN_TARGET_VERSION_DEGRADED_ANDROID_CHROME, chromeVersion)
+ == FileUtils.VERSION_GREATER) {
return EngineWebViewVersionStatus.DISABLED;
- } else if (FileUtils.compareVersions("57.0", chromeVersion) == FileUtils.VERSION_GREATER) {
+ } else if (FileUtils.compareVersions(KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME, chromeVersion)
+ == FileUtils.VERSION_GREATER) {
return EngineWebViewVersionStatus.DEGRADED;
}
diff --git a/android/KMEA/app/src/main/res/values-es-rES/strings.xml b/android/KMEA/app/src/main/res/values-es-rES/strings.xml
index 0ab0ea317b..4846da0dac 100644
--- a/android/KMEA/app/src/main/res/values-es-rES/strings.xml
+++ b/android/KMEA/app/src/main/res/values-es-rES/strings.xml
@@ -83,13 +83,13 @@
Error en el teclado %1$s:%2$s para %3$s idioma.
- Keyman requires WebView to be installed.\"
+ Keyman requiere que WebView esté instalado.\"
- Keyman requires WebView to be enabled.\"
+ Keyman requiere que WebView esté habilitado.\"
- Install WebView
+ Instalar WebView
- Enable WebView
+ Habilitar WebView
Comprobando si hay un diccionario asociado para descargar
No se puede conectar al servidor Keyman para comprobar si hay un diccionario asociado para descargar
@@ -124,7 +124,7 @@
¡Falló al acceder al servidor!
- DownloadManager disabled - cannot check for updates
+ Descargador deshabilitado: no se pueden comprobar actualizaciones
"¡Todos los recursos están actualizados!"
diff --git a/android/KMEA/app/src/test/java/com/keyman/engine/util/WebViewUtilsTest.java b/android/KMEA/app/src/test/java/com/keyman/engine/util/WebViewUtilsTest.java
index 344b01879a..342934200a 100644
--- a/android/KMEA/app/src/test/java/com/keyman/engine/util/WebViewUtilsTest.java
+++ b/android/KMEA/app/src/test/java/com/keyman/engine/util/WebViewUtilsTest.java
@@ -32,28 +32,30 @@ public class WebViewUtilsTest {
@Test
public void test_Chrome36_EngineWebViewVersionStatusDisabled() {
- String chromeVersion = "36.0";
+ double chromeVersionFloat = Float.parseFloat(WebViewUtils.KEYMAN_MIN_TARGET_VERSION_DEGRADED_ANDROID_CHROME) - 1.0;
+ String chromeVersion = String.valueOf(chromeVersionFloat);
Assert.assertEquals(WebViewUtils.getEngineWebViewVersionStatus(context, null, chromeVersion),
WebViewUtils.EngineWebViewVersionStatus.DISABLED);
}
@Test
public void test_Chrome37_EngineWebViewVersionStatusDegraded() {
- String chromeVersion = "37.0";
+ String chromeVersion = WebViewUtils.KEYMAN_MIN_TARGET_VERSION_DEGRADED_ANDROID_CHROME;
Assert.assertEquals(WebViewUtils.getEngineWebViewVersionStatus(context, null, chromeVersion),
WebViewUtils.EngineWebViewVersionStatus.DEGRADED);
}
@Test
- public void test_Chrome56_EngineWebViewVersionStatusDegraded() {
- String chromeVersion = "56.0";
+ public void test_Chrome94_EngineWebViewVersionStatusDegraded() {
+ double chromeVersionFloat = Float.parseFloat(WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME) - 1.0;
+ String chromeVersion = String.valueOf(chromeVersionFloat);
Assert.assertEquals(WebViewUtils.getEngineWebViewVersionStatus(context, null, chromeVersion),
WebViewUtils.EngineWebViewVersionStatus.DEGRADED);
}
@Test
- public void test_Chrome57_EngineWebViewVersionStatusFull() {
- String chromeVersion = "57.0";
+ public void test_Chrome95_EngineWebViewVersionStatusFull() {
+ String chromeVersion = WebViewUtils.KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME;
Assert.assertEquals(WebViewUtils.getEngineWebViewVersionStatus(context, null, chromeVersion),
WebViewUtils.EngineWebViewVersionStatus.FULL);
}
diff --git a/android/Tests/KeyboardHarness/app/build.gradle b/android/Tests/KeyboardHarness/app/build.gradle
index ab8066393b..3eb7f27d28 100644
--- a/android/Tests/KeyboardHarness/app/build.gradle
+++ b/android/Tests/KeyboardHarness/app/build.gradle
@@ -6,7 +6,7 @@ ext.rootPath = '../../../'
apply from: "$rootPath/version.gradle"
android {
- compileSdk 34
+ compileSdk 35
namespace="com.keyman.android.tests.keyboardHarness"
// Don't compress kmp files so they can be copied via AssetManager
@@ -21,7 +21,7 @@ android {
defaultConfig {
applicationId "com.keyman.android.tests.keyboardHarness"
minSdkVersion 21
- targetSdkVersion 34
+ targetSdkVersion 35
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle
versionCode KEYMAN_VERSION_CODE as Integer
diff --git a/android/Tests/KeyboardHarness/build.gradle b/android/Tests/KeyboardHarness/build.gradle
index 8fdf2aa422..966127346a 100644
--- a/android/Tests/KeyboardHarness/build.gradle
+++ b/android/Tests/KeyboardHarness/build.gradle
@@ -15,8 +15,8 @@ buildscript {
allprojects {
repositories {
+ maven { url uri("${projectDir}/libs") }
google()
- jcenter()
mavenCentral()
}
}
diff --git a/android/Tests/keycode/app/build.gradle b/android/Tests/keycode/app/build.gradle
index a0b24abb44..2f828db571 100644
--- a/android/Tests/keycode/app/build.gradle
+++ b/android/Tests/keycode/app/build.gradle
@@ -1,11 +1,18 @@
-apply plugin: 'com.android.application'
+plugins {
+ id 'com.android.application'
+}
ext.rootPath = '../../../'
apply from: "$rootPath/version.gradle"
android {
compileSdk 34
- namespace="com.keyman.android.tests.keycode"
+ namespace "com.keyman.android.tests.keycode"
+
+ buildFeatures {
+ // needed for custom BuildConfigField values in defaultConfig
+ buildConfig = true
+ }
defaultConfig {
applicationId "com.keyman.android.tests.keycode"
@@ -27,11 +34,10 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'androidx.appcompat:appcompat:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'com.google.android.material:material:1.12.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
-
- testImplementation 'androidx.test:core:1.5.0'
- testImplementation 'androidx.test.ext:junit:1.1.5'
- testImplementation 'org.robolectric:robolectric:4.10.3'
+ testImplementation 'androidx.test.ext:junit:1.2.1'
+ testImplementation 'org.robolectric:robolectric:4.14.1'
+ testImplementation 'androidx.test:core:1.6.1'
}
diff --git a/android/Tests/keycode/app/src/androidTest/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java b/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java
similarity index 72%
rename from android/Tests/keycode/app/src/androidTest/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java
rename to android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java
index d4bbda1b05..cf2b42ce88 100644
--- a/android/Tests/keycode/app/src/androidTest/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java
+++ b/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleInstrumentedTest.java
@@ -1,25 +1,25 @@
package com.keyman.android.tests.keycode;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.core.app.ApplicationProvider;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import android.content.Context;
import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
/**
* Instrumented test, which will execute on an Android device.
*
* @see Testing documentation
*/
-@RunWith(AndroidJUnit4.class)
+@RunWith(RobolectricTestRunner.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
+ Context appContext = ApplicationProvider.getApplicationContext();
assertEquals("com.keyman.android.tests.keycode", appContext.getPackageName());
}
diff --git a/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleUnitTest.java b/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleUnitTest.java
index 7e0e3c7481..544b21224c 100644
--- a/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleUnitTest.java
+++ b/android/Tests/keycode/app/src/test/java/com/keyman/android/tests/keycode/ExampleUnitTest.java
@@ -1,4 +1,4 @@
-package com.example.darcy.keycode;
+package com.keyman.android.tests.keycode;
import org.junit.Test;
diff --git a/android/Tests/keycode/build.gradle b/android/Tests/keycode/build.gradle
index 47a85343bf..3a7f319289 100644
--- a/android/Tests/keycode/build.gradle
+++ b/android/Tests/keycode/build.gradle
@@ -4,10 +4,10 @@ buildscript {
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.4.2'
+ classpath 'com.android.tools.build:gradle:8.9.2'
// NOTE: Do not place your application dependencies here; they belong
@@ -18,10 +18,6 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/android/Tests/keycode/gradle.properties b/android/Tests/keycode/gradle.properties
index 7d43aa3719..44ff6135fe 100644
--- a/android/Tests/keycode/gradle.properties
+++ b/android/Tests/keycode/gradle.properties
@@ -14,4 +14,6 @@ org.gradle.jvmargs=-Xmx1024m
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+# AGP 8.0 generates R classes for resources defined in the current module only.
+android.nonTransitiveRClass=false
diff --git a/android/build.sh b/android/build.sh
index 12017e2c6b..eeaa3f6e9a 100755
--- a/android/build.sh
+++ b/android/build.sh
@@ -13,9 +13,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
-. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
-. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
-. "$KEYMAN_ROOT/resources/build/zip.inc.sh"
+# shellcheck disable=SC2154
+. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh"
+. "${KEYMAN_ROOT}/resources/build/build-utils-ci.inc.sh"
+. "${KEYMAN_ROOT}/resources/build/zip.inc.sh"
################################ Main script ################################
@@ -24,8 +25,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
#
oemtargets=()
-if [ ! -z "${RELEASE_OEM+x}" ]; then
- if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
+if [[ ! -z "${RELEASE_OEM+x}" ]]; then
+ if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then
oemtargets+=(":fv=../oem/firstvoices/android OEM FirstVoices for Android app")
fi
fi
@@ -38,8 +39,7 @@ builder_describe \
build \
test \
"publish Publishes symbols to Sentry and the APKs to the Play Store." \
- "archive Copy release artifacts to upload/ and rsync to downloads.keyman" \
- "--ci+ Deprecated build option. Remove in 20.0" \
+ "archive Copy release artifacts to upload/ and create zip" \
--upload-sentry+ \
":engine=KMEA Keyman Engine for Android" \
":app=KMAPro Keyman for Android" \
@@ -51,72 +51,75 @@ builder_describe \
builder_parse "$@"
-# Override JAVA_HOME to OpenJDK 11
-set_java_home
-
-# This script also responsible for cleaning up /android/upload
-builder_run_child_actions clean
-
-if builder_start_action clean; then
+function do_clean() {
builder_heading "Cleanup /android/upload"
- rm -rf "$KEYMAN_ROOT/android/upload"
- builder_finish_action success clean
-fi
-
-builder_run_child_actions configure build test
-
-function do_test_help() {
- check-markdown "$KEYMAN_ROOT/android/docs/help"
- check-markdown "$KEYMAN_ROOT/android/docs/engine"
+ rm -rf "${KEYMAN_ROOT}/android/upload"
}
-builder_run_action test:help do_test_help
+function do_test_help() {
+ check-markdown "${KEYMAN_ROOT}/android/docs/help"
+ check-markdown "${KEYMAN_ROOT}/android/docs/engine"
+}
-builder_run_child_actions publish
+function archive_artifacts() {
+ local UPLOAD_PATH KEYMAN_ENGINE_ANDROID_ZIP KEYMAN_APK FIRSTVOICES_APK
-# Copy release artifacts to upload/ and rsync to downloads.keyman.com
-if builder_start_action archive; then
- UPLOAD_PATH="$KEYMAN_ROOT/android/upload/${KEYMAN_VERSION}"
+ UPLOAD_PATH="${KEYMAN_ROOT}/android/upload/${KEYMAN_VERSION}"
KEYMAN_ENGINE_ANDROID_ZIP="keyman-engine-android-${KEYMAN_VERSION}.zip"
KEYMAN_APK="keyman-${KEYMAN_VERSION}.apk"
FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION}.apk"
- ZIP_FILE="${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}"
- ZIP_FLAGS=("-q" "-r") # quiet, recursive
+ rm -rf "${UPLOAD_PATH}"
mkdir -p "${UPLOAD_PATH}"
# Create Keyman Engine for Android archive
- builder_echo "Copying Keyman Engine for Android into ${UPLOAD_PATH}..."
- cd "${UPLOAD_PATH}"
- cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs/keyman-engine.aar" ./
- add_zip_files "${ZIP_FILE}" "${ZIP_FLAGS[@]}" "keyman-engine.aar"
- rm -f "keyman-engine.aar"
+ builder_echo "Adding Keyman Engine for Android to zip..."
+ (
+ # shellcheck disable=SC2164
+ cd "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs"
+ add_zip_files -q -r "${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}" keyman-engine.aar
+ )
- builder_echo "Copying Keyman Engine for Android Sample projects into ${UPLOAD_PATH}..."
- cp -rf "${KEYMAN_ROOT}/android/Samples" ./
- add_zip_files "${ZIP_FILE}" "-x@../../zip-excludes" "${ZIP_FLAGS[@]}" "Samples"
- rm -rf "Samples"
+ builder_echo "Adding Keyman Engine for Android Sample projects to zip..."
+ (
+ # shellcheck disable=SC2164
+ cd "${KEYMAN_ROOT}/android"
+ add_zip_files -q -r -xr!build.sh "${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}" Samples
+ )
# Copy release APK
- cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" ./
+ cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}"
# FirstVoices app
- if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
- cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" ./
+ if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then
+ cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}"
fi
#
# Write download info files
#
- cd "${UPLOAD_PATH}"
- write_download_info "Keyman Engine for Android" "${KEYMAN_ENGINE_ANDROID_ZIP}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
- write_download_info "Keyman for Android" "${KEYMAN_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
+ write_download_info "${UPLOAD_PATH}" "${KEYMAN_ENGINE_ANDROID_ZIP}" "Keyman Engine for Android" zip android
+ write_download_info "${UPLOAD_PATH}" "${KEYMAN_APK}" "Keyman for Android" apk android
- if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
- write_download_info "FirstVoices Keyboards" "${FIRSTVOICES_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
+ if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then
+ write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_APK}" "FirstVoices Keyboards" apk android
fi
+}
- builder_finish_action success archive
-fi
+# Override JAVA_HOME to OpenJDK 11
+set_java_home
+
+# This script also responsible for cleaning up /android/upload
+builder_run_child_actions clean
+
+builder_run_action clean do_clean
+
+builder_run_child_actions configure build test
+
+builder_run_action test:help do_test_help
+
+builder_run_child_actions publish
+
+builder_run_action archive archive_artifacts
diff --git a/android/docs/help/about/system-requirements.md b/android/docs/help/about/system-requirements.md
index 16efbf28af..ae43794793 100644
--- a/android/docs/help/about/system-requirements.md
+++ b/android/docs/help/about/system-requirements.md
@@ -9,7 +9,7 @@ Keyman for Android will run on Android phones and tablets that have a minimum ve
[Android 5.0 (Lollipop)](https://developer.android.com/about/versions/lollipop).
### Minimum Chrome Version
-Keyman for Android requires a minimum version 53.0 of [Google
+Keyman for Android requires a minimum version 95.0 of [Google
Chrome](https://play.google.com/store/apps/details?id=com.android.chrome).
If you have an older device, you may need to upgrade Chrome before you can use
Keyman, e.g. through the Play Store.
diff --git a/common/linux/build.sh b/common/linux/build.sh
index 756826cc2d..b52102313e 100755
--- a/common/linux/build.sh
+++ b/common/linux/build.sh
@@ -18,7 +18,7 @@ builder_describe "Keyman common Linux modules" \
builder_parse "$@"
-if [[ $BUILDER_OS != linux ]]; then
+if ! builder_is_linux; then
builder_echo grey "Platform is not linux; skipping common/linux"
exit 0
fi
diff --git a/common/mac/build.sh b/common/mac/build.sh
index 392966d84c..44a7064c83 100755
--- a/common/mac/build.sh
+++ b/common/mac/build.sh
@@ -18,7 +18,7 @@ builder_describe "Keyman common mac modules" \
builder_parse "$@"
-if [[ $BUILDER_OS != mac ]]; then
+if ! builder_is_macos; then
builder_echo grey "Platform is not macOS; skipping common/mac"
exit 0
fi
diff --git a/common/test/resources/test_kill_browserstack.sh b/common/test/resources/test_kill_browserstack.sh
index 82e40f85cd..f6ea16a36b 100755
--- a/common/test/resources/test_kill_browserstack.sh
+++ b/common/test/resources/test_kill_browserstack.sh
@@ -12,7 +12,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
-if [[ $BUILDER_OS == win ]]; then
+if builder_is_windows; then
# BrowserStackLocal may not exist, so always pass
taskkill //f //im BrowserStackLocal.exe || true
fi
diff --git a/common/web/types/src/kmx/kmx-plus/element-string.ts b/common/web/types/src/kmx/kmx-plus/element-string.ts
index e65d996f34..af17145427 100644
--- a/common/web/types/src/kmx/kmx-plus/element-string.ts
+++ b/common/web/types/src/kmx/kmx-plus/element-string.ts
@@ -81,11 +81,11 @@ export class ElementString extends Array {
// error. So we can just exit here.
return null; // UnicodeSet error
}
- const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges, options?.x);
+ const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges, options?.compileContext);
if (!uset) {
return null; // UnicodeSet error already added to callback
}
- elem.uset = sections.uset.allocUset(uset, sections, options?.x);
+ elem.uset = sections.uset.allocUset(uset, sections, options?.compileContext);
elem.value = sections.strs.allocString('', {...options, singleOk: true}); // no string
} else if (item.type === ElementType.codepoint || item.type === ElementType.escaped || item.type === ElementType.string) {
// some kind of a string
diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
index 373cc57b27..4fd5dcd632 100644
--- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
+++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
@@ -141,7 +141,7 @@ export class StrsItem {
/** add any context from the options to this strsitem */
setContext(opts?: StrsOptions) {
// At present, there's only a single piece of context available
- this._context = this._context || opts?.x;
+ this._context = this._context || opts?.compileContext;
}
get context() : any {
@@ -174,7 +174,7 @@ export interface StrsOptions {
/** string can be stored as a single CharStrsItem, not in strs table. */
singleOk?: boolean;
/** optional context */
- x?: any;
+ compileContext?: any;
};
export class Strs extends Section {
@@ -374,13 +374,13 @@ export class Vars extends Section {
export class VarsItem extends Section {
id: StrsItem;
value: StrsItem;
- x?: any;
+ compileContext?: any;
- constructor(id: string, value: string, sections: DependencySections, x?: any) {
+ constructor(id: string, value: string, sections: DependencySections, compileContext?: any) {
super();
this.id = sections.strs.allocString(id);
- this.value = sections.strs.allocString(value, {unescape: true});
- this.x = x;
+ this.value = sections.strs.allocString(value, { unescape: true });
+ this.compileContext = compileContext;
}
valid() : boolean {
@@ -389,11 +389,11 @@ export class VarsItem extends Section {
};
export class UnicodeSetItem extends VarsItem {
- constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser, x?: any) {
- super(id, value, sections, x);
+ constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser, compileContext?: any) {
+ super(id, value, sections, compileContext);
const needRanges = sections.usetparser.sizeUnicodeSet(value);
if (needRanges >= 0) {
- this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges);
+ this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges, compileContext);
} // otherwise: error (was recorded via callback)
}
unicodeSet?: UnicodeSet;
@@ -403,9 +403,9 @@ export class UnicodeSetItem extends VarsItem {
};
export class SetVarItem extends VarsItem {
- constructor(id: string, value: string[], sections: DependencySections, x?: any) {
- super(id, value.join(' '), sections, x);
- this.items = sections.elem.allocElementString(sections, {x}, value);
+ constructor(id: string, value: string[], sections: DependencySections, compileContext?: any) {
+ super(id, value.join(' '), sections, compileContext);
+ this.items = sections.elem.allocElementString(sections, { compileContext }, value);
}
items: ElementString; // element string array
valid() : boolean {
@@ -414,8 +414,8 @@ export class SetVarItem extends VarsItem {
};
export class StringVarItem extends VarsItem {
- constructor(id: string, value: string, sections: DependencySections, x?: any) {
- super(id, value, sections, x);
+ constructor(id: string, value: string, sections: DependencySections, compileContext?: any) {
+ super(id, value, sections, compileContext);
}
// no added fields
};
@@ -453,7 +453,7 @@ export class Tran extends Section {
};
export class UsetItem {
- constructor(public uset: UnicodeSet, public str: StrsItem, public x?: any) {
+ constructor(public uset: UnicodeSet, public str: StrsItem, public compileContext?: any) {
}
compareTo(other: UsetItem) : number {
return this.str.compareTo(other.str);
@@ -462,11 +462,11 @@ export class UsetItem {
export class Uset extends Section {
usets: UsetItem[] = [];
- allocUset(set: UnicodeSet, sections: DependencySections, x?: any) : UsetItem {
+ allocUset(set: UnicodeSet, sections: DependencySections, compileContext?: any) : UsetItem {
// match the same pattern
let result = this.usets.find(s => set.pattern == s.uset.pattern);
if (result === undefined) {
- result = new UsetItem(set, sections.strs.allocString(set.pattern), x);
+ result = new UsetItem(set, sections.strs.allocString(set.pattern), compileContext);
this.usets.push(result);
}
return result;
diff --git a/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts b/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts
index 797a5415b9..6ad16cd4ee 100644
--- a/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts
+++ b/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts
@@ -8,13 +8,13 @@ export interface UnicodeSetParser {
* @param pattern string to parse such as `[a-z]`
* @param rangeCount number of ranges to allow for
*/
- parseUnicodeSet(pattern: string, rangeCount: number, x?: any) : UnicodeSet | null;
+ parseUnicodeSet(pattern: string, rangeCount: number, compileContext?: any) : UnicodeSet | null;
/**
* Calculate the number of ranges in a UnicodeSet
* @param pattern string to parse such as `[a-z]`
* @returns number of ranges, or -1 (with callback-reported err) on err
*/
- sizeUnicodeSet(pattern: string, x?: any) : number;
+ sizeUnicodeSet(pattern: string, compileContext?: any) : number;
}
/**
diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts
index 071c931e9e..779af42ba5 100644
--- a/common/web/types/src/main.ts
+++ b/common/web/types/src/main.ts
@@ -19,6 +19,8 @@ export * as KmpJsonFile from './package/kmp-json-file.js';
export { Uni_IsSurrogate1, Uni_IsSurrogate2 } from './util/util.js';
export * as util from './util/util.js';
+export { ObjectWithCompileContext } from './util/types.js';
+
export * as KeymanFileTypes from './util/file-types.js';
export * as Schemas from './schemas.js';
diff --git a/common/web/types/src/util/types.ts b/common/web/types/src/util/types.ts
new file mode 100644
index 0000000000..3ed1dda4f9
--- /dev/null
+++ b/common/web/types/src/util/types.ts
@@ -0,0 +1,6 @@
+/**
+ * Any object with metadata, for example for line number errs.
+ * Defined as 'any' here to reduce noise on the client side.
+ * @see {@link KeymanXMLReader.getMetaData()}
+ */
+export type ObjectWithCompileContext = any;
diff --git a/common/web/types/src/util/util.ts b/common/web/types/src/util/util.ts
index e0ce2fdbbc..e9d4060ed0 100644
--- a/common/web/types/src/util/util.ts
+++ b/common/web/types/src/util/util.ts
@@ -3,7 +3,7 @@ export { MATCH_HEX_ESCAPE, CONTAINS_QUAD_ESCAPE, MATCH_QUAD_ESCAPE };
/**
* xml2js will not place single-entry objects into arrays. Easiest way to fix
- * this is to box them ourselves as needed. Ensures that o.x is an array.
+ * this is to box them ourselves as needed. Ensures that o[x] is always an array.
*
* @param o Object with property to box
* @param x Name of element to box
diff --git a/common/windows/build.sh b/common/windows/build.sh
index 06f5dd0201..663a7f41c7 100755
--- a/common/windows/build.sh
+++ b/common/windows/build.sh
@@ -18,7 +18,7 @@ builder_describe "Keyman common Windows modules" \
builder_parse "$@"
-if [[ $BUILDER_OS != win ]]; then
+if ! builder_is_windows; then
builder_echo grey "Platform is not Windows; skipping common/windows"
exit 0
fi
diff --git a/core/commands.inc.sh b/core/commands.inc.sh
index 761be3db4d..73ad033a8e 100644
--- a/core/commands.inc.sh
+++ b/core/commands.inc.sh
@@ -91,7 +91,7 @@ do_test() {
if [[ $target =~ ^(x86|x64)$ ]]; then
cmd //C build.bat $target $BUILDER_CONFIGURATION test $testparams
else
- if [[ $target == wasm ]] && [[ $BUILDER_OS == mac ]]; then
+ if [[ $target == wasm ]] && builder_is_macos; then
# 11794 -- parallel tests failing on some mac build agents; temporary
# mitigation until we diagnose root cause
meson test -j 1 -C "$MESON_PATH" $testparams
@@ -136,7 +136,7 @@ do_uninstall() {
# ----------------------------------------------------------------------------
build_meson_cross_file_for_wasm() {
- if [ $BUILDER_OS == win ]; then
+ if builder_is_windows; then
local R=$(cygpath -w $(echo $EMSCRIPTEN_BASE) | sed 's_\\_\\\\_g')
else
local R=$(echo $EMSCRIPTEN_BASE | sed 's_/_\\/_g')
diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build
index 8e9539a3dd..0fe3980681 100644
--- a/core/tests/unit/ldml/keyboards/meson.build
+++ b/core/tests/unit/ldml/keyboards/meson.build
@@ -65,6 +65,7 @@ kmc_cmd = [node, '--enable-source-maps', kmc_root]
# Build all keyboards in output folder
foreach kbd : tests_needing_copy
+ # This will output a (wrong) deprecation warning with meson < 1.5 - see #13250
configure_file(input: kbd + '.xml', output: kbd + '.xml', copy: true)
endforeach
@@ -77,6 +78,7 @@ foreach kbd : tests_with_testdata
endforeach
foreach kbd : tests_from_cldr
+ # This will output a (wrong) deprecation warning with meson < 1.5 - see #13250
configure_file(input: ldml_data / kbd + '.xml', output: kbd + '.xml', copy: true)
configure_file(
command: kmc_cmd + ['build', 'ldml-test-data', '@INPUT@', '--out-file', '@OUTPUT@'],
diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md
index fb0be6d34c..577640fc65 100644
--- a/developer/docs/help/context/keyboard-editor.md
+++ b/developer/docs/help/context/keyboard-editor.md
@@ -13,13 +13,10 @@ multiple source files, which are all managed within this one editor.
The following image shows the first tab of the editor, the Details tab,
for a brand new keyboard, with those three initial tabs.
-
+
-> ### Note
-Users of earlier versions of Keyman Developer may initially have trouble
-finding tabs such as the Icon tab or the On-Screen tab, as they are not
-initially visible. The Features grid on the Details tab allows you to
-add these extra features into the keyboard.
+> [!Note]
+> Users of earlier versions of Keyman Developer may initially have trouble finding tabs such as the Icon tab or the On-Screen tab, as they are not initially visible. The Features grid on the Details tab allows you to add these extra features into the keyboard.
## Keyboard component files
@@ -47,7 +44,7 @@ because that reflects both the Details and the Layout tab.
## Details tab
-
+
The Details tab grows as you add more options to a keyboard. The fields
here are:
@@ -102,7 +99,7 @@ Features
## Layout tab
-
+
The Layout tab gives you a simple interface to quickly create a keyboard
using a visual representation of a desktop/laptop computer keyboard. You
@@ -124,13 +121,11 @@ There are two ways to assign characters to the keys:
onto the appropriate key. This will set the key to output that
character.
-> ### Hint
- To add the character to a key with existing characters, hold
-Ctrl while dropping it onto the key.
+> [!TIP]
+> To add the character to a key with existing characters, hold Ctrl while dropping it onto the key.
-
-> ### Note
- Any key that does not have a character assigned to it will output what the selected Windows layout specifies.
+> [!Note]
+> Any key that does not have a character assigned to it will output what the selected Windows layout specifies.
Distinguish between left and right Ctrl/Alt
: A Keyman keyboard can treat left and right Ctrl and Alt identically,
@@ -151,9 +146,7 @@ Display 102nd Key (as on European keyboards)
You can press and release Ctrl to select
another key on the keyboard using your keyboard.
-
-
-
+
The Code view shows the source code of the keyboard file. This is where
all the information in the Details and Layout tab is stored, and
@@ -165,7 +158,7 @@ editor shown within this view.
## On-Screen tab
-
+
This tab allows you to edit the visual representation of your keyboard
layout. The content on this tab is stored in the .kvks file associated
@@ -213,17 +206,28 @@ Distinguish between left and right Ctrl/Alt
: If checked, treats the left and right Ctrl/Alt as separate layers
Display 102nd Key (as on European keyboards)
-: European keyboards have one extra key that is not on US keyboards.
+: European keyboards (ISO layout) have one extra key that is not on US keyboards (ANSI layout).
This key is positioned to the right of the left shift key. Some
other keyboards have additional keys; these are not shown on the
- layout designer. If you have a European layout selected as your
+ layout designer.
+
+ If you have a European layout selected as your
Windows layout, the 102nd key will always be visible in the
designer; ensure you select this checkbox if you want the 102nd key
to always be visible to end users of the layout, irrespective of
- their selected base layout. When a European layout is selected as
+ their selected base layout.
+
+ When a European layout is selected as
your Windows layout, the shape of the Enter key will also change to
take two rows, and the backslash key will move down one row, but not
- otherwise change in behaviour.
+ otherwise change in behaviour.
+
+
+ *See the difference between the two layouts in Keyman Developer.*
+
+ > [!NOTE]
+ > The "Display 102nd Key (as on European keyboards)" checkbox can be left unchecked.
+ > To ensure compatibility, the extra key shouldn't be assigned since it won't be accessible on an ANSI physical keyboard.
Auto-fill underlying layout
: When the Fill from layout button is clicked, if this option is
@@ -232,7 +236,7 @@ Auto-fill underlying layout
## Touch Layout tab
-
+
The Touch Layout tab is used to create the visible representation of the
keyboard layout for touch devices. It works similarly to the On Screen
@@ -251,7 +255,7 @@ including iPhone and iPad, in different orientations, to allow you to
visualize the keyboard layout before you load it onto a device. The
following image shows all aspects of the touch design view.
-
+
### Left sidebar controls
@@ -266,6 +270,20 @@ Template...
be lost. Therefore, selecting an appropriate template early in the
development process is suggested.
+ - template-basic: has only a phone layout (4 rows of keys), with three layers: default (K_A through K_Z
+ plus some punctuation), shift (Shift K_A through Shift K_Z, plus some punctuation), numeric (K_0 through
+ K_9 unshifted and shifted, plus other non-alphanumerice keys).
+
+ - template-latin: has both phone and tablet layouts (which are identical except for some key spacing,
+ each with 4 rows of keys), with four layers: default, shift, numeric, symbol. The first three layers
+ are similar to those of template-basic, with the addition of longpress keys for accented characters.
+ The symbol layer has many symbols not usually seen on a physical keyboard.
+
+ - template-traditional: has both phone (4 rows of keys) and tablet (5 rows of keys) layouts. The phone
+ layout is similar to the template-basic layout. The tablet layout has eight layers: default, shift, ctrl,
+ shift-ctrl, alt, shift-alt, ctrl-alt, shift-ctrl-alt. The default layer has the keys from the default layer
+ of the desktop layout, the shift layer has the keys from the shift layer of the desktop layout, and so on.
+
Import from On Screen
: If you have an existing On-Screen Keyboard for your keyboard layout,
importing the design from the On-Screen Keyboard can reduce the
@@ -454,7 +472,7 @@ dragged between the main keyboard area and the long press area. The bar
below the long press area contains the corresponding controls for the
long press keys.
-
+
The Code view allows you to edit the JSON source of the touch layout
file. This makes certain operations simpler, such as batched rearranging
@@ -467,7 +485,7 @@ The format of the layout file is described in [Creating a touch keyboard layout
Underneath the keyboard area is the Long-press, Flicks, and Multitaps controls.
-
+
Red circle with an X
: This button to the top right of the key will delete the key from the
@@ -494,7 +512,7 @@ the selected key for different Gesture Type. The only difference between this co
## Icon tab
-
+
The icon tab allows you to edit the icon associated with the keyboard. An external icon editor will allow you to create more sophisticated icons with alpha transparency and multiple resolutions, but this will cover the standard 16x16 256 colour icon, which is all that many keyboards require. The controls at the top left are:
@@ -560,7 +578,7 @@ This tab allows you to edit the [named constants](/developer/language/guide/cons
## Build tab
-
+
A set of common controls is across the top of this tab:
diff --git a/developer/docs/help/context/package-editor.md b/developer/docs/help/context/package-editor.md
index 38026732bc..85335a2c1b 100644
--- a/developer/docs/help/context/package-editor.md
+++ b/developer/docs/help/context/package-editor.md
@@ -129,6 +129,12 @@ are optional.
A web site is encouraged and should be filled in the web site field, including
the initial "http://" or "https://".
+The Description is the keyboard summary that users will see on the keyboard’s
+overview page at the keyman.com web site.
+[GitHub style Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
+can be used in the description text. However, HTML tags are not supported.
+
+
A package can optionally include a 140x250 JPEG or PNG image file to be shown
during installation. This image file must be added to the Files list in step 1,
then selected from the list here.
@@ -191,4 +197,4 @@ keyboard to the [Keyman Cloud Keyboards Repository](/developer/keyboards/).
## Package Installers
As of Keyman Developer 17, bundled executable package installers for Keyman for
-Windows can be created using [kmc](kmc), but cannot be created within the IDE.
\ No newline at end of file
+Windows can be created using [kmc](kmc), but cannot be created within the IDE.
diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md
new file mode 100644
index 0000000000..b09d5919cf
--- /dev/null
+++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md
@@ -0,0 +1,26 @@
+---
+title: Using Gestures in Keyman Developer
+---
+
+Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for touch keyboard layout. To allow gestures onto your keyboard, go to the Touch layout tab and define the keys as follows:
+
+
+
+## Tips
+
+***Flicks** and **long presses** can both be used in a single keyboard. In this scenario, it is recommended to use flicks in only one direction—south (down). Long presses work well alongside this pattern. See, for example, [sil_euro_latin](https://help.keyman.com/keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout).
+ * Another helpful tip seen in the sil_euro_latin keyboard is using south-flicks on the base and shift layers corresponding to the numeric/symbol layer, this aids in both memorization and discovery.
+* **Flicks** in multiple directions are possible, but currently Keyman does not support hints for multiple directions, so they are not very discoverable.
+* **Long presses** are more discoverable, but **flicks** are faster. We suggest using south flicks along with long presses, and including the flick output in the long press for maximum discoverability and optimum ergonomics.
+
+## Caution
+
+* **Flicks** near screen edges tend to be problematic--once you drag off the screen, then Keyman can no longer tell that you are flicking (or you may end up activating system controls on the bottom edge):
+ * On the bottom row, south flicks are not recommended.
+ * On the top row, we have the banner space reserved in order to be able to support flicks and long presses up into that region of the screen.
+ * Left/right screen edges, horizontal flicks are not the best practice.
+
+## See also
+
+* The [Winchus keyboard](https://help.keyman.com/keyboard/winchus/1.4/winchus#guia-rapida-celular)
+* The [GFF Amharic keyboard](https://help.keyman.com/keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md
index bc7c8e792f..acfe3efcec 100644
--- a/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md
+++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md
@@ -38,17 +38,23 @@ ago) used the / key as the deadkey.
/d gave ɗ, and so on.
It would be possible to use that desktop keyboard logic on touch
-screens. But your mobile device users will find these rules annoying.
+screens. But your mobile device users will find these rules annoying.
+
Instead of two keys they would probably have to press four, since the
standard alphabetical keyboard on most touch devices does not have the
slash key. So they would have to press the key to switch to the numeric
-keyboard, then press slash, then press the key to go back to the
-alphabetical keyboard, then the letter. What touch screen users would
+keyboard, then press slash, then press the key to switch back to the
+alphabetical keyboard, and then the letter. What touch screen users would
appreciate is something that uses the great feature of touch screens,
the long press or press and hold on a letter, to see analogous letters.
-So we’ll set up a touch screen keyboard that lets you press and hold ‘d’
-to see the two hooked d characters, press and hold ‘b’ to see the hooked
-b characters, and so on.
+
+So we’ll set up a touch screen keyboard that lets you
+* press and hold ‘d’ to see the two hooked d characters
+* press and hold ‘b’ to see the hooked b characters, and so on.
[Next: Making, testing and distributing the touch
keyboard](making-touch-keyboard)
+
+## See also
+
+* Tips for using [gestures](gestures) in Keyman Developer
diff --git a/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg b/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg
new file mode 100644
index 0000000000..1d407cc22a
Binary files /dev/null and b/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg differ
diff --git a/developer/docs/help/images/ui/OnScreenKeyboard_ISO.png b/developer/docs/help/images/ui/OnScreenKeyboard_ISO.png
new file mode 100644
index 0000000000..64e3f6ef7e
Binary files /dev/null and b/developer/docs/help/images/ui/OnScreenKeyboard_ISO.png differ
diff --git a/developer/docs/help/main/credits.md b/developer/docs/help/main/credits.md
index f2c8a027c5..a4c2124870 100644
--- a/developer/docs/help/main/credits.md
+++ b/developer/docs/help/main/credits.md
@@ -2,6 +2,6 @@
title: Additional Credits
---
-## Code2000, Code2001 and Code2002 fonts
+## Code2000, Code2001 and Code2002 fonts
© 2006 James Kass.
diff --git a/developer/docs/help/main/license.md b/developer/docs/help/main/license.md
index 73b788f900..83f45978e9 100644
--- a/developer/docs/help/main/license.md
+++ b/developer/docs/help/main/license.md
@@ -4,7 +4,7 @@ title: Keyman® Developer License
MIT License
-Copyright (c) 1994-2018 SIL International
+Copyright (c) 1994-2025 SIL Global
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
diff --git a/developer/docs/internal/developer-messaging.md b/developer/docs/internal/developer-messaging.md
index d7fe3bb9d4..453230a733 100644
--- a/developer/docs/internal/developer-messaging.md
+++ b/developer/docs/internal/developer-messaging.md
@@ -102,7 +102,7 @@ Example: keys.ts \- compiler \- call site for error
```js
static ERROR_InvalidScanCode = SevError | 0x0009;
-static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithMetadata) => mx(
+static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, compileContext:ObjectWithCompileContext) => mx(
this.ERROR_InvalidScanCode, context,
Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}',
`…additional markdown detail…`
@@ -126,7 +126,7 @@ The `mx()` function does the following (note that `m()` is called first).
```js
let evt = m(code, message, detail); // raw message
-evt = LdmlCompilerMessages.offset(evt, x); // with offset
+evt = LdmlCompilerMessages.offset(evt, compileContext); // with offset
```
Note: We could have: `ml(e:CompilerEvent, lineNumber?: number)` so that compilers with a line number could pass that in directly.
diff --git a/developer/src/build.sh b/developer/src/build.sh
index 6d9136443c..4d8846c9cc 100755
--- a/developer/src/build.sh
+++ b/developer/src/build.sh
@@ -67,7 +67,7 @@ builder_parse "$@"
#-------------------------------------------------------------------------------------------------------------------
-if [[ $BUILDER_OS == win ]]; then
+if builder_is_windows; then
source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh"
fi
diff --git a/developer/src/common/web/utils/src/compiler-interfaces.ts b/developer/src/common/web/utils/src/compiler-interfaces.ts
index 443121955b..d7081b815a 100644
--- a/developer/src/common/web/utils/src/compiler-interfaces.ts
+++ b/developer/src/common/web/utils/src/compiler-interfaces.ts
@@ -1,5 +1,5 @@
import { CompilerCallbacks } from "./compiler-callbacks.js";
-import { ObjectWithMetadata } from "./symbol-utils.js";
+import { ObjectWithCompileContext } from '@keymanapp/common-types';
import { KeymanXMLReader, XML_FILENAME_SYMBOL } from "./xml-utils.js";
/**
@@ -215,9 +215,9 @@ export class CompilerError {
* @param x any object parsed from XML or with the XML_META_DATA_SYMBOL symbol copied over
* @returns modified event object
*/
- public static setFromMetadata(event: CompilerEvent, x?: ObjectWithMetadata): CompilerEvent {
- if(x) {
- const metadata = KeymanXMLReader.getMetaData(x) || {};
+ public static setFromMetadata(event: CompilerEvent, compileContext?: ObjectWithCompileContext): CompilerEvent {
+ if (compileContext) {
+ const metadata = KeymanXMLReader.getMetaData(compileContext) || {};
const offset = metadata?.startIndex;
if (offset) {
event.offset = offset;
@@ -462,8 +462,8 @@ export function dedentCompilerMessageDetail(event: CompilerEvent) {
* // it contains redundant info.
* // 2. No code execution within the arrow function other than the 'mx' call, string interpolation,
* // with `${def(o.property)}` as the max complexity of interpolation.
- * static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithMetadata) => mx(
- * this.ERROR_InvalidScanCode, x,
+ * static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, compileContext: ObjectWithCompileContext) => mx(
+ * this.ERROR_InvalidScanCode, compileContext,
* `Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`,
* // Note: If detail is omitted, leave the trailing comma on the prior line to leave room for it
* `…additional markdown detail…`
@@ -478,7 +478,7 @@ export function dedentCompilerMessageDetail(event: CompilerEvent) {
* @see CompilerMessageSpec
* @returns the event
*/
-export function CompilerMessageObjectSpec(code: number, context: ObjectWithMetadata, message: string, detail?: string): CompilerEvent {
+export function CompilerMessageObjectSpec(code: number, context: ObjectWithCompileContext, message: string, detail?: string): CompilerEvent {
let evt = CompilerMessageSpec(code, message, detail); // constructs raw message
evt = CompilerError.setFromMetadata(evt, context); // updates with offset from context
return evt;
diff --git a/developer/src/common/web/utils/src/developer-utils-messages.ts b/developer/src/common/web/utils/src/developer-utils-messages.ts
index 6fd328802b..917af99e70 100644
--- a/developer/src/common/web/utils/src/developer-utils-messages.ts
+++ b/developer/src/common/web/utils/src/developer-utils-messages.ts
@@ -1,8 +1,9 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*/
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m } from './compiler-interfaces.js';
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx } from './compiler-interfaces.js';
import { constants } from '@keymanapp/ldml-keyboard-constants';
+import { ObjectWithCompileContext } from '@keymanapp/common-types';
const DeveloperUtilsErrMask = CompilerErrorNamespace.DeveloperUtils;
// const SevInfo = CompilerErrorSeverity.Info | DeveloperUtilsErrMask;
@@ -14,8 +15,10 @@ const SevError = CompilerErrorSeverity.Error | DeveloperUtilsErrMask;
export class DeveloperUtilsMessages {
// structured Ajv validation error
static ERROR_SchemaValidationError = SevError | 0x0001;
- static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}) => m(this.ERROR_SchemaValidationError,
- `Error validating LDML XML file: ${def(o.instancePath)}: ${def(o.keyword)}: ${def(o.message)} ${def(o.params)}`);
+ static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}, compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_SchemaValidationError, compileContext,
+ `Error validating LDML XML file: ${def(o.instancePath)}: ${def(o.keyword)}: ${def(o.message)} ${def(o.params)}`,
+ );
static ERROR_ImportInvalidBase = SevError | 0x0002;
static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) =>
diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts
index 00dc6d128d..546f6eda0c 100644
--- a/developer/src/common/web/utils/src/index.ts
+++ b/developer/src/common/web/utils/src/index.ts
@@ -70,8 +70,8 @@ export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.j
export { DeveloperUtilsMessages } from './developer-utils-messages.js';
export * as SourceFilenamePatterns from './source-filename-patterns.js';
-export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL } from './xml-utils.js';
-export { SymbolUtils, ObjectWithMetadata } from './symbol-utils.js';
+export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL, withOffset } from './xml-utils.js';
+export { SymbolUtils } from './symbol-utils.js';
export * as LineUtils from './line-utils.js';
export * as GitHubUrls from './github-urls.js';
export * as CloudUrls from './cloud-urls.js';
diff --git a/developer/src/common/web/utils/src/symbol-utils.ts b/developer/src/common/web/utils/src/symbol-utils.ts
index 9404b404d8..a340344c9c 100644
--- a/developer/src/common/web/utils/src/symbol-utils.ts
+++ b/developer/src/common/web/utils/src/symbol-utils.ts
@@ -6,13 +6,7 @@
* Utilities for manipulating Symbol properties
*/
-
-/**
- * Any object with metadata, for example for line number errs.
- * Defined as 'any' here to reduce noise on the client side.
- * @see {@link KeymanXMLReader.getMetaData()}
- */
-export type ObjectWithMetadata = any;
+import { ObjectWithCompileContext } from '@keymanapp/common-types';
export class SymbolUtils {
/**
@@ -21,8 +15,8 @@ export class SymbolUtils {
* @param from source for symbols
* @returns the onto object
*/
- public static copySymbols(onto: T, from: ObjectWithMetadata): T {
- const o = onto as ObjectWithMetadata;
+ public static copySymbols(onto: T, from: ObjectWithCompileContext): T {
+ const o = onto as ObjectWithCompileContext;
for (const sym of Object.getOwnPropertySymbols(from)) {
o[sym] = from[sym];
}
diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts
index 0a31bc051b..f25b03582d 100644
--- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts
+++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts
@@ -12,7 +12,7 @@ import { constants } from '@keymanapp/ldml-keyboard-constants';
import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js';
import boxXmlArray = util.boxXmlArray;
import { LineFinderEventResolver } from '../../line-utils.js';
-import { XML_FILENAME_SYMBOL, KeymanXMLReader } from '../../xml-utils.js';
+import { XML_FILENAME_SYMBOL, KeymanXMLReader, findInstanceObject } from '../../xml-utils.js';
interface NameAndProps {
'$'?: any; // content
@@ -310,12 +310,13 @@ export class LDMLKeyboardXMLSourceFileReader implements EventResolver {
public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile): boolean {
if(!SchemaValidators.default.ldmlKeyboard3(source)) {
for (const err of (SchemaValidators.default.ldmlKeyboard3).errors) {
+ const context = findInstanceObject(source, err?.instancePath?.split('/'));
this.callbacks.reportMessage(DeveloperUtilsMessages.Error_SchemaValidationError({
instancePath: err.instancePath,
keyword: err.keyword,
message: err.message || 'Unknown AJV Error', // docs say 'message' is optional if 'messages:false' in options
params: Object.entries(err.params || {}).sort().map(([k,v])=>`${k}="${v}"`).join(' '),
- }));
+ }, context));
}
return false;
}
diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts
index 1fa186072f..8222e72b68 100644
--- a/developer/src/common/web/utils/src/xml-utils.ts
+++ b/developer/src/common/web/utils/src/xml-utils.ts
@@ -412,7 +412,6 @@ export class KeymanXMLWriter {
}
}
-
/**
* traverse an AJV instancePath and map to an object if possible
* @param source object tree root (contains the root object)
@@ -435,6 +434,7 @@ export function findInstanceObject(source: any, path: string[]) : any {
return null;
}
}
+
/**
* Return an object simulating an XML object with an offset number
* For use in calling message functions
@@ -448,4 +448,4 @@ export function withOffset(c: number, compileContext?: any) : KeymanXMLMetadata
startIndex: c
});
return o;
-}
\ No newline at end of file
+}
diff --git a/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts b/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts
index d09c1dfc41..286edd7958 100644
--- a/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts
+++ b/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts
@@ -8,7 +8,7 @@ import { testReaderCases } from '../helpers/reader-callback-test.js';
import CLDRScanToVkey = Constants.CLDRScanToVkey;
import CLDRScanToKeyMap = Constants.CLDRScanToKeyMap;
import USVirtualKeyCodes = Constants.USVirtualKeyCodes;
-import { KeymanXMLReader, XML_FILENAME_SYMBOL } from '../../src/xml-utils.js';
+import { KeymanXMLReader, withOffset, XML_FILENAME_SYMBOL } from '../../src/xml-utils.js';
function pluckKeysFromKeybag(keys: LKKey[], ids: string[]) {
return keys.filter(({id}) => ids.indexOf(id) !== -1);
@@ -25,7 +25,7 @@ describe('ldml keyboard xml reader tests', function () {
keyword: 'required',
message: `must have required property 'info'`,
params: 'missingProperty="info"',
- })],
+ }, withOffset(39))],
},
{
subpath: 'invalid-conforms-to.xml',
@@ -34,7 +34,7 @@ describe('ldml keyboard xml reader tests', function () {
keyword: 'enum',
message: `must be equal to one of the allowed values`,
params: 'allowedValues="45,46"', // this has to be kept in sync with the DTD
- })],
+ }, withOffset(39))],
},
{
subpath: 'import-minimal.xml',
diff --git a/developer/src/common/web/utils/test/xml-utils.tests.ts b/developer/src/common/web/utils/test/xml-utils.tests.ts
index 4b3df736b6..ec4f1ae80c 100644
--- a/developer/src/common/web/utils/test/xml-utils.tests.ts
+++ b/developer/src/common/web/utils/test/xml-utils.tests.ts
@@ -12,7 +12,7 @@ import { env } from 'node:process';
import { readFileSync, writeFileSync } from 'node:fs';
-import { KeymanXMLType, KeymanXMLReader, KeymanXMLWriter } from '../src/xml-utils.js';
+import { KeymanXMLType, KeymanXMLReader, KeymanXMLWriter, findInstanceObject } from '../src/xml-utils.js';
import { LineFinder } from '../src/line-utils.js';
import { makePathToFixture } from './helpers/index.js';
import { SymbolUtils } from '../src/symbol-utils.js';
@@ -198,5 +198,36 @@ describe(`XML Reader line number test`, () => {
LineFinder.offsetToLineColumn(
getMetaData(actual.keyboard3.transforms).startIndex, lines), { line: 8, column: 2 });
});
+ describe('findInstanceObject test', () => {
+ const path0 = '/keyboard3/layers/0';
+ const TARGET = Symbol("Looking for this!");
+ it(`Should be able to parse ${path0}`, () => {
+ const o = {
+ keyboard3: {
+ layers: [
+ TARGET,
+ ]
+ }
+ };
+ assert.equal(findInstanceObject(o, path0.split('/')), TARGET);
+ });
+ // path to property
+ const path1 = '/keyboard3/conformsTo';
+ it(`Should be able to parse ${path1}`, () => {
+ const keyboard3 = { conformsTo: "1234"};
+ const o = {
+ keyboard3,
+ };
+ assert.equal(findInstanceObject(o, path1.split('/')), keyboard3);
+ });
+ const path2 = '/keyboard3/bad/path';
+ it(`Should be able to handle ${path2}`, () => {
+ const keyboard3 = { conformsTo: "1234"};
+ const o = {
+ keyboard3,
+ };
+ assert.equal(findInstanceObject(o, path2.split('/')), undefined);
+ });
+ });
});
diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh
index 8f5b722506..504811eb4b 100755
--- a/developer/src/inst/build.sh
+++ b/developer/src/inst/build.sh
@@ -20,9 +20,9 @@ builder_describe_outputs \
builder_parse "$@"
-. "$KEYMAN_ROOT/resources/build/win/environment.inc.sh"
-. "$KEYMAN_ROOT/resources/build/win/wix.inc.sh"
-. "$KEYMAN_ROOT/resources/build/win/zip.inc.sh"
+. "${KEYMAN_ROOT}/resources/build/win/environment.inc.sh"
+. "${KEYMAN_ROOT}/resources/build/win/wix.inc.sh"
+. "${KEYMAN_ROOT}/resources/build/zip.inc.sh"
# In dev environments, we'll hack the tier to alpha; CI sets this for us in real builds.
if [[ -z ${KEYMAN_TIER+x} ]]; then
@@ -51,7 +51,7 @@ function do_clean() {
#-------------------------------------------------------------------------------------------------------------------
function do_publish() {
- verify-program-signatures
+ builder_if_release_build_level verify-program-signatures
verify-node-installer-version
"$KEYMAN_ROOT/common/windows/cef-checkout.sh"
@@ -80,7 +80,7 @@ function do_publish() {
copy-kmdev
- verify-installer-signatures
+ builder_if_release_build_level verify-installer-signatures
}
function do_test() {
@@ -233,7 +233,7 @@ function make-installer() {
echo "Version=$KEYMAN_VERSION" >> setup.inf
echo "MSIFileName=keymandeveloper.msi" >> setup.inf
echo "Title=Keyman Developer ${KEYMAN_VERSION_WITH_TAG}" >>setup.inf
- wzzip setup.zip keymandeveloper.msi setup.inf
+ add_zip_files setup.zip keymandeveloper.msi setup.inf
cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION.exe"
wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION.exe"
@@ -242,27 +242,24 @@ function make-installer() {
#
}
-# TODO: rename this to keyman-developer-cli-$Version.zip
-KMC_ZIP="$DEVELOPER_ROOT/release/$KEYMAN_VERSION/kmcomp-$KEYMAN_VERSION.zip"
-
function make-kmc-install-zip() {
builder_heading make-kmc-install-zip
copy-schemas
- cd "$DEVELOPER_ROOT/bin"
+ (
+ # shellcheck disable=2164
+ cd "${DEVELOPER_ROOT}/bin"
- wzzip -bd -bb0 "$KMC_ZIP" \
- kmconvert.exe \
- sentry.dll sentry.x64.dll \
- kmdecomp.exe \
- keyboard_info.schema.json \
- kmp.schema.json \
- keyman-touch-layout.spec.json keyman-touch-layout.clean.spec.json \
- xml/layoutbuilder/*.keyman-touch-layout \
- projects/* \
- server/*
+ # TODO: rename this to keyman-developer-cli-$Version.zip
+ local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION}.zip"
- cd "$THIS_SCRIPT_PATH"
+ add_zip_files "${KMCOMP_ZIP}" -q -r \
+ kmconvert.exe \
+ keyboard_info.schema.json \
+ xml/layoutbuilder/*.keyman-touch-layout \
+ projects/ \
+ server/
+ )
}
# TODO: are these required?
diff --git a/developer/src/kmanalyze/build.sh b/developer/src/kmanalyze/build.sh
index 0e07044ffc..ecd9fd263f 100755
--- a/developer/src/kmanalyze/build.sh
+++ b/developer/src/kmanalyze/build.sh
@@ -32,7 +32,7 @@ function do_build() {
build_version.res
vs_msbuild kmanalyze.vcxproj //t:Build "//p:Platform=Win32"
cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM"
- cp "$WIN32_TARGET_PATH/kmanalyze.pdb" "$DEVELOPER_DEBUGPATH"
+ builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmanalyze.pdb" "$DEVELOPER_DEBUGPATH"
}
function do_publish() {
diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts
index 9370d27c08..906be3538f 100644
--- a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts
+++ b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts
@@ -28,7 +28,7 @@ export class KmnFileWriter {
let data: string = "\n";
// add top part of kmn file: STORES
- data += this.writeData_Stores(data_ukelele);
+ data += this.write_KmnFileHeader(data_ukelele);
// add bottom part of kmn file: RULES
data += this.writeData_Rules(data_ukelele);
@@ -46,7 +46,7 @@ export class KmnFileWriter {
let data: string = "\n";
// add top part of kmn file: STORES
- data += this.writeData_Stores(data_ukelele);
+ data += this.write_KmnFileHeader(data_ukelele);
// add bottom part of kmn file: RULES
data += this.writeData_Rules(data_ukelele);
@@ -64,7 +64,7 @@ export class KmnFileWriter {
* @param data_ukelele an object containing all data read from a .keylayout file
* @return string - all stores to be printed
*/
- public writeData_Stores(data_ukelele: convert_object): string {
+ public write_KmnFileHeader(data_ukelele: convert_object): string {
let data: string = "";
@@ -97,6 +97,9 @@ export class KmnFileWriter {
let data: string = "";
// filter array of all rules and remove duplicates
+ // during the process of creating Rule[], duplicate rules might occur
+ // (e.g. when in a keylayout file the same modifiers occur in several behaviors thus producing the same rules).
+ // This is to filter out those duplicate Rule objects
const unique_data_Rules: Rule[] = data_ukelele.arrayOf_Rules.filter((curr) => {
return (!(curr.output === new TextEncoder().encode("") || curr.output === undefined)
&& (curr.key !== "")
@@ -377,7 +380,8 @@ export class KmnFileWriter {
/**
* @brief member function to review rules for acceptable modifiers, duplicate or ambiguous rules and return an array containing possible warnings.
- * Definition of comparisons e.g. 1-1, 2-4, 6-6
+ * Keyman can not handle duplicate rules so we need to make sure a rule is written only once by either omitting a duplicate rule or commenting out an ambiguous rule.
+ * Omitting rules and definition of comparisons e.g. 1-1, 2-4, 6-6
* see https://docs.google.com/document/d/12J3NGO6RxIthCpZDTR8FYSRjiMgXJDLwPY2z9xqKzJ0/edit?tab=t.0#heading=h.pcz8rjyrl5ug
* @param rule : Rule[] - an array of all rules
* @param index the index of a rule in array[rule]
diff --git a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts
index 64dc832448..5f47448c33 100644
--- a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts
+++ b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts
@@ -113,7 +113,7 @@ describe('KmnFileWriter', function () {
});
- describe("writeData_Stores() ", function () {
+ describe("write_KmnFileHeader() ", function () {
const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions);
const sut_r = new KeylayoutFileReader(compilerTestCallbacks);
const sut_w = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions);
@@ -148,18 +148,18 @@ describe('KmnFileWriter', function () {
+ "group(main) using keys\n\n"
+ "\n";
- it(('writeData_Stores should return store text with filename ').padEnd(62, " ") + 'on correct input', async function () {
- const written_correctName = sut_w.writeData_Stores(converted);
+ it(('write_KmnFileHeader should return store text with filename ').padEnd(62, " ") + 'on correct input', async function () {
+ const written_correctName = sut_w.write_KmnFileHeader(converted);
assert.equal(written_correctName, (out_expected_first + converted.keylayout_filename + out_expected_last));
});
- it(('writeData_Stores should return store text without filename ').padEnd(62, " ") + 'on empty input', async function () {
- const written_emptyName = sut_w.writeData_Stores(converted_empty);
+ it(('write_KmnFileHeader should return store text without filename ').padEnd(62, " ") + 'on empty input', async function () {
+ const written_emptyName = sut_w.write_KmnFileHeader(converted_empty);
assert.equal(written_emptyName, (out_expected_first + out_expected_last));
});
- it(('writeData_Stores should return store text without filename ').padEnd(62, " ") + 'on only filename as input', async function () {
- const written_onlyName = sut_w.writeData_Stores(converted_unavailable);
+ it(('write_KmnFileHeader should return store text without filename ').padEnd(62, " ") + 'on only filename as input', async function () {
+ const written_onlyName = sut_w.write_KmnFileHeader(converted_unavailable);
assert.equal(written_onlyName, (out_expected_first + converted_unavailable.keylayout_filename + out_expected_last));
});
});
diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts
index 21985ad09a..403f721f90 100644
--- a/developer/src/kmc-kmn/src/compiler/compiler.ts
+++ b/developer/src/kmc-kmn/src/compiler/compiler.ts
@@ -6,7 +6,7 @@ TODO: implement additional interfaces:
*/
// TODO: rename wasm-host?
-import { VisualKeyboard, KvkFileReader, LdmlKeyboardTypes, KeymanFileTypes, KvkFileWriter } from '@keymanapp/common-types';
+import { VisualKeyboard, KvkFileReader, LdmlKeyboardTypes, KeymanFileTypes, KvkFileWriter, ObjectWithCompileContext } from '@keymanapp/common-types';
import {
CompilerCallbacks, CompilerEvent, CompilerOptions, KeymanCompiler, KeymanCompilerArtifacts,
KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact, KvksFileReader,
@@ -530,7 +530,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
* @param rangeCount - number of ranges to allocate
* @returns UnicodeSet accessor object, or null on failure
*/
- public parseUnicodeSet(pattern: string, rangeCount: number, x?: any) : LdmlKeyboardTypes.UnicodeSet | null {
+ public parseUnicodeSet(pattern: string, rangeCount: number, compileContext?: any) : LdmlKeyboardTypes.UnicodeSet | null {
if(!this.verifyInitialized()) {
/* c8 ignore next 2 */
// verifyInitialized will set a callback if needed
@@ -563,7 +563,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
// rc is negative: it's an error code.
this.wasmExports.free(buf);
// translate error code into callback
- this.callbacks.reportMessage(getUnicodeSetError(rc, x));
+ this.callbacks.reportMessage(getUnicodeSetError(rc, compileContext));
return null;
}
}
@@ -571,7 +571,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
/**
* @internal
*/
- public sizeUnicodeSet(pattern: string, x?: any) : number {
+ public sizeUnicodeSet(pattern: string, compileContext?: any) : number {
if(!this.verifyInitialized()) {
/* c8 ignore next 2 */
return null;
@@ -583,7 +583,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
if (rc >= 0) {
return rc;
} else {
- this.callbacks.reportMessage(getUnicodeSetError(rc, x));
+ this.callbacks.reportMessage(getUnicodeSetError(rc, compileContext));
return -1;
}
}
@@ -594,7 +594,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
* @param rc parseUnicodeSet error code
* @returns the compiler event
*/
-function getUnicodeSetError(rc: number, x?: any) : CompilerEvent {
+function getUnicodeSetError(rc: number, compileContext?: ObjectWithCompileContext) : CompilerEvent {
// from kmcmplib.h
const KMCMP_ERROR_SYNTAX_ERR = -1;
const KMCMP_ERROR_HAS_STRINGS = -2;
@@ -602,16 +602,16 @@ function getUnicodeSetError(rc: number, x?: any) : CompilerEvent {
const KMCMP_FATAL_OUT_OF_RANGE = -4;
switch(rc) {
case KMCMP_ERROR_SYNTAX_ERR:
- return KmnCompilerMessages.Error_UnicodeSetSyntaxError(x);
+ return KmnCompilerMessages.Error_UnicodeSetSyntaxError(compileContext);
case KMCMP_ERROR_HAS_STRINGS:
- return KmnCompilerMessages.Error_UnicodeSetHasStrings(x);
+ return KmnCompilerMessages.Error_UnicodeSetHasStrings(compileContext);
case KMCMP_ERROR_UNSUPPORTED_PROPERTY:
- return KmnCompilerMessages.Error_UnicodeSetHasProperties(x);
+ return KmnCompilerMessages.Error_UnicodeSetHasProperties(compileContext);
case KMCMP_FATAL_OUT_OF_RANGE:
- return KmnCompilerMessages.Fatal_UnicodeSetOutOfRange(x);
+ return KmnCompilerMessages.Fatal_UnicodeSetOutOfRange(compileContext);
default:
/* c8 ignore next */
- return CompilerError.setFromMetadata(KmnCompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}), x);
+ return CompilerError.setFromMetadata(KmnCompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}), compileContext);
}
}
diff --git a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts
index 3274a62ca1..46f573f253 100644
--- a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts
+++ b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts
@@ -1,7 +1,7 @@
import { KeyAddress } from "../kmw-compiler/validate-layout-file.js";
import { kmnfile } from "../kmw-compiler/compiler-globals.js";
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls, ObjectWithMetadata } from "@keymanapp/developer-utils";
-
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls } from "@keymanapp/developer-utils";
+import { ObjectWithCompileContext } from "@keymanapp/common-types";
const Namespace = CompilerErrorNamespace.KmnCompiler;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
const SevHint = CompilerErrorSeverity.Hint | Namespace;
@@ -107,8 +107,8 @@ export class KmnCompilerMessages {
);
static FATAL_UnicodeSetOutOfRange = SevFatal | 0x904;
- static Fatal_UnicodeSetOutOfRange = (x?: ObjectWithMetadata) => mx(
- this.FATAL_UnicodeSetOutOfRange, x,
+ static Fatal_UnicodeSetOutOfRange = (compileContext?: ObjectWithCompileContext) => mx(
+ this.FATAL_UnicodeSetOutOfRange, compileContext,
`UnicodeSet buffer was too small`,
`Raised when caller to UnicodeSet functions provides an invalid buffer. If
you experience this error, it should be reported to the Keyman team for
@@ -118,8 +118,8 @@ export class KmnCompilerMessages {
// TODO: rename the following functions to Error_UsetHasStrings etc
static ERROR_UnicodeSetHasStrings = SevError | 0x905;
- static Error_UnicodeSetHasStrings = (x?: ObjectWithMetadata) => mx(
- this.ERROR_UnicodeSetHasStrings, x,
+ static Error_UnicodeSetHasStrings = (compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_UnicodeSetHasStrings, compileContext,
`uset contains strings, not allowed`,
`The provided uset uses multi-character strings, (\`{}\` notation, e.g.
\`[żġħ{ie}{għ}]\`. ). Although full UnicodeSets support strings, LDML
@@ -130,8 +130,8 @@ export class KmnCompilerMessages {
);
static ERROR_UnicodeSetHasProperties = SevError | 0x906;
- static Error_UnicodeSetHasProperties = (x?: ObjectWithMetadata) => mx(
- this.ERROR_UnicodeSetHasProperties, x,
+ static Error_UnicodeSetHasProperties = (compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_UnicodeSetHasProperties, compileContext,
`uset contains properties, not allowed`,
`The provided uset uses property notation (\`\\p{…}\` or \`[:…:]\`). LDML
keyboards do not support Unicode properties in usets, because that would
@@ -142,8 +142,8 @@ export class KmnCompilerMessages {
);
static ERROR_UnicodeSetSyntaxError = SevError | 0x907;
- static Error_UnicodeSetSyntaxError = (x?: ObjectWithMetadata) => mx(
- this.ERROR_UnicodeSetSyntaxError, x,
+ static Error_UnicodeSetSyntaxError = (compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_UnicodeSetSyntaxError, compileContext,
`uset had a Syntax Error while parsing`,
`The provided uset has a syntax error and could not be parsed. Verify the
format of the uset against the specification.
diff --git a/developer/src/kmc-ldml/src/compiler/disp.ts b/developer/src/kmc-ldml/src/compiler/disp.ts
index fd15c8ace7..b38ef72531 100644
--- a/developer/src/kmc-ldml/src/compiler/disp.ts
+++ b/developer/src/kmc-ldml/src/compiler/disp.ts
@@ -62,7 +62,7 @@ export class DispCompiler extends SectionCompiler {
const result = new Disp();
// displayOptions
- result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, { unescape: true, x: this.keyboard3?.displays?.displayOptions });
+ result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, { unescape: true, compileContext:this.keyboard3?.displays?.displayOptions });
// displays
result.disps = this.keyboard3.displays?.display.map(display => ({
@@ -70,13 +70,13 @@ export class DispCompiler extends SectionCompiler {
stringVariables: true,
markers: true,
unescape: true,
- x: display,
+ compileContext: display,
}, sections),
- id: sections.strs.allocString(display.keyId, { x: display }), // not escaped, not substituted
+ id: sections.strs.allocString(display.keyId, { compileContext: display }), // not escaped, not substituted
display: sections.strs.allocString(display.display, {
stringVariables: true,
unescape: true,
- x: display,
+ compileContext: display,
}, sections),
})) || []; // TODO-LDML: need coverage for the []
diff --git a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
index 79015dca6b..a88e861f58 100644
--- a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
@@ -1,7 +1,8 @@
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
import { SectionCompiler } from "./section-compiler.js";
import { util, KMXPlus, LdmlKeyboardTypes } from "@keymanapp/common-types";
-import { CompilerCallbacks, LDMLKeyboard, ObjectWithMetadata } from "@keymanapp/developer-utils";
+import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils";
+import { ObjectWithCompileContext } from "@keymanapp/common-types";
import { VarsCompiler } from './vars.js';
import { LdmlCompilerMessages } from './ldml-compiler-messages.js';
@@ -34,7 +35,7 @@ export class StrsCompiler extends EmptyCompiler {
const strs = section;
/** attempt to find a context object for the string */
- function findContextForString(s: string): ObjectWithMetadata {
+ function findContextForString(s: string): ObjectWithCompileContext {
// try exact match
for(const str of strs.strings) {
if (str.value == s) return str.context;
diff --git a/developer/src/kmc-ldml/src/compiler/keys.ts b/developer/src/kmc-ldml/src/compiler/keys.ts
index 3c890a5067..8656e7fbf2 100644
--- a/developer/src/kmc-ldml/src/compiler/keys.ts
+++ b/developer/src/kmc-ldml/src/compiler/keys.ts
@@ -324,16 +324,16 @@ export class KeysCompiler extends SectionCompiler {
// allocate the in-memory
const flicks: KeysFlicks = new KeysFlicks(
- sections.strs.allocString(flickId, { x: flick })
+ sections.strs.allocString(flickId, { compileContext: flick })
);
// add data from each segment
for (const flickSegment of flick.flickSegment) {
const { keyId, directions } = flickSegment;
- const keyIdStr = sections.strs.allocString(keyId, { x: flickSegment });
+ const keyIdStr = sections.strs.allocString(keyId, { compileContext: flickSegment });
const directionsList: ListItem = sections.list.allocListFromSpaces(
directions,
- { x: flickSegment },
+ { compileContext: flickSegment },
sections);
flicks.flicks.push({
directions: directionsList,
@@ -370,20 +370,20 @@ export class KeysCompiler extends SectionCompiler {
if (!!gap) {
flags |= constants.keys_key_flags_gap;
}
- const id = sections.strs.allocString(key.id, { x: key });
+ const id = sections.strs.allocString(key.id, { compileContext: key });
const longPress: ListItem = sections.list.allocListFromSpaces(
- longPressKeyIds, { x: key },
+ longPressKeyIds, { compileContext: key },
sections);
const longPressDefault = sections.strs.allocString(longPressDefaultKeyId,
- { x: key },
+ { compileContext: key },
sections);
const multiTap: ListItem = sections.list.allocListFromSpaces(
multiTapKeyIds,
- { x: key },
+ { compileContext: key },
sections);
- const keySwitch = sections.strs.allocString(layerId, { x: key }); // 'switch' is a reserved word
+ const keySwitch = sections.strs.allocString(layerId, { compileContext: key }); // 'switch' is a reserved word
const toRaw = output;
@@ -396,7 +396,7 @@ export class KeysCompiler extends SectionCompiler {
unescape: true,
singleOk: true,
nfd: true,
- x: key,
+ compileContext: key,
},
sections);
if (!to.isOneChar) {
diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts
index 9dff22a5bf..baaf9282b5 100644
--- a/developer/src/kmc-ldml/src/compiler/layr.ts
+++ b/developer/src/kmc-ldml/src/compiler/layr.ts
@@ -60,7 +60,7 @@ export class LayrCompiler extends SectionCompiler {
if (totalLayerCount === 0) { // TODO-LDML: does not validate touch layers yet
// no layers seen anywhere
valid = false;
- this.callbacks.reportMessage(LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(this.keyboard3?.layers[0]));
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(this.keyboard3));
}
return valid;
}
@@ -69,13 +69,13 @@ export class LayrCompiler extends SectionCompiler {
const sect = new Layr();
sect.lists = this.keyboard3.layers.map((layers) => {
- const hardware = sections.strs.allocString(layers.formId, {x:layers});
+ const hardware = sections.strs.allocString(layers.formId, {compileContext: layers});
// Already validated in validate
const layerEntries = [];
for (const layer of layers.layer) {
const rows = layer.row.map((row) => {
const erow: LayrRow = {
- keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id, { x: row })),
+ keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id, { compileContext: row })),
};
// include linenumber info for row
return SectionCompiler.copySymbols(erow, row);
@@ -84,7 +84,7 @@ export class LayrCompiler extends SectionCompiler {
// push a layer entry for each modifier set
for (const mod of mods) {
layerEntries.push({
- id: sections.strs.allocString(layer.id, {x:layer}),
+ id: sections.strs.allocString(layer.id, {compileContext: layer}),
mod,
rows,
});
diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
index f76e0d3b3a..1f879f475b 100644
--- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
+++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
@@ -1,4 +1,5 @@
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageObjectSpec as mx, CompilerMessageSpec as m, CompilerMessageDef as def, ObjectWithMetadata } from '@keymanapp/developer-utils';
+import { ObjectWithCompileContext } from "@keymanapp/common-types";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageObjectSpec as mx, CompilerMessageSpec as m, CompilerMessageDef as def } from '@keymanapp/developer-utils';
// const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler;
const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler;
const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler;
@@ -13,38 +14,38 @@ const SevErrorTransform = SevError | 0xF00;
*/
export class LdmlCompilerMessages {
static HINT_NormalizationDisabled = SevHint | 0x0001;
- static Hint_NormalizationDisabled = (x?: ObjectWithMetadata) => mx(
- this.HINT_NormalizationDisabled, x,
+ static Hint_NormalizationDisabled = (compileContext?: ObjectWithCompileContext) => mx(
+ this.HINT_NormalizationDisabled, compileContext,
`normalization=disabled is not recommended.`,
);
static ERROR_InvalidLocale = SevError | 0x0002;
- static Error_InvalidLocale = (o:{tag: string}, x?: ObjectWithMetadata) => mx(
- this.ERROR_InvalidLocale, x,
+ static Error_InvalidLocale = (o:{tag: string}, compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_InvalidLocale, compileContext,
`Invalid BCP 47 locale form '${def(o.tag)}'`,
);
static ERROR_HardwareLayerHasTooManyRows = SevError | 0x0003;
- static Error_HardwareLayerHasTooManyRows = (x?: ObjectWithMetadata) => mx(
- this.ERROR_HardwareLayerHasTooManyRows, x,
+ static Error_HardwareLayerHasTooManyRows = (compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_HardwareLayerHasTooManyRows, compileContext,
`'hardware' layer has too many rows`,
);
static ERROR_RowOnHardwareLayerHasTooManyKeys = SevError | 0x0004;
- static Error_RowOnHardwareLayerHasTooManyKeys = (o: { row: number, hardware: string, modifiers: string }, x?: ObjectWithMetadata) => mx(
- this.ERROR_RowOnHardwareLayerHasTooManyKeys, x,
+ static Error_RowOnHardwareLayerHasTooManyKeys = (o: { row: number, hardware: string, modifiers: string }, compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_RowOnHardwareLayerHasTooManyKeys, compileContext,
`Row #${def(o.row)} on 'hardware' ${def(o.hardware)} layer for modifier ${def(o.modifiers)} has too many keys`,
);
static ERROR_KeyNotFoundInKeyBag = SevError | 0x0005;
- static Error_KeyNotFoundInKeyBag = (o: { keyId: string, col: number, row: number, layer: string, form: string }, x?: ObjectWithMetadata) => mx(
- this.ERROR_KeyNotFoundInKeyBag, x,
+ static Error_KeyNotFoundInKeyBag = (o: { keyId: string, col: number, row: number, layer: string, form: string }, compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_KeyNotFoundInKeyBag, compileContext,
`Key '${def(o.keyId)}' in position #${def(o.col)} on row #${def(o.row)} of layer ${def(o.layer)}, form '${def(o.form)}' not found in key bag`,
);
static HINT_OneOrMoreRepeatedLocales = SevHint | 0x0006;
- static Hint_OneOrMoreRepeatedLocales = (x?: ObjectWithMetadata) => mx(
- this.HINT_OneOrMoreRepeatedLocales, x,
+ static Hint_OneOrMoreRepeatedLocales = (compileContext?: ObjectWithCompileContext) => mx(
+ this.HINT_OneOrMoreRepeatedLocales, compileContext,
`After minimization, one or more locales is repeated and has been removed`,
);
@@ -54,38 +55,38 @@ export class LdmlCompilerMessages {
m(this.ERROR_InvalidFile, `The source file has an invalid structure: ${def(o.errorText)}`);
static HINT_LocaleIsNotMinimalAndClean = SevHint | 0x0008;
- static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}, x?: ObjectWithMetadata) => mx(
- this.HINT_LocaleIsNotMinimalAndClean, x,
+ static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}, compileContext?: ObjectWithCompileContext) => mx(
+ this.HINT_LocaleIsNotMinimalAndClean, compileContext,
`Locale '${def(o.sourceLocale)}' is not minimal or correctly formatted and should be '${def(o.locale)}'`,
);
static ERROR_InvalidScanCode = SevError | 0x0009;
- static Error_InvalidScanCode = (o: { codes: string, id: string }, x?: ObjectWithMetadata) => mx(
- this.ERROR_InvalidScanCode, x,
+ static Error_InvalidScanCode = (o: { codes: string, id: string }, compileContext?: ObjectWithCompileContext) => mx(
+ this.ERROR_InvalidScanCode, compileContext,
`Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`,
);
static WARN_CustomForm = SevWarn | 0x000A;
- static Warn_CustomForm = (o: { id: string }, x?: ObjectWithMetadata) => mx(
- this.WARN_CustomForm, x,
+ static Warn_CustomForm = (o: { id: string }, compileContext?: ObjectWithCompileContext) => mx(
+ this.WARN_CustomForm, compileContext,
`Custom