diff --git a/.github/workflows/api-verification.yml b/.github/workflows/api-verification.yml
index 786e8ebad2d..443adb471f3 100644
--- a/.github/workflows/api-verification.yml
+++ b/.github/workflows/api-verification.yml
@@ -67,6 +67,8 @@ jobs:
- name: "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }})"
run: |
+ echo "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }}):" >> $GITHUB_STEP_SUMMARY
+
BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb")
cd ${{ github.workspace }}/keyman/linux
./scripts/deb-packaging.sh \
@@ -93,19 +95,19 @@ jobs:
if: needs.api_verification.result == 'success'
run: |
echo "RESULT=success" >> $GITHUB_ENV
- echo "MSG=Package build succeeded" >> $GITHUB_ENV
+ echo "MSG=API verification succeeded" >> $GITHUB_ENV
- name: Set cancelled
if: needs.api_verification.result == 'cancelled'
run: |
echo "RESULT=error" >> $GITHUB_ENV
- echo "MSG=Package build cancelled" >> $GITHUB_ENV
+ echo "MSG=API verification cancelled" >> $GITHUB_ENV
- name: Set failure
if: needs.api_verification.result == 'failure'
run: |
echo "RESULT=failure" >> $GITHUB_ENV
- echo "MSG=Package build failed" >> $GITHUB_ENV
+ echo "MSG=API verification failed" >> $GITHUB_ENV
- name: Set final status
run: |
diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml
index 9c374f5d050..52c485aa658 100644
--- a/.github/workflows/deb-packaging.yml
+++ b/.github/workflows/deb-packaging.yml
@@ -116,7 +116,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- dist: [focal, jammy, noble]
+ dist: [focal, jammy, noble, oracular]
steps:
- name: Checkout
@@ -142,7 +142,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- dist: [oracular]
+ dist: [plucky]
steps:
- name: Checkout
diff --git a/.gitignore b/.gitignore
index 772784fdb8c..6c8fbfbec84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -183,3 +183,6 @@ lcov.info
/keyman*.buildinfo
/keyman*.changes
/keyman*.tar.?z
+
+# flag file for build script
+.configured
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4ebf41b87d1..6216a806143 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,5 +1,7 @@
# Contributing to Keyman
+(Keyman team members, see also the [onboarding](https://docs.google.com/document/d/1i6fBi9K38-LitcJZiRfAvRu1-7H0iQ_op5kxDMdhSec/edit?usp=sharing) doc)
+
⭐ Thank you for your contribution! ⭐
The following is a set of guidelines for contributing to Keyman, Keyman
diff --git a/HISTORY.md b/HISTORY.md
index 01762fcc2c9..d5ad1a589fd 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,215 @@
# Keyman Version History
+## 18.0.153 alpha 2024-12-05
+
+* feat(developer,common): verify normalization of strings (#12748)
+* chore(core): Add link to Keyman Glossary (#12774)
+* test(common/web/types): unit tests for file-types (#12716)
+
+## 18.0.152 alpha 2024-12-04
+
+* refactor(mac): pass kmx data blob to keyman core instead of file path (#12760)
+* fix(developer): honour provided script when checking for matching scripts (#12768)
+* chore(common): rename test files (#12709)
+* fix(common): rename test file (#12770)
+
+## 18.0.151 alpha 2024-12-03
+
+* feat(android): Enhance how ENTER key is handled for FV and KMSample2 (#12745)
+* feat(developer): report on mismatching lang tag scripts when building keyboard-info (#12753)
+
+## 18.0.150 alpha 2024-12-02
+
+* fix(core,developer): use `NDEBUG` flag to disable assertions in release build (#12715)
+
+## 18.0.149 alpha 2024-12-01
+
+* refactor(developer): unify test action (#12736)
+
+## 18.0.148 alpha 2024-11-29
+
+* test(common/web/types): unit tests for unicodeset-parser-api (#12714)
+* chore(developer): rename test files (#12707)
+* feat(core,linux,developer,windows): implement loading KMX from blob (#12721)
+* chore(common): add offline support for emscripten (#12740)
+
+## 18.0.147 alpha 2024-11-28
+
+* docs(android): Add android/docs/internal/README (#12717)
+* test(common/web/types): unit tests for string-list (#12702)
+* docs(common): linux and macOS emscripten setup (#12701)
+* refactor(developer): output number of tests when running on TC (#12710)
+* refactor(common): output number of tests when running on TC (#12719)
+* chore(web): rename file missed in #12704 (#12720)
+* fix(core): permanently disable logging (#12724)
+* fix(linux): disable assertions in release builds of ibus-keyman (#12725)
+* chore(common): improve offline builds (#12739)
+
+## 18.0.146 alpha 2024-11-27
+
+* test(developer): kmcmplib compiler unit tests 5 (#12612)
+* refactor(common): move all lexical model types into `LexicalModelTypes` container (#12712)
+* refactor(common): move remaining LDML keyboard types into `LdmlKeyboardTypes` (#12713)
+* chore(web): rename test files and folders (#12704)
+* chore(core): rename test files (#12705)
+* chore(linux): rename test files (#12706)
+
+## 18.0.145 alpha 2024-11-26
+
+* docs(windows): update emscripten bash setup (#12700)
+* chore(common): Add link to onboarding doc to `CONTRIBUTING.md` (#12697)
+
+## 18.0.144 alpha 2024-11-25
+
+* chore(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /developer/src/server/src/win32/trayicon/addon-src (#12687)
+* chore(developer): make package subfile description fully optional (#12665)
+* fix(developer): box package compiler info fields (#12666)
+* fix(developer): correct whitespace handling in virtual keys and remove partially implemented virtual key series in kmcmplib compiler (#12604)
+
+## 18.0.143 alpha 2024-11-22
+
+* chore(deps): bump cross-spawn from 7.0.3 to 7.0.6 (#12685)
+
+## 18.0.142 alpha 2024-11-20
+
+* chore(common): Update CODEOWNERS (#12680)
+
+## 18.0.141 alpha 2024-11-15
+
+* chore(linux): add support for Ubuntu 25.04 Plucky Puffin (#12675)
+
+## 18.0.140 alpha 2024-11-13
+
+* chore(common): Add 17.0.330 - 17.0.332 to version history (#12663)
+* fix(developer): reconnect `--full-test` in kmcmplib build and enable for CI (#12631)
+* docs(developer): kmc-generate (#12647)
+
+## 18.0.139 alpha 2024-11-12
+
+* fix(windows): help links updated (#12646)
+
+## 18.0.138 alpha 2024-11-08
+
+* fix(common): check for invalid markers (#12613)
+* chore: update minimum versions (#12632)
+* fix(windows): correct path to output file in publish step for fv keyboards (#12637)
+* chore(core): move API docs from help.keyman.com (#12642)
+* feat(developer): kmc generate (#11014)
+* feat(developer): kmc-copy (#12555)
+* feat(developer): add GitHub and Cloud support to kmc-copy (#12586)
+
+## 18.0.137 alpha 2024-11-07
+
+* fix(windows): correct engine help source path for upload (#12625)
+* fix(developer): use 'N' for nomatch store debug strings (regression in #12107) (#12629)
+* chore(developer): skip masaram_gondi in kmcmplib full test (#12630)
+* feat(developer): analyze osk-char-use merge with existing mapping file (#12622)
+* feat(developer): Report key 'address' in validation failures in layout compiler (#12588)
+
+## 18.0.136 alpha 2024-11-06
+
+* fix(developer): handle merge commits when checking git log date (#12627)
+
+## 18.0.135 alpha 2024-11-05
+
+* fix(developer): handle paste of TSV into Wordlist grid (#12594)
+* fix(developer): handle missing files in kmc-kmn (#12595)
+* fix(developer): handle missing files in kmc-model (#12596)
+* refactor(android): move Android Engine help in-repo (#12598)
+* refactor(ios): move ios Engine help in-repo (#12599)
+* refactor(windows): move windows Engine help in-repo (#12600)
+* refactor(web): move web engine help to app repo (#12601)
+* docs: Update websites readme with debug info (#12602)
+* fix(developer): create Server config directory before options save (#12608)
+* chore(linux): add support for Ubuntu 25.04 Plucky Puffin (#12614)
+* fix(linux): properly check for missing dependencies (#12615)
+* fix(linux): set environment variable for rendering of downloads dialog (#12616)
+
+## 18.0.134 alpha 2024-11-04
+
+* (#12606)
+
+## 18.0.133 alpha 2024-11-01
+
+* test(developer): kmcmplib compiler unit tests 4 (#12489)
+
+## 18.0.132 alpha 2024-10-30
+
+* feat(windows): kmdevlink app (#12552)
+
+## 18.0.131 alpha 2024-10-25
+
+* chore(android,windows): Update Crowdin strings for Khmer (#12574)
+* refactor(developer): add kps-file-reader and kps-file-writer (#12545)
+* fix(mac): make modifiers operational in OSK (#12556)
+* fix(mac): support missing alt layers in OSK (#12565)
+
+## 18.0.130 alpha 2024-10-24
+
+* fix(android/engine): Increase robustness when checking package kmp.json languages (#12567)
+* chore(ios): support xcode 16 build (#12570)
+
+## 18.0.129 alpha 2024-10-21
+
+* chore(mac): support Xcode 16 build (#12554)
+
+## 18.0.128 alpha 2024-10-14
+
+* docs(developer): add refs to Keyman MIME types (#12540)
+
+## 18.0.127 alpha 2024-10-12
+
+* fix(developer): use TextDecoder to convert Uint8Array to string (#12537)
+
+## 18.0.126 alpha 2024-10-11
+
+* test(common): add markdown link check test for product documentation (#12472)
+* chore(linux): improve output if `dpkg-gensymbols` fails and run other tests (#12527)
+
+## 18.0.125 alpha 2024-10-10
+
+* chore(common): allow to run `build.sh` scripts in `bashdb` debugger (#12518)
+* chore(linux): Improve output of API Verification (#12522)
+* chore(linux): allow to skip API change (#12519)
+* fix(linux): fix problem with API checks with merge commits (#12520)
+* refactor(web): move `KeyboardObject` type to `common/web/types` (#12514)
+
+## 18.0.124 alpha 2024-10-09
+
+* chore(common): fix links in minimum-versions.md (#12507)
+* fix(developer): use richedit in debug memo to support Egyptian cartouches (#12464)
+* feat(android): Add controls for auto-correct (#12443)
+
+## 18.0.123 alpha 2024-10-08
+
+* chore(developer,common): deps: replace xml2js with fast-xml-parser (#12502)
+* chore(ios): renew certificate (#12512)
+
+## 18.0.122 alpha 2024-10-07
+
+* feat(mac): both option keys generate right alt if no left alt mapping (#12458)
+* chore(common): improve configuration detection for hextobin (#12481)
+
+## 18.0.121 alpha 2024-10-03
+
+* docs(developer): Fix image links in help (#12488)
+* fix(oem/fv): Update keyboard versions and names for fv_all.kmp 13.1 (#12486)
+* feat(common): unified XML parser/writer (#12482)
+
+## 18.0.120 alpha 2024-10-02
+
+* chore(common): Add note on troubleshooting website errors (#12487)
+* docs(common): mention `KEYMAN_USE_NVM` in minimum versions doc (#12490)
+* docs(web): fix paths to several help pages (#12491)
+* docs(web): fix structure of test document (#12492)
+
+## 18.0.119 alpha 2024-09-28
+
+* refactor(developer): copy dev 17.0 help into repo (#12427)
+* fix(developer): warn before importing over touch layout (#12478)
+* chore(linux): display branch name with API verification (#12480)
+* docs(core): Update kmx-plus-file-format.md (#12479)
+
## 18.0.118 alpha 2024-09-26
* chore(developer): add context/options (#11566)
@@ -836,6 +1046,27 @@
* chore(common): move to 18.0 alpha (#10713)
* chore: move to 18.0 alpha
+## 17.0.332 stable 2024-11-06
+
+* fix(developer): create Server config directory before options save (#12609)
+* fix(developer): handle merge commits when checking git log date (#12628)
+* fix(linux): set environment variable for rendering of downloads dialog (#12617)
+
+## 17.0.331 stable 2024-10-30
+
+* fix(android): Hide suggestion banner on password fields (#12466)
+* fix(common): declare dep on @keymanapp/ldml-keyboard-constants (#12475)
+* fix(oem/fv): Update keyboard versions and names for fv_all.kmp (#12504)
+* chore(ios): renew certificate (#12513)
+* fix(developer): prevent invalid string ids (#12524)
+* fix(developer): ignore excess whitespace in `` attribute (#12523)
+
+## 17.0.330 stable 2024-09-16
+
+* refactor(android): Move Sentry and APK to publish task (#12392)
+* fix(developer): rewrite ldml visual keyboard compiler (#12406)
+* fix(developer): check vars string usage before definition (#12407)
+
## 17.0.329 stable 2024-09-09
* chore(android,ios): Add ojibwa ifinal/rdot keyboards to FirstVoices (#12020)
diff --git a/VERSION.md b/VERSION.md
index 49dcd615789..153289471d0 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-18.0.119
\ No newline at end of file
+18.0.154
\ No newline at end of file
diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
index 4fb1c567922..5ca3de5807f 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java
@@ -11,6 +11,7 @@
import com.keyman.engine.KMManager;
import com.keyman.engine.KMManager.KeyboardType;
import com.keyman.engine.KMHardwareKeyboardInterpreter;
+import com.keyman.engine.KMManager.SuggestionType;
import com.keyman.engine.KeyboardEventHandler.OnKeyboardEventListener;
import com.keyman.engine.R;
import com.keyman.engine.data.Keyboard;
@@ -169,8 +170,9 @@ public void onStartInput(EditorInfo attribute, boolean restarting) {
if (kbInfo != null) {
String langId = kbInfo.getLanguageID();
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
- boolean mayPredict = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(langId), true);
- KMManager.setBannerOptions(mayPredict);
+ int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(langId), KMManager.KMDefault_Suggestion);
+ // Enable banner if maySuggest is not SuggestionType.SUGGESTIONS_DISABLED (0)
+ KMManager.setBannerOptions(maySuggest != SuggestionType.SUGGESTIONS_DISABLED.toInt());
} else {
KMManager.setBannerOptions(false);
}
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java
index 75116bad8cc..977489f40a9 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguageSettingsActivity.java
@@ -16,9 +16,13 @@
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@@ -55,50 +59,6 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
private final static String TAG = "LanguageSettingsAct";
- private class PreferenceToggleListener implements View.OnClickListener {
- String prefsKey;
- String lgCode;
-
- public PreferenceToggleListener(String prefsKey, String lgCode) {
- this.prefsKey = prefsKey;
- this.lgCode = lgCode;
- }
-
- @Override
- public void onClick(View v) {
- // For predictions/corrections toggle
- SwitchCompat toggle = (SwitchCompat) v;
-
- SharedPreferences.Editor prefEditor = prefs.edit();
-
- // predictionsToggle overrides correctionToggle and correctionsTextView
- if (prefsKey.endsWith(KMManager.predictionPrefSuffix)) {
- boolean override = toggle.isChecked();
- overrideCorrectionsToggle(override);
- }
-
- // This will allow preemptively making settings for languages without models.
- // Seems more trouble than it's worth to block this.
- prefEditor.putBoolean(prefsKey, toggle.isChecked());
- prefEditor.apply();
-
- // Don't use/apply language modeling settings for languages without models.
- if (associatedLexicalModel.isEmpty()) {
- return;
- }
-
- Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(context);
- if(kbInfo != null) {
- // If the active keyboard is for this language, immediately enact the new pref setting.
- String kbdLgCode = kbInfo.getLanguageID();
- if (kbdLgCode.equals(lgCode)) {
- // Not only registers the model but also applies our modeling preferences.
- KMManager.registerAssociatedLexicalModel(lgCode);
- }
- }
- }
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -139,33 +99,48 @@ public void onCreate(Bundle savedInstanceState) {
FilteredKeyboardsAdapter adapter = new FilteredKeyboardsAdapter(context, KeyboardPickerActivity.getInstalledDataset(context), lgCode);
- // The following two layouts/toggles will need to link with these objects.
+ // The following radio group will need to link with these objects.
Context appContext = this.getApplicationContext();
prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
- boolean mayPredict = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(lgCode), true);
- boolean mayCorrect = prefs.getBoolean(KMManager.getLanguageCorrectionPreferenceKey(lgCode), true);
-
- RelativeLayout layout = (RelativeLayout)findViewById(R.id.corrections_toggle);
+ int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(lgCode), KMManager.KMDefault_Suggestion);
- correctionsTextView = (TextView) layout.findViewById(R.id.text1);
- correctionsTextView.setText(getString(R.string.enable_corrections));
- correctionsToggle = layout.findViewById(R.id.toggle);
- correctionsToggle.setChecked(mayCorrect); // Link to persistent option storage! Also needs handler.
- String prefsKey = KMManager.getLanguageCorrectionPreferenceKey(lgCode);
- correctionsToggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode));
+ // Initialize Radio button group change listeners
+ RadioGroup radioGroup = (RadioGroup) findViewById(R.id.suggestion_radio_group);
+ radioGroup.clearCheck();
- layout = (RelativeLayout)findViewById(R.id.predictions_toggle);
+ int[] RadioButtonArray = {
+ R.id.suggestion_radio_0,
+ R.id.suggestion_radio_1,
+ R.id.suggestion_radio_2,
+ R.id.suggestion_radio_3};
+ RadioButton radioButton = (RadioButton)radioGroup.findViewById(RadioButtonArray[maySuggest]);
+ radioButton.setChecked(true);
- textView = (TextView) layout.findViewById(R.id.text1);
- textView.setText(getString(R.string.enable_predictions));
- SwitchCompat predictionsToggle = layout.findViewById(R.id.toggle);
- predictionsToggle.setChecked(mayPredict); // Link to persistent option storage! Also needs handler.
- prefsKey = KMManager.getLanguagePredictionPreferenceKey(lgCode);
- predictionsToggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode));
+ radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, @IdRes int checkId) {
+ RadioButton checkedButton = (RadioButton)radioGroup.findViewById(checkId);
+ int index = radioGroup.indexOfChild(checkedButton);
+ KMManager.setMaySuggest(lgCode, KMManager.SuggestionType.fromInt(index));
+
+ // Don't use/apply language modeling settings for languages without models.
+ if (associatedLexicalModel.isEmpty()) {
+ return;
+ }
- overrideCorrectionsToggle(mayPredict);
+ Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(context);
+ if(kbInfo != null) {
+ // If the active keyboard is for this language, immediately enact the new pref setting.
+ String kbdLgCode = kbInfo.getLanguageID();
+ if (kbdLgCode.equals(lgCode)) {
+ // Not only registers the model but also applies our modeling preferences.
+ KMManager.registerAssociatedLexicalModel(lgCode);
+ }
+ }
+ }
+ });
- layout = (RelativeLayout)findViewById(R.id.model_picker);
+ RelativeLayout layout = (RelativeLayout)findViewById(R.id.model_picker);
textView = (TextView) layout.findViewById(R.id.text1);
textView.setText(getString(R.string.model_label));
diff --git a/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml
index a475ae37255..de474402c06 100644
--- a/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values-it-rIT/strings.xml
@@ -68,7 +68,7 @@
Regola altezza tastiera
- Adjust longpress delay
+ Regola ritardo pressione prolungata
Didascalia barra spaziatrice
@@ -89,11 +89,11 @@
Non mostrare la didascalia sulla barra spaziatrice
Vibra durante la digitazione
-
+
Mostra sempre il banner
-
+
Da attuare
-
+
Quando è spento, mostrato solo quando il testo predittivo è abilitato
Consenti l\'invio di segnalazioni di crash attraverso la rete
@@ -126,13 +126,13 @@
Ripristina impostazioni predefinite
- Delay Time: %1$.1f seconds
+ Tempo ritardo: %1$.1f secondi
- Delay time longer
+ Tempo ritardo maggiore
- Delay time shorter
+ Tempo ritardo minore
- Longpress delay time slider
+ Cursore tempo ritardo pressione prolungata
Cerca o digita URL
diff --git a/android/KMAPro/kMAPro/src/main/res/values-km-rKH/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-km-rKH/strings.xml
index f465074f8ba..c1363c161ad 100644
--- a/android/KMAPro/kMAPro/src/main/res/values-km-rKH/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values-km-rKH/strings.xml
@@ -67,6 +67,8 @@
កែកម្ពស់ក្ដារចុច
+ កែសម្រួលការពន្យារពេលរបស់ longpress
+
ពាក្យលើគ្រាប់ចុចដកឃ្លា
ក្តារចុច
@@ -86,11 +88,11 @@
កុំបង្ហាញពាក្យនៅលើគ្រាប់ចុចដកឃ្លា
ញ័រពេលវាយអក្សរ
-
+
បង្ហាញបដាជានិច្ច
-
+
ត្រូវអនុវត្ត
-
+
បើបិទ បដានឹងបង្ហាញតែនៅពេលបើកការណែនាំពាក្យស្វ័យប្រវត្តិប៉ុណ្ណោះ
អនុញ្ញាតឱ្យផ្ញើរបាយការណ៍អំពីការគាំងតាមបណ្ដាញ
@@ -122,6 +124,14 @@
បង្វិលឧបករណ៍ដើម្បីកែបញ្ឈ និងផ្ដេក
ប្ដូរកំណត់ទៅលំនាំដើម
+
+ រយៈពេលពន្យារ: %1$.1f វិនាទី
+
+ ពន្យារពេលយូរជាងមុន
+
+ ពន្យារពេលតិចជាងមុន
+
+ របារំកិលពន្យារពេល longpress
ស្វែងរក ឬវាយបញ្ចូល URL
diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js
index 5e5d7241235..21f783ffc55 100644
--- a/android/KMEA/app/src/main/assets/android-host.js
+++ b/android/KMEA/app/src/main/assets/android-host.js
@@ -229,11 +229,23 @@ function deregisterModel(modelID) {
keyman.removeModel(modelID);
}
-function enableSuggestions(model, mayPredict, mayCorrect) {
+function enableSuggestions(model, suggestionType) {
// Set the options first so that KMW's ModelManager can properly handle model enablement states
// the moment we actually register the new model.
- keyman.core.languageProcessor.mayPredict = mayPredict;
- keyman.core.languageProcessor.mayCorrect = mayCorrect;
+ // Use console_debug
+ console_debug('enableSuggestions(model, maySuggest='+suggestionType+')');
+ const suggestionSettings = [
+ // mayPredict, mayCorrect, mayAutoCorrect
+ [false, false, false], // 0 = SuggestionType.SUGGESTIONS_DISABLED
+ [true, false, false], // 1 = SuggestionType.PREDICTIONS_ONLY
+ [true, true, false], // 2 = SuggestionType.PREDICTIONS_WITH_CORRECTIONS
+ [true, true, true], // 3 = SuggestionType.PREDICTIONS_WITH_AUTO_CORRECT
+ ];
+ const t = suggestionSettings[suggestionType]
+ ? suggestionSettings[suggestionType] : suggestionSettings[0];
+ keyman.core.languageProcessor.mayPredict = t[0];
+ keyman.core.languageProcessor.maySuggest = t[1];
+ // keyman.core.languageProcessor.mayAutoCorrect = t[2];
registerModel(model);
}
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java
index 1e28e4af0b3..5ce294909c1 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java
@@ -16,6 +16,7 @@
import com.keyman.engine.KeyboardEventHandler.EventType;
import com.keyman.engine.KMManager;
import com.keyman.engine.KMManager.KeyboardType;
+import com.keyman.engine.KMManager.SuggestionType;
import com.keyman.engine.util.KMLog;
import com.keyman.engine.data.Keyboard;
@@ -165,7 +166,9 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
boolean modelPredictionPref = false;
if (!KMManager.getMayPredictOverride() && KMManager.currentLexicalModel != null) {
- modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true);
+ modelPredictionPref = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(
+ KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), KMManager.KMDefault_Suggestion)
+ != SuggestionType.SUGGESTIONS_DISABLED.toInt();
}
KMManager.setBannerOptions(modelPredictionPref);
RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams();
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 592f4be2a15..e776476aff6 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
@@ -192,6 +192,35 @@ public enum EnterModeType {
DEFAULT, // Default ENTER action
}
+ // Enum for whether the suggestion banner allows predictions, corrections, auto-corrections
+ public enum SuggestionType {
+ // Suggestion Disabled - No Predictions, No corrections, No auto-corrections
+ SUGGESTIONS_DISABLED,
+
+ // Suggestions Enabled
+ PREDICTIONS_ONLY, // Predictions with no corrections
+ PREDICTIONS_WITH_CORRECTIONS, // Predictions with corrections
+ PREDICTIONS_WITH_AUTO_CORRECT; // Predictions with auto-corrections
+
+ public static SuggestionType fromInt(int mode) {
+ switch (mode) {
+ case 0:
+ return SUGGESTIONS_DISABLED;
+ case 1:
+ return PREDICTIONS_ONLY;
+ case 2:
+ return PREDICTIONS_WITH_CORRECTIONS;
+ case 3:
+ return PREDICTIONS_WITH_AUTO_CORRECT;
+ }
+ return SUGGESTIONS_DISABLED;
+ }
+
+ public int toInt() {
+ return this.ordinal();
+ }
+ }
+
protected static InputMethodService IMService;
private static boolean debugMode = false;
@@ -220,6 +249,7 @@ public enum EnterModeType {
public final static String predictionPrefSuffix = ".mayPredict";
public final static String correctionPrefSuffix = ".mayCorrect";
+ public final static String autoCorrectionPrefSuffix = ".mayAutoCorect";
// Special override for when the keyboard may have haptic feedback when typing.
// haptic feedback disabled for hardware keystrokes
@@ -315,6 +345,8 @@ public enum EnterModeType {
public static final int KMMinimum_LongpressDelay = 300;
public static final int KMMaximum_LongpressDelay = 1500;
+ // Default prediction/correction setting
+ public static final int KMDefault_Suggestion = SuggestionType.PREDICTIONS_WITH_CORRECTIONS.toInt();
// Keyman files
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
@@ -707,6 +739,10 @@ public static String getLanguageCorrectionPreferenceKey(String langID) {
return langID + correctionPrefSuffix;
}
+ public static String getLanguageAutoCorrectionPreferenceKey(String langID) {
+ return langID + autoCorrectionPrefSuffix;
+ }
+
public static void hideSystemKeyboard() {
if (SystemKeyboard != null) {
SystemKeyboard.hideKeyboard();
@@ -1350,6 +1386,18 @@ public static boolean getMayPredictOverride() {
return mayPredictOverride;
}
+ /**
+ * Store SuggestionType as int in preference
+ * @param languageID as String
+ * @param suggestType SuggestionType
+ */
+ public static void setMaySuggest(String languageID, SuggestionType suggestType) {
+ SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(KMManager.getLanguageAutoCorrectionPreferenceKey(languageID), suggestType.toInt());
+ editor.commit();
+ }
+
/**
* Determines if the InputType field is a numeric field
* @param inputType
@@ -1462,24 +1510,23 @@ public static boolean registerLexicalModel(HashMap lexicalModelI
model = model.replaceAll("\'", "\\\\'"); // Double-escaped-backslash b/c regex.
model = model.replaceAll("\"", "'");
- // When entering password field, mayPredict should override to false
+ // When entering password field, maySuggest should override to disabled
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
- boolean mayPredict = (mayPredictOverride) ? false :
- prefs.getBoolean(getLanguagePredictionPreferenceKey(languageID), true);
- boolean mayCorrect = prefs.getBoolean(getLanguageCorrectionPreferenceKey(languageID), true);
+ int maySuggest = mayPredictOverride ? SuggestionType.SUGGESTIONS_DISABLED.toInt() :
+ prefs.getInt(getLanguageAutoCorrectionPreferenceKey(languageID), KMDefault_Suggestion);
RelativeLayout.LayoutParams params;
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreTextChange() && modelFileExists) {
params = getKeyboardLayoutParams();
// Do NOT re-layout here; it'll be triggered once the banner loads.
- InAppKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
+ InAppKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %d)", model, maySuggest));
}
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreTextChange() && modelFileExists) {
params = getKeyboardLayoutParams();
// Do NOT re-layout here; it'll be triggered once the banner loads.
- SystemKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
+ SystemKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %d)", model, maySuggest));
}
return true;
}
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/packages/JSONUtils.java b/android/KMEA/app/src/main/java/com/keyman/engine/packages/JSONUtils.java
index d7645052172..80fa18bc882 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/packages/JSONUtils.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/packages/JSONUtils.java
@@ -38,6 +38,9 @@ public static JSONArray getLanguages() {
return new JSONArray();
}
File[] packages = resourceRoot.listFiles();
+ if (packages == null) {
+ return new JSONArray();
+ }
JSONArray languagesArray = new JSONArray();
JSONParser parser = new JSONParser();
@@ -60,6 +63,9 @@ public static JSONArray getLanguages() {
String kbdFilename = pkg.getName() + "/" + kbdID + ".js";
// Merge languages
+ if (!kmpKeyboardObj.has("languages")) {
+ continue;
+ }
JSONArray kmpLanguageArray = kmpKeyboardObj.getJSONArray("languages");
for (int j=0; j
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/android/KMEA/app/src/main/res/values-it-rIT/strings.xml b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml
index da1e3531a2b..7dce8252b27 100644
--- a/android/KMEA/app/src/main/res/values-it-rIT/strings.xml
+++ b/android/KMEA/app/src/main/res/values-it-rIT/strings.xml
@@ -133,10 +133,15 @@
%1$s tastiera installata
Tastiera eliminata
-
+
Abilita correzioni
-
+
Abilita previsioni
+
+ Disabilita suggerimenti
+ Solo previsioni
+ Previsioni con correzioni
+ Previsioni con correzioni automatiche
Dizionario
diff --git a/android/KMEA/app/src/main/res/values-km-rKH/strings.xml b/android/KMEA/app/src/main/res/values-km-rKH/strings.xml
index 8763efa4701..a687e6d3770 100644
--- a/android/KMEA/app/src/main/res/values-km-rKH/strings.xml
+++ b/android/KMEA/app/src/main/res/values-km-rKH/strings.xml
@@ -131,10 +131,15 @@
បានដំឡើងក្ដារចុច %1$s
ក្ដារចុចត្រូវបានលុប
-
+
បើកការកែពាក្យ
-
+
បើកការណែនាំពាក្យស្វ័យប្រវត្តិ
+
+ បិទការព្យាករណ៍ពាក្យ
+ អនុញ្ញាតតែមុខងារព្យាករណ៍ពាក្យ
+ អនុញ្ញាតមុខងារព្យាករណ៍ពាក្យ និងកែពាក្យ
+ អនុញ្ញាតមុខងារព្យាករណ៍ពាក្យ និងកែពាក្យដោយស្វ័យប្រវត្តិ
បញ្ជីពាក្យ
diff --git a/android/KMEA/app/src/main/res/values/strings.xml b/android/KMEA/app/src/main/res/values/strings.xml
index 506626cd244..5c5d6f3173e 100644
--- a/android/KMEA/app/src/main/res/values/strings.xml
+++ b/android/KMEA/app/src/main/res/values/strings.xml
@@ -211,12 +211,21 @@
Keyboard deleted
-
+
Enable corrections
-
+
Enable predictions
+
+ Disable suggestions
+
+ Predictions only
+
+ Predictions with corrections
+
+ Predictions with auto-corrections
+
Dictionary
diff --git a/android/KMEA/app/src/test/java/com/keyman/engine/packages/JSONUtilsTest.java b/android/KMEA/app/src/test/java/com/keyman/engine/packages/JSONUtilsTest.java
index cf8ddadfbe3..689dd72e400 100644
--- a/android/KMEA/app/src/test/java/com/keyman/engine/packages/JSONUtilsTest.java
+++ b/android/KMEA/app/src/test/java/com/keyman/engine/packages/JSONUtilsTest.java
@@ -19,6 +19,7 @@
@RunWith(RobolectricTestRunner.class)
public class JSONUtilsTest {
private static final File TEST_RESOURCE_ROOT = new File("test_resources", "packages");
+ private static final File TEST_INVALID_RESOURCE_ROOT = new File("test_resources", "invalid_packages");
private static final String TAG = "JSONUtilsTest";
@Before
@@ -52,6 +53,18 @@ public void test_getLanguages() {
}
}
+ /**
+ * Test parsing null_languages/kmp.json which has no languages
+ */
+ @Test
+ public void test_invalid_getLanguages() {
+ JSONUtils.initialize(TEST_INVALID_RESOURCE_ROOT);
+ JSONArray result = JSONUtils.getLanguages();
+ final int EXPECTED_NUM_LANGUAGES = 0;
+
+ Assert.assertEquals(EXPECTED_NUM_LANGUAGES, result.length());
+ }
+
@Test
public void test_findID() {
JSONParser parser = new JSONParser();
diff --git a/android/KMEA/test_resources/invalid_packages/null_languages/kmp.json b/android/KMEA/test_resources/invalid_packages/null_languages/kmp.json
new file mode 100644
index 00000000000..65e6e1e0073
--- /dev/null
+++ b/android/KMEA/test_resources/invalid_packages/null_languages/kmp.json
@@ -0,0 +1,127 @@
+{
+ "system": {
+ "keymanDeveloperVersion": "11.0.1304.0",
+ "fileVersion": "7.0"
+ },
+ "options": {
+ "graphicFile": "splash.bmp"
+ },
+ "info": {
+ "name": {
+ "description": "Cameroon QWERTY"
+ },
+ "version": {
+ "description": "6.0.2"
+ },
+ "copyright": {
+ "description": "\u00A92018 SIL Cameroon"
+ },
+ "author": {
+ "description": "Matthew Lee",
+ "url": "mailto:keyboards_cameroon@sil.org"
+ },
+ "website": {
+ "description": "LangTechCameroon.info",
+ "url": "LangTechCameroon.info"
+ }
+ },
+ "files": [
+ {
+ "name": "Cameroon.ico",
+ "description": "File Cameroon.ico"
+ },
+ {
+ "name": "sil_cameroon_qwerty.css",
+ "description": "File sil_cameroon_qwerty.css"
+ },
+ {
+ "name": "AndikaAfr-R.ttf",
+ "description": "Font Andika Afr"
+ },
+ {
+ "name": "sil_cameroon_qwerty.kvk",
+ "description": "File sil_cameroon_qwerty.kvk"
+ },
+ {
+ "name": "sil_cameroon_qwerty.kmx",
+ "description": "Keyboard Cameroon QWERTY"
+ },
+ {
+ "name": "splash.bmp",
+ "description": "File splash.bmp"
+ },
+ {
+ "name": "welcome.htm",
+ "description": "File welcome.htm"
+ },
+ {
+ "name": "e_chart.png",
+ "description": "File e_chart.png"
+ },
+ {
+ "name": "e_tableau.png",
+ "description": "File e_tableau.png"
+ },
+ {
+ "name": "helpIcon.png",
+ "description": "File helpIcon.png"
+ },
+ {
+ "name": "ic_launcher.png",
+ "description": "File ic_launcher.png"
+ },
+ {
+ "name": "icon.png",
+ "description": "File icon.png"
+ },
+ {
+ "name": "QwertyColor.png",
+ "description": "File QwertyColor.png"
+ },
+ {
+ "name": "sil-logo-blue.png",
+ "description": "File sil-logo-blue.png"
+ },
+ {
+ "name": "kb.css",
+ "description": "File kb.css"
+ },
+ {
+ "name": "sil_cameroon_qwerty.js",
+ "description": "File sil_cameroon_qwerty.js"
+ },
+ {
+ "name": "globe.png",
+ "description": "File globe.png"
+ },
+ {
+ "name": "longpress-qwerty.png",
+ "description": "File longpress-qwerty.png"
+ },
+ {
+ "name": "modkeys-en-q.png",
+ "description": "File modkeys-en-q.png"
+ },
+ {
+ "name": "modkeys-fr-q.png",
+ "description": "File modkeys-fr-q.png"
+ },
+ {
+ "name": "kmp.inf",
+ "description": "Package information"
+ },
+ {
+ "name": "kmp.json",
+ "description": "Package information (JSON)"
+ }
+ ],
+ "keyboards": [
+ {
+ "name": "Cameroon QWERTY",
+ "id": "sil_cameroon_qwerty",
+ "version": "6.0.2",
+ "oskFont": "AndikaAfr-R.ttf",
+ "displayFont": "AndikaAfr-R.ttf"
+ }
+ ]
+}
diff --git a/android/README.md b/android/README.md
index 2d1090574b1..6abb94161d5 100644
--- a/android/README.md
+++ b/android/README.md
@@ -27,14 +27,14 @@ yes | ~/Library/Android/sdk/tools/bin/sdkmanager --licenses
### Crash Reporting
-Keyman for Android uses [Sentry](https://sentry.io) for crash reporting. The
-analytics for Debug are associated with an App Bundle ID
+Keyman for Android uses [Sentry](https://sentry.io) for crash reporting. The
+analytics for Debug are associated with an App Bundle ID
`com.tavultesoft.kmapro.debug`.
### Compiling From Command Line
1. Launch a command prompt and cd to the directory **keyman/android**
-2. Run the top level build script `./build.sh configure build --debug` which will:
+2. Run the top level build script `./build.sh configure build:engine build:app --debug` which will:
* Compile KMEA (and its KMW dependency)
* Download default keyboard and dictionary resources as needed
* Compile KMAPro
@@ -79,7 +79,7 @@ analytics for Debug are associated with an App Bundle ID
Replace `SERIAL` with the device serial number listed in step 2.
### Compiling the app's offline help
-Keyman for Android help is maintained in the Markdown files in android/help/.
+Keyman for Android help is maintained in the Markdown files in android/docs/help.
The script `/resources/build/build-help.inc.sh` uses the `pandoc` tool to convert the Markdown files into html.
```bash
@@ -121,7 +121,7 @@ Building these projects follow the same steps as KMAPro:
## How to Build Keyman Engine for Android
1. Open a terminal or Git Bash prompt and go to the Android project folder (e.g. `cd ~/keyman/android/`)
-2. Run `./build.sh --debug`
+2. Run `./build.sh build:engine --debug`
Keyman Engine for Android library (**keyman-engine.aar**) is now ready to be imported in any project.
@@ -167,3 +167,10 @@ dependencies {
````
5. include `import com.keyman.engine.*;` to use Keyman Engine in a class.
+
+### Keyman Engine for Android help content
+Keyman Engine for Android help is maintained in the Markdown files in android/docs/engine/.
+
+## Design Documentation
+
+Internal design documents about features pertaining to Keyman for Android and Keyman Engine for Android are maintained in the Markdown files in android/docs/internal/.
diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java
index 4ad462a76c1..438ed237dbc 100644
--- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java
+++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java
@@ -130,6 +130,11 @@ public void onStartInput(EditorInfo attribute, boolean restarting) {
super.onStartInput(attribute, restarting);
KMManager.onStartInput(attribute, restarting);
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM);
+
+ // Determine special handling for ENTER key
+ int inputType = attribute.inputType;
+ KMManager.setEnterMode(attribute.imeOptions, inputType);
+
// User switched to a new input field so we should extract the text from input field
// and pass it to Keyman Engine together with selection range
InputConnection ic = getCurrentInputConnection();
diff --git a/android/build.sh b/android/build.sh
index d876d882d77..e6d99278980 100755
--- a/android/build.sh
+++ b/android/build.sh
@@ -14,6 +14,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
builder_describe \
"Build Keyman Engine for Android, Keyman for Android, and FirstVoices Android app." \
+ "@/resources/tools/check-markdown test:help" \
clean \
configure \
build \
@@ -23,6 +24,7 @@ builder_describe \
--upload-sentry+ \
":engine=KMEA Keyman Engine for Android" \
":app=KMAPro Keyman for Android" \
+ ":help Online documentation" \
":sample1=Samples/KMSample1 Sample app: KMSample1" \
":sample2=Samples/KMSample2 Sample app: KMSample2" \
":keyboardharness=Tests/KeyboardHarness Test app: KeyboardHarness" \
@@ -42,4 +44,13 @@ if builder_start_action clean; then
builder_finish_action success clean
fi
-builder_run_child_actions configure build test publish
+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"
+}
+
+builder_run_action test:help do_test_help
+
+builder_run_child_actions publish
\ No newline at end of file
diff --git a/android/docs/engine/KMKeyboard/index.md b/android/docs/engine/KMKeyboard/index.md
new file mode 100644
index 00000000000..774d0be6f95
--- /dev/null
+++ b/android/docs/engine/KMKeyboard/index.md
@@ -0,0 +1,30 @@
+---
+title: KMKeyboard class
+---
+
+## Summary
+
+The **`KMKeyboard`** class provides Keyman's methods for extending an Android WebView
+
+## Syntax
+
+```java
+KMKeyboard.methodName()
+```
+
+## Description
+
+The KMKeyboard is the core class which provides most of the methods you will need to develop an in-app or system keyboard with Keyman Engine
+
+
+
+## Methods
+
+`getShouldShowHelpBubble()`
+: get the stored preference whether to show the help bubble on the globe key
+
+`setShouldShowHelpBubble()`
+: sets the stored preference whether to show the help bubble on the globe key
+
+`initKMKeyboard()`
+: initialize the KMKeyboard WebView
diff --git a/android/docs/engine/KMManager/addKeyboard.md b/android/docs/engine/KMManager/addKeyboard.md
new file mode 100644
index 00000000000..f03048da470
--- /dev/null
+++ b/android/docs/engine/KMManager/addKeyboard.md
@@ -0,0 +1,108 @@
+---
+title: KMManager.addKeyboard()
+---
+
+## Summary
+
+The **`addKeyboard()`** method adds a keyboard into the keyboards list.
+
+## Syntax
+
+``` javascript
+KMManager.addKeyboard(Context context, Keyboard keyboardInfo)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`keyboardInfo`
+: A `Keyboard` type of keyboard information.
+
+### Returns
+
+Returns `true` if the keyboard was added successfully, `false`
+otherwise.
+
+## Description
+
+Use this method to include a keyboard in the keyboards list so that it
+can be selected from the keyboards menu. If the keyboard with same
+keyboard ID and language ID exists, it updates the existing keyboard
+info.
+
+
+
+------------------------------------------------------------------------
+
+## Syntax (Deprecated)
+
+``` javascript
+KMManager.addKeyboard(Context context, HashMap keyboardInfo)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`keyboardInfo`
+: A dictionary of keyboard information with keys and values defined as
+ `HashMap`.
+
+### Returns
+
+Returns `true` if the keyboard was added successfully, `false`
+otherwise.
+
+## Description
+
+Use this method to include a keyboard in the keyboards list so that it
+can be selected from the keyboards menu. If the keyboard with same
+keyboard ID and language ID exists, it updates the existing keyboard
+info.
+
+
+
+------------------------------------------------------------------------
+
+## Examples
+
+### Example: Using `addKeyboard()`
+
+The following script illustrate the use of `addKeyboard()`:
+
+``` javascript
+ // Add a custom keyboard
+ Keyboard kbInfo = new Keyboard(
+ "basic_kbdtam99", // Package ID - filename of the .kmp file
+ "basic_kbdtam99", // Keyboard ID - filename of the .js file
+ "Tamil 99 Basic", // Keyboard Name
+ "ta", // Language ID
+ "Tamil", // Language Name
+ "1.0", // Keyboard Version
+ null, // URL to help documentation if available
+ null, // URL to latest .kmp file
+ true, // Boolean to show this is a new keyboard in the keyboard picker
+
+ // Font information of the .ttf font to use in KMSample2 (for example "aava1.ttf").
+ // basic_kbdtam99 doesn't include a font. Can set blank "" or KMManager.KMDefault_KeyboardFont
+ // KMEA will use the font for the OSK, but the Android device determines the system font used for keyboard output
+ KMManager.KMDefault_KeyboardFont, // Font for KMSample2
+ KMManager.KMDefault_KeyboardFont); // Font for OSK
+
+ KMManager.addKeyboard(this, kbInfo);
+```
+
+## History
+
+Added syntax using Keyboard type parameter in Keyman Engine for Android
+14.0.
+
+Deprecated syntax using the HashMap<String key, String value>
+parameter in Keyman Engine for Android 14.0
+
+## See also
+
+- [`removeKeyboard()`](removeKeyboard)
diff --git a/android/docs/engine/KMManager/addKeyboardDownloadEventListener.md b/android/docs/engine/KMManager/addKeyboardDownloadEventListener.md
new file mode 100644
index 00000000000..3b56762d8a2
--- /dev/null
+++ b/android/docs/engine/KMManager/addKeyboardDownloadEventListener.md
@@ -0,0 +1,46 @@
+---
+title: KMManager.addKeyboardDownloadEventListener()
+---
+
+## Summary
+
+The **`addKeyboardDownloadEventListener()`** method adds the specified
+listener into the list of keyboard download event listeners.
+
+## Syntax
+
+``` javascript
+KMManager.addKeyboardDownloadEventListener(OnKeyboardDownloadEventListener listener)
+```
+
+### Parameters
+
+`listener`
+: The listener to receive keyboard download event notifications.
+
+## Description
+
+Use this method to add a listener to receive keyboard download event
+notifications. The listener must implement
+`KMManager.OnKeyboardDownloadEventListener` interface.
+
+## Examples
+
+### Example: Using `addKeyboardDownloadEventListener()`
+
+The following script illustrate the use of
+`addKeyboardDownloadEventListener()`:
+
+``` javascript
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // ...
+ KMManager.addKeyboardDownloadEventListener(this);
+ // ...
+ }
+```
+
+## See also
+
+- [`removeKeyboardDownloadEventListener()`](removeKeyboardDownloadEventListener)
diff --git a/android/docs/engine/KMManager/addKeyboardEventListener.md b/android/docs/engine/KMManager/addKeyboardEventListener.md
new file mode 100644
index 00000000000..b8ee6e84d1a
--- /dev/null
+++ b/android/docs/engine/KMManager/addKeyboardEventListener.md
@@ -0,0 +1,45 @@
+---
+title: KMManager.addKeyboardEventListener()
+---
+
+## Summary
+
+The **`addKeyboardEventListener()`** method adds the specified listener
+into the list of keyboard event listeners.
+
+## Syntax
+
+``` javascript
+KMManager.addKeyboardEventListener(OnKeyboardEventListener listener)
+```
+
+### Parameters
+
+`listener`
+: The listener to receive keyboard event notifications.
+
+## Description
+
+Use this method to add a listener to receive keyboard event
+notifications. The listener must implement
+KMManager.OnKeyboardEventListener interface.
+
+## Examples
+
+### Example: Using `addKeyboardEventListener()`
+
+The following script illustrate the use of `addKeyboardEventListener()`:
+
+``` javascript
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // ...
+ KMManager.addKeyboardEventListener(this);
+ // ...
+ }
+```
+
+## See also
+
+- [`removeKeyboardEventListener()`](removeKeyboardEventListener)
diff --git a/android/docs/engine/KMManager/addLexicalModel.md b/android/docs/engine/KMManager/addLexicalModel.md
new file mode 100644
index 00000000000..72237277d8b
--- /dev/null
+++ b/android/docs/engine/KMManager/addLexicalModel.md
@@ -0,0 +1,49 @@
+---
+title: KMManager.addLexicalModel()
+---
+
+## Summary
+The **addLexicalModel()** method adds a lexical model into the lexical models list.
+
+## Syntax
+
+```javascript
+KMManager.addLexicalModel(Context context, HashMap lexicalModelInfo)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`lexicalModelInfo`
+: A dictionary of lexical model information with keys and values defined as `HashMaplexicalModelInfo = new HashMap();
+ lexicalModelInfo.put(KMManager.KMKey_PackageID, "example.ta.wordlist");
+ lexicalModelInfo.put(KMManager.KMKey_LanguageID, "ta");
+ lexicalModelInfo.put(KMManager.KMKey_LexicalModelID, "example.ta.wordlist");
+ lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, "1.0");
+ KMManager.addLexicalModel(this, lexicalModelInfo);
+
+ KMManager.registerLexicalModel(lexicalModelInfo);
+```
+
+## See also
+* [registerLexicalModel()](registerLexicalModel)
diff --git a/android/docs/engine/KMManager/advanceToNextInputMode.md b/android/docs/engine/KMManager/advanceToNextInputMode.md
new file mode 100644
index 00000000000..486294b7545
--- /dev/null
+++ b/android/docs/engine/KMManager/advanceToNextInputMode.md
@@ -0,0 +1,31 @@
+---
+title: KMManager.advanceToNextInputMode()
+---
+
+## Summary
+The **advanceToNextInputMode()** method switches to the next system keyboard input mode.
+
+## Syntax
+
+```javascript
+KMManager.advanceToNextInputMode()
+```
+
+## Description
+Use this method to switch to the next system keyboard.
+
+
+## Examples
+
+### Example: Using `advanceToNextInputMode()`
+
+The following script illustrates the use of `advanceToNextInputMode()`:
+```java
+ // Remove the second keyboard in the list
+ KMManager.removeKeyboard(this, 1);
+
+ KMManager.advanceToNextInputMode();
+```
+
+## See also
+* [advanceToPreviousInputMethod()](advanceToPreviousInputMethod)
diff --git a/android/docs/engine/KMManager/advanceToPreviousInputMethod.md b/android/docs/engine/KMManager/advanceToPreviousInputMethod.md
new file mode 100644
index 00000000000..3d4b248d434
--- /dev/null
+++ b/android/docs/engine/KMManager/advanceToPreviousInputMethod.md
@@ -0,0 +1,32 @@
+---
+title: KMManager.advanceToPreviousInputMethod()
+---
+
+## Summary
+The **advanceToPreviousInputMethod()** method switches to the previous system keyboard input mode.
+
+## Syntax
+
+```javascript
+KMManager.advanceToPreviousInputMethod()
+```
+
+## Description
+Use this method to switch to the previous system keyboard.
+
+When only 1 Keyman keyboard is installed, this is the default action for pressing the globe key.
+
+## Examples
+
+### Example: Using `advanceToPreviousInputMethod()`
+
+The following script illustrates the use of `advanceToPreviousInputMethod()`:
+```java
+ // Remove the second keyboard in the list
+ KMManager.removeKeyboard(this, 1);
+
+ KMManager.advanceToPreviousInputMethod();
+```
+
+## See also
+* [advanceToNextInputMode()](advanceToNextInputMode)
diff --git a/android/docs/engine/KMManager/applyKeyboardHeight.md b/android/docs/engine/KMManager/applyKeyboardHeight.md
new file mode 100644
index 00000000000..2830519c625
--- /dev/null
+++ b/android/docs/engine/KMManager/applyKeyboardHeight.md
@@ -0,0 +1,54 @@
+---
+title: KMManager.applyKeyboardHeight()
+---
+
+## Summary
+The **applyKeyboardHeight()** method sets the height of the keyboard frame for
+the device's current [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali)
+(portrait vs landscape).
+
+## Syntax
+```java
+KMManager.applyKeyboardHeight(Context context, int height)
+```
+
+### Parameters
+
+`context`
+: The context
+
+`height`
+: The height of the keyboard frame in *density-independent pixels (dp)*
+
+## Description
+Use this method when you want to increase or decrease the keyboard height for
+the device in the current screen orientation. This height is independent from
+the height of the suggestion banner frame.
+
+For reference, here's a table of the default Keyman keyboard heights for various devices and screen orientation.
+
+ Device Type and Screen Orientation | Default height (dp) |
+|-----------------------------------|---------------------|
+| Default handset in portrait | 205 |
+| Default handset in landscape | 150 |
+| 7" tablet in portrait | 305 |
+| 7" tablet in landscape | 200 |
+| 10" tablet in portrait | 405 |
+| 10" tablet in landscape | 300 |
+
+**Note:** This new keyboard height would be applied for all platforms, so an
+adjusted keyboard height for a phone would appear too small for a tablet.
+
+## Examples
+
+### Example: Using `applyKeyboardHeight()`
+The following script illustrates the use of `applyKeyboardHeight()`:
+
+```java
+ // Increase the Keyman keyboard height (default Keyman value for most phones is 205dp)
+ int newKeyboardHeight = 300;
+ KMManager.applyKeyboardHeight(this, newKeyboardHeight);
+```
+
+## See also
+* [getKeyboardHeight()](getKeyboardHeight)
diff --git a/android/docs/engine/KMManager/canAddNewKeyboard.md b/android/docs/engine/KMManager/canAddNewKeyboard.md
new file mode 100644
index 00000000000..82c3f5f89c5
--- /dev/null
+++ b/android/docs/engine/KMManager/canAddNewKeyboard.md
@@ -0,0 +1,40 @@
+---
+title: KMManager.canAddNewKeyboard()
+---
+
+## Summary
+
+The **`canAddNewKeyboard()`** method returns whether adding a new
+keyboard is enabled.
+
+## Syntax
+
+``` javascript
+KMManager.canAddNewKeyboard()
+```
+
+### Returns
+
+Returns `true` if adding a new keyboard is enabled, `false` otherwise.
+
+## Description
+
+Use this method to check if additional keyboards can be added.
+
+## Examples
+
+### Example: Using `canAddNewKeyboard()`
+
+The following script illustrate the use of `canAddNewKeyboard()`:
+
+``` javascript
+ Keyboard kbInfo = ...; // Keyboard information
+ if (KMManager.canAddNewKeyboard()) {
+ KMManager.addKeyboard(this, kbInfo);
+ }
+```
+
+## See also
+
+- [`canRemoveKeyboard`](canRemoveKeyboard)
+- [`setCanAddNewKeyboard`](setCanAddNewKeyboard)
diff --git a/android/docs/engine/KMManager/canRemoveKeyboard.md b/android/docs/engine/KMManager/canRemoveKeyboard.md
new file mode 100644
index 00000000000..b3217d5ac69
--- /dev/null
+++ b/android/docs/engine/KMManager/canRemoveKeyboard.md
@@ -0,0 +1,33 @@
+---
+title: KMManager.canRemoveKeyboard()
+---
+
+## Summary
+The **canRemoveKeyboard()** method returns whether removing a keyboard is enabled, like in the keyboard picker menu.
+
+## Syntax
+
+```javascript
+KMManager.canRemoveKeyboard()
+```
+## Returns
+
+Returns `true` if removing a keyboard is enabled, `false` otherwise.
+
+## Description
+Use this method to check if a keyboard can be removed. If enabled, you can long-press on a row in the keyboard picker menu to remove a keyboard.
+
+## Examples
+
+### Example: Using `canRemoveKeyboard()`
+
+The following script illustrates the use of `canRemoveKeyboard()`:
+```java
+ if (KeyboardController.getInstance().get().size() > 1 && KMManager.canRemoveKeyboard()) {
+ // Remove the second keyboard
+ KMManager.removeKeyboard(this, 1);
+ }
+```
+
+## See also
+* [canAddNewKeyboard()](canAddNewKeyboard)
diff --git a/android/docs/engine/KMManager/copyHTMLBannerAssets.md b/android/docs/engine/KMManager/copyHTMLBannerAssets.md
new file mode 100644
index 00000000000..d8d51c91820
--- /dev/null
+++ b/android/docs/engine/KMManager/copyHTMLBannerAssets.md
@@ -0,0 +1,44 @@
+---
+title: KMManager.copyHTMLBannerAssets
+---
+
+## Summary
+The **copyHTMLBannerAssets()** method copies a folder of HTML banner assets so it's available for your keyboard app's resources.
+
+## Syntax
+
+```javascript
+KMManager.copyHTMLBannerAssets(Context context, String path)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`path`
+: Relative to the /assets folder, the folder which contains the HTML assets to display when suggestions aren't available.
+
+### Returns
+Returns `true` if HTML assets were copied, `false` otherwise.
+
+## Description
+When suggestions aren't available for a keyboard, an HTML banner is displayed instead.
+Use this method to specify any HTML assets the banner will use to theme your keyboard app. Some examples of assets would be .svg images or .css files used in your banner.
+
+This can be called towards the end of `SystemKeyboard.onCreate()`.
+
+You still need to call [setHTMLBanner()](setHTMLBanner) for Keyman Engine to assign the HTML banner for in-app and system keyboards.
+
+## Examples
+
+### Example: Using `copyHTMLBannerAssets()`
+
+The following script illustrates the use of `copyHTMLBannerAssets()`:
+```javascript
+ // Copies HTML banner assets located in /assets/banner/
+ KMManager.copyHTMLBannerAssets(this, "banner");
+```
+
+## See also
+* [setHTMLBanner()](setHTMLBanner)
diff --git a/android/docs/engine/KMManager/createInputView.md b/android/docs/engine/KMManager/createInputView.md
new file mode 100644
index 00000000000..d59469e8a34
--- /dev/null
+++ b/android/docs/engine/KMManager/createInputView.md
@@ -0,0 +1,58 @@
+---
+title: KMManager.createInputView()
+---
+
+## Summary
+
+The **`createInputView()`** creates the input view to be used in
+InputMethodService.
+
+## Syntax
+
+``` javascript
+KMManager.createInputView(InputMethodService inputMethodService)
+```
+
+### Parameters
+
+`inputMethodService`
+: The InputMethodService.
+
+### Returns
+
+Returns the input view created.
+
+## Description
+
+Use this method to create the input view in response to an
+InputMethodService's `onCreateInputView()` method.
+
+## Examples
+
+### Example: Using `createInputView()`
+
+The following script illustrate the use of `createInputView()`:
+
+``` javascript
+private static View inputView = null;
+
+@Override
+public View onCreateInputView() {
+ // create the inputView only once
+ if (inputView == null)
+ inputView = KMManager.createInputView(this);
+
+ // we must remove the inputView from its previous parent before returning it
+ ViewGroup parent = (ViewGroup) inputView.getParent();
+ if (parent != null)
+ parent.removeView(inputView);
+
+ return inputView;
+}
+```
+
+## See also
+
+- [`onStartInput()`](onStartInput)
+- [`onConfigurationChanged()`](onConfigurationChanged)
+- [`onDestroy()`](onDestroy)
diff --git a/android/docs/engine/KMManager/deregisterLexicalModel.md b/android/docs/engine/KMManager/deregisterLexicalModel.md
new file mode 100644
index 00000000000..427fa2f7699
--- /dev/null
+++ b/android/docs/engine/KMManager/deregisterLexicalModel.md
@@ -0,0 +1,35 @@
+---
+title: KMManager.deregisterLexicalModel()
+---
+
+## Summary
+The **deregisterLexicalModel()** method deregisters the specified lexical model from the LMLayer so it isn't used.
+
+## Syntax
+```javascript
+KMManager.degisterLexicalModel(String modelID)
+```
+
+### Parameters
+
+`modelID`
+: The ID of the lexical model to deregister
+
+### Returns
+`true`
+
+## Description
+Use this method when you want the lexical model to stop generating suggestions.
+
+## Examples
+
+### Example: Using `deregisterLexicalModel()`
+The following script illustrates the use of `dergisterLexicalModel()`:
+
+```java
+ String lexicalModelID = "example.ta.wordlist";
+ KMManager.deregisterLexicalModel(lexicalModelID);
+```
+
+## See also
+* [registerLexicalModel()](registerLexicalModel)
diff --git a/android/docs/engine/KMManager/executeHardwareKeystroke.md b/android/docs/engine/KMManager/executeHardwareKeystroke.md
new file mode 100644
index 00000000000..5ac8ecede53
--- /dev/null
+++ b/android/docs/engine/KMManager/executeHardwareKeystroke.md
@@ -0,0 +1,63 @@
+---
+title: KMManager.executeHardwareKeystroke()
+---
+
+## Summary
+The **executeHardwareKeystroke()** method passes the keystroke generated from a physical keyboard to the KeymanWeb engine to handle.
+
+## Syntax
+```java
+bool KMManager.executeHardwareKeystroke(int code, int shift, KeyboardType keyboard, int lstates, int eventModifiers)
+```
+
+### Parameters
+
+`code`
+: key identifier
+
+`shift`
+: shift state
+
+| Flag | Value | Description |
+|--------------|----------|----------------------------------------|
+| Left Ctrl | `0x01` | Left Control Flag |
+| Right Ctrl | `0x02` | Right Control Flag |
+| Left Alt | `0x04` | Left Alt Flag |
+| Right Alt | `0x08` | Right Alt Flag |
+| Shift | `0x10` | Shift Flag |
+| Ctrl | `0x20` | Control Flag |
+| Alt | `0x40` | Alt Flag |
+
+`keyboard`
+: `KeyboardType.KEYBOARD_TYPE_INAPP` or `KeyboardType.KEYBOARD_TYPE_SYSTEM`
+
+`lstates`
+: lock state. If neither the on or off state is specified for a lock key, then it is treated as unknown state for keyboard rule matching.
+
+| Value | Description |
+|--------|----------------|
+| `0x0100` | Caps lock |
+| `0x0200` | No caps lock |
+| `0x0400` | Num lock |
+| `0x0800` | No Num lock |
+| `0x1000` | Scroll |
+| `0x2000` | No scroll lock |
+
+`eventModifiers`
+: [KeyEvent Flags](https://developer.android.com/reference/android/view/KeyEvent#getMetaState\(\)) indicating which meta keys are currently pressed.
+
+### Returns
+Returns `true` if the keyboard was initialized and executed the keystroke, `false` otherwise.
+
+## Description
+Use this method to pass a hardware keystroke to KeymanWeb Engine to process. The KeymanWeb Engine also factors in modifier and meta keys.
+
+## Examples
+
+### Example: Using `executeHardwareKeystroke()`
+The following script illustrate the use of `executeHardwareKeystroke()`:
+
+```java
+ // Send keystroke to KeymanWeb for processing: will return true to swallow the keystroke
+ return KMManager.executeHardwareKeystroke(code, keymanModifiers, keyboardType, Lstates, androidModifiers);
+```
diff --git a/android/docs/engine/KMManager/getAssociatedLexicalModel.md b/android/docs/engine/KMManager/getAssociatedLexicalModel.md
new file mode 100644
index 00000000000..e92e6f3449a
--- /dev/null
+++ b/android/docs/engine/KMManager/getAssociatedLexicalModel.md
@@ -0,0 +1,39 @@
+---
+title: KMManager.getAssociatedLexicalModel()
+---
+
+## Summary
+The **getAssociatedLexicalModel()** method searches the installed lexical models list and see if there's an associated model for a given language
+
+## Syntax
+```java
+HashMap getAssociatedLexicalModel(String langId)
+```
+
+### Parameters
+
+`langId`
+: The language ID
+
+### Returns
+A dictionary of the associated lexical model information with keys and values defined as
+`HashMap lexModelMap = KMManager.getAssociatedLexicalModel(langId);
+```
+
+## See also
+* [addLexicalModel()](addLexicalModel)
+* [registerLexicalModel()](registerLexicalModel)
diff --git a/android/docs/engine/KMManager/getBannerHeight.md b/android/docs/engine/KMManager/getBannerHeight.md
new file mode 100644
index 00000000000..d8e9d5e7389
--- /dev/null
+++ b/android/docs/engine/KMManager/getBannerHeight.md
@@ -0,0 +1,38 @@
+---
+title: KMManager.getBannerHeight()
+---
+
+## Summary
+
+The **`getBannerHeight()`** method returns the height of the suggestion
+banner.
+
+## Syntax
+
+``` javascript
+KMManager.getBannerHeight(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns the height of the suggestion banner frame in
+*density-independent pixel (dp)*.
+
+## Description
+
+Use this method to get the height of the suggestion banner frame.
+
+## Examples
+
+### Example: Using `getBannerHeight()`
+
+The following script illustrate the use of `getBannerHeight()`:
+
+``` javascript
+ int bannerHeight = KMManager.getBannerHeight(this);
+```
diff --git a/android/docs/engine/KMManager/getCurrentKeyboardIndex.md b/android/docs/engine/KMManager/getCurrentKeyboardIndex.md
new file mode 100644
index 00000000000..cbe086b7b44
--- /dev/null
+++ b/android/docs/engine/KMManager/getCurrentKeyboardIndex.md
@@ -0,0 +1,47 @@
+---
+title: KMManager.getCurrentKeyboardIndex()
+---
+
+## Summary
+
+The **`getCurrentKeyboardIndex()`** method returns index number of the
+current keyboard in keyboards list.
+
+## Syntax
+
+``` javascript
+KMManager.getCurrentKeyboardIndex(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns 0-based index number of the current keyboard if exists in
+keyboards list, -1 otherwise.
+
+## Description
+
+Use this method to get the index number of the currently selected
+keyboard if it exists in keyboards list.
+
+## Examples
+
+### Example: Using `getCurrentKeyboardIndex()`
+
+The following script illustrate the use of `getCurrentKeyboardIndex()`:
+
+``` javascript
+ int index = KMManager.getCurrentKeyboardIndex(this);
+```
+
+## See also
+
+- [`getCurrentKeyboardInfo()`](getCurrentKeyboardInfo)
+- [`getKeyboardIndex()`](getKeyboardIndex)
+- [`getKeyboardInfo()`](getKeyboardInfo)
+- [`getKeyboardsList()`](getKeyboardsList)
+- [`keyboardExists()`](keyboardExists)
diff --git a/android/docs/engine/KMManager/getCurrentKeyboardInfo.md b/android/docs/engine/KMManager/getCurrentKeyboardInfo.md
new file mode 100644
index 00000000000..4eb789e736d
--- /dev/null
+++ b/android/docs/engine/KMManager/getCurrentKeyboardInfo.md
@@ -0,0 +1,96 @@
+---
+title: KMManager.getCurrentKeyboardInfo()
+---
+
+## Summary
+
+The **`getCurrentKeyboardInfo()`** method returns keyboard information
+of the current keyboard.
+
+## Syntax
+
+``` javascript
+KMManager.getCurrentKeyboardInfo(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns the current keyboard information of `Keyboard` type.
+
+## Description
+
+Use this method to get details of the currently selected keyboard.
+Details include package ID, keyboard ID, language ID, keyboard name,
+language name and fonts.
+
+
+
+------------------------------------------------------------------------
+
+## Syntax (Deprecated)
+
+``` javascript
+KMManager.getCurrentKeyboardInfo(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+(Deprecated) Returns an information dictionary of the current keyboard
+with keys and values defined as `HashMap`.
+
+## Description
+
+Use this method to get details of the currently selected keyboard.
+Details include keyboard ID, language ID, keyboard name, language name
+and fonts.
+
+
+
+------------------------------------------------------------------------
+
+## Examples
+
+### Example: Using `getCurrentKeyboardInfo()`
+
+The following script illustrate the use of `getCurrentKeyboardInfo()`:
+
+``` javascript
+ Keyboard keyboardInfo = KMManager.getCurrentKeyboardInfo(this);
+ if (keyboardInfo != null) {
+ String packageID = keyboardInfo.getPackageID();
+ String keyboardId = keyboardInfo.getKeyboardID();
+ String keyboardName = keyboardInfo.getKeyboardName();
+ String languageId = keyboardInfo.getLanguageID();
+ String languageName = keyboardInfo.getLanguageName();
+ String version = keyboardInfo.getVersion();
+ String font = keyboardInfo.getFont();
+ String oskFont = keyboardInfo.getOSKFont();
+ //
+ }
+```
+
+## History
+
+Added syntax for returning Keyboard type in Keyman Engine for Android
+14.0.
+
+Deprecated syntax for returning HashMap<String key, String value>
+in Keyman Engine for Android 14.0
+
+## See also
+
+- [`getCurrentKeyboardIndex()`](getCurrentKeyboardIndex)
+- [`getKeyboardIndex()`](getKeyboardIndex)
+- [`getKeyboardInfo()`](getKeyboardInfo)
+- [`getKeyboardsList()`](getKeyboardsList)
+- [`keyboardExists()`](keyboardExists)
diff --git a/android/docs/engine/KMManager/getDefaultKeyboard.md b/android/docs/engine/KMManager/getDefaultKeyboard.md
new file mode 100644
index 00000000000..ad62af60da8
--- /dev/null
+++ b/android/docs/engine/KMManager/getDefaultKeyboard.md
@@ -0,0 +1,29 @@
+---
+title: KMManager.getDefaultKeyboard()
+---
+
+## Summary
+The `getDefaultKeyboard()` method returns the keyboard information for the fallback keyboard.
+
+## Syntax
+```java
+KMManager.getDefaultKeyboard()
+```
+
+### Returns
+Returns `Keyboard` type for the fallback keyboard. If not specified, this defaults to keyboard information for sil_euro_latin.
+
+## Description
+The `getDefaultKeyboard()` method returns the keyboard information for the fallback keyboard. If Keyman Engine
+has issues with a current keyboard, KMManager will switch to this fallback keyboard.
+
+## Examples
+
+### Example: Using getDefaultKeyboard()
+The following script illustrates the use of `getDefaultKeyboard()`:
+```java
+ Keyboard kbd = KMManager.getDefaultKeyboard();
+```
+
+## See also
+* [setDefaultKeyboard](setDefaultKeyboard)
diff --git a/android/docs/engine/KMManager/getFontTypeface.md b/android/docs/engine/KMManager/getFontTypeface.md
new file mode 100644
index 00000000000..4149d92b360
--- /dev/null
+++ b/android/docs/engine/KMManager/getFontTypeface.md
@@ -0,0 +1,51 @@
+---
+title: KMManager.getFontTypeface()
+---
+
+## Summary
+
+The **`getFontTypeface()`** method creates a new typeface from the
+specified font filename.
+
+## Syntax
+
+``` javascript
+KMManager.getFontTypeface(Context context, String fontFilename)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`fontFilename`
+: The filename of the font.
+
+### Returns
+
+Returns the new typeface created from font file with specified filename
+if it exists, `null` otherwise.
+
+## Description
+
+Use this method to create a new typeface from the font file with
+specified filename if it exists in `assets/fonts/` folder.
+
+## Examples
+
+### Example: Using `getFontTypeface()`
+
+The following script illustrate the use of `getFontTypeface()`:
+
+``` javascript
+ KMTextView textView = (KMTextView) findViewById(R.id.kmTextView);
+ Typeface fontTypeface = KMManager.getFontTypeface(this, "aava1.ttf");
+ textView.setTypeface(fontTypeface);
+```
+
+## See also
+
+- [`getKeyboardTextFontTypeface()`](getKeyboardTextFontTypeface)
+- [`getKeyboardOskFontTypeface()`](getKeyboardOskFontTypeface)
+- [`getKeyboardTextFontFilename()`](getKeyboardTextFontFilename)
+- [`getKeyboardOskFontFilename()`](getKeyboardOskFontFilename)
diff --git a/android/docs/engine/KMManager/getGlobeKeyAction.md b/android/docs/engine/KMManager/getGlobeKeyAction.md
new file mode 100644
index 00000000000..398afe1ee5a
--- /dev/null
+++ b/android/docs/engine/KMManager/getGlobeKeyAction.md
@@ -0,0 +1,35 @@
+---
+title: KMManager.getGlobeKeyAction()
+---
+
+## Summary
+The **getGlobeKeyAction()** method returns the short-press action type of the 'Globe' key.
+
+## Syntax
+```java
+KMManager.getGlobeKeyAction(KeyboardType kbType)
+```
+
+### Parameters
+kbType
+: The keyboard type. `KEYBOARD_TYPE_INAPP` or `KEYBOARD_TYPE_SYSTEM`
+
+### Returns
+Returns the action type of the short-press 'Globe' key as one of
+ `GLOBE_KEY_ACTION_SHOW_MENU`, `GLOBE_KEY_ACTION_SWITCH_TO_NEXT_KEYBOARD`,
+ `GLOBE_KEY_ACTION_ADVANCE_TO_PREVIOUS_SYSTEM_KEYBOARD`, `GLOBE_KEY_ACTION_ADVANCE_TO_NEXT_SYSTEM_KEYBOARD`,
+ `GLOBE_KEY_ACTION_SHOW_SYSTEM_KEYBOARDS`, or `GLOBE_KEY_ACTION_DO_NOTHING`.
+
+## Description
+Use this method to get the short-press action type of the 'Globe' key.
+
+## Examples
+
+### Example: Using getGlobeKeyAction()
+The following script illustrate the use of `getGlobeKeyAction()`:
+```java
+GlobeKeyAction action = KMManager.getGlobeKeyAction(KeyboardType.KEYBOARD_TYPE_SYSTEM);
+```
+
+## See also
+* [setGlobeKeyAction()](setGlobeKeyAction)
diff --git a/android/docs/engine/KMManager/getHapticFeedback.md b/android/docs/engine/KMManager/getHapticFeedback.md
new file mode 100644
index 00000000000..ff428ca2463
--- /dev/null
+++ b/android/docs/engine/KMManager/getHapticFeedback.md
@@ -0,0 +1,23 @@
+---
+title: KMManager.getHapticFeedback()
+---
+
+## Summary
+The **getHapticFeedback()** method returns whether the device vibrates as the user types.
+
+## Syntax
+```java
+KMManager.getHapticFeedback()
+```
+
+### Returns
+Returns `true` if the device vibrates as the user types, `false` otherwise.
+
+## Description
+Use this method to check if the device is currently configured to provide haptic feedback. Default is `false`.
+
+## History
+Keyman Engine for Android 15.0: New function.
+
+## See also
+* [setHapticFeedback](setHapticFeedback)
diff --git a/android/docs/engine/KMManager/getKMKeyboard.md b/android/docs/engine/KMManager/getKMKeyboard.md
new file mode 100644
index 00000000000..1cdc3cdd503
--- /dev/null
+++ b/android/docs/engine/KMManager/getKMKeyboard.md
@@ -0,0 +1,28 @@
+---
+title: KMManager.getKMKeyboard()
+---
+
+## Summary
+The `getKMKeyboard()` method returns the [KMKeyboard](../KMKeyboard) type depending whether it's an in-app or system keyboard.
+
+## Syntax
+```java
+KMKeyboard KMManager.getKMKeyboard(KeyboardType type)
+```
+### Parameters
+type
+: `KeyboardType.KEYBOARD_TYPE_INAPP` or `KeyboardType.KEYBOARD_TYPE_SYSTEM`
+
+### Returns
+Returns the KMKeyboard object.
+
+## Description
+The `getKMKeyboard()` method returns the KMKeyboard for the specified keyboard type.
+
+## Examples
+
+### Example: Using getKMKeyboard()
+The following script illustrates the use of `getKMKeyboard()`:
+```java
+ KMKeyboard keyboard = KMManager.getKMKeyboard(KeyboardType.KEYBOARD_TYPE_SYSTEM);
+```
diff --git a/android/docs/engine/KMManager/getKeyboardFontFilename.md b/android/docs/engine/KMManager/getKeyboardFontFilename.md
new file mode 100644
index 00000000000..086922224d4
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardFontFilename.md
@@ -0,0 +1,38 @@
+---
+title: KMManager.getKeyboardFontFilename() (Deprecated)
+---
+
+## Summary
+
+(Deprecated) The **`getKeyboardFontFilename()`** method returns the
+selected keyboard's font filename.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardFontFilename()
+```
+
+### Returns
+
+Returns the selected keyboard's font filename as `String` if it has any,
+empty string otherwise.
+
+## Description
+
+Use this method to get the font filename of the selected keyboard.
+
+## Examples
+
+### Example: Using `getKeyboardFontFilename()`
+
+The following script illustrate the use of `getKeyboardFontFilename()`:
+
+``` javascript
+ String fontFilename = KMManager.getKeyboardFontFilename();
+```
+
+## See also
+
+- [`getKeyboardFontTypeface()` (Deprecated)](getKeyboardFontTypeface)
+- [`getFontTypeface()`](getFontTypeface)
diff --git a/android/docs/engine/KMManager/getKeyboardFontTypeface.md b/android/docs/engine/KMManager/getKeyboardFontTypeface.md
new file mode 100644
index 00000000000..1f414ce14fd
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardFontTypeface.md
@@ -0,0 +1,46 @@
+---
+title: KMManager.getKeyboardFontTypeface() (Deprecated)
+---
+
+## Summary
+
+(Deprecated) The **`getKeyboardFontTypeface()`** method creates a new
+typeface from the selected keyboard's font.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardFontTypeface(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns the new typeface created from the selected keyboard's font if it
+exists, `null` otherwise.
+
+## Description
+
+Use this method to create a new typeface from the selected keyboard's
+font if it has any.
+
+## Examples
+
+### Example: Using `getKeyboardFontTypeface()`
+
+The following script illustrate the use of `getKeyboardFontTypeface()`:
+
+``` javascript
+ KMTextView textView = (KMTextView) findViewById(R.id.kmTextView);
+ Typeface fontTypeface = KMManager.getKeyboardFontTypeface(this);
+ textView.setTypeface(fontTypeface);
+```
+
+## See also
+
+- [`getFontTypeface()`](getFontTypeface)
+- [`getKeyboardFontFilename()` (Deprecated)](getKeyboardFontFilename)
diff --git a/android/docs/engine/KMManager/getKeyboardHeight.md b/android/docs/engine/KMManager/getKeyboardHeight.md
new file mode 100644
index 00000000000..c459e45ddba
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardHeight.md
@@ -0,0 +1,42 @@
+---
+title: KMManager.getKeyboardHeight()
+---
+
+## Summary
+
+The **`getKeyboardHeight()`** method returns the height of the keyboard
+frame.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardHeight(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns the height of the keyboard frame in *density-independent pixels
+(dp)*.
+
+## Description
+
+Use this method to get the height of the keyboard frame.
+
+## Examples
+
+### Example: Using `getKeyboardHeight()`
+
+The following script illustrate the use of `getKeyboardHeight()`:
+
+``` javascript
+ int keyboardHeight = KMManager.getKeyboardHeight(this);
+```
+
+## See also
+
+- [applyKeyboardHeight](applyKeyboardHeight)
diff --git a/android/docs/engine/KMManager/getKeyboardIndex.md b/android/docs/engine/KMManager/getKeyboardIndex.md
new file mode 100644
index 00000000000..d1f9616350d
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardIndex.md
@@ -0,0 +1,53 @@
+---
+title: KMManager.getKeyboardIndex()
+---
+
+## Summary
+
+The **`getKeyboardIndex()`** method returns index number of the
+specified keyboard in keyboards list.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardIndex(Context context, String keyboardID, String languageID)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`keyboardID`
+: ID of the keyboard.
+
+`languageID`
+: ID of the associated language.
+
+### Returns
+
+Returns 0-based index number of the specified keyboard if exists in
+keyboards list, -1 otherwise.
+
+## Description
+
+Use this method to get the index number of the keyboard with given
+keyboard ID and language ID if it exists in keyboards list.
+
+## Examples
+
+### Example: Using `getKeyboardIndex()`
+
+The following script illustrate the use of `getKeyboardIndex()`:
+
+``` javascript
+ int index = KMManager.getKeyboardIndex(this, "tamil99m", "ta");
+```
+
+## See also
+
+- [`getCurrentKeyboardIndex()`](getCurrentKeyboardIndex)
+- [`getCurrentKeyboardInfo()`](getCurrentKeyboardInfo)
+- [`getKeyboardInfo()`](getKeyboardInfo)
+- [`getKeyboardsList()`](getKeyboardsList)
+- [`keyboardExists()`](keyboardExists)
diff --git a/android/docs/engine/KMManager/getKeyboardInfo.md b/android/docs/engine/KMManager/getKeyboardInfo.md
new file mode 100644
index 00000000000..5838e0ca233
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardInfo.md
@@ -0,0 +1,98 @@
+---
+title: KMManager.getKeyboardInfo()
+---
+
+## Summary
+
+The **`getKeyboardInfo()`** method returns information of the specified
+keyboard.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardInfo(Context context, int index)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`index`
+: 0-based position of the keyboard in keyboards list.
+
+### Returns
+
+Returns an information `Keyboard` type of the specified keyboard.
+
+## Description
+
+Use this method to get details of the keyboard at given position in
+keyboards list. Details include keyboard ID, language ID, keyboard name,
+language name and fonts.
+
+
+
+------------------------------------------------------------------------
+
+## Syntax (Deprecated)
+
+``` javascript
+KMManager.getKeyboardInfo(Context context, int index)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`index`
+: 0-based position of the keyboard in keyboards list.
+
+### Returns
+
+(Deprecated) Returns an information dictionary of the specified keyboard
+with keys and values defined as `HashMap`.
+
+## Description
+
+Use this method to get details of the keyboard at given position in
+keyboards list. Details include keyboard ID, language ID, keyboard name,
+language name and fonts.
+
+## Examples
+
+### Example: Using `getKeyboardInfo()`
+
+The following script illustrate the use of `getKeyboardInfo()`:
+
+``` javascript
+ Keyboard keyboardInfo = KMManager.getKeyboardInfo(this, 1);
+ if (keyboardInfo != null) {
+ String packageID = keyboardInfo.getPackageID();
+ String keyboardId = keyboardInfo.getKeyboardID();
+ String keyboardName = keyboardInfo.getKeyboardName();
+ String languageId = keyboardInfo.getLanguageID();
+ String languageName = keyboardInfo.getLanguageName();
+ String version = keyboardInfo.getVersion();
+ String font = keyboardInfo.getFont();
+ String oskFont = keyboardInfo.getOSKFont();
+ //
+ }
+```
+
+## History
+
+Added syntax for returning Keyboard type in Keyman Engine for Android
+14.0.
+
+Deprecated syntax for returning the HashMap<String key, String
+value> in Keyman Engine for Android 14.0
+
+## See also
+
+- [`getCurrentKeyboardIndex()`](getCurrentKeyboardIndex)
+- [`getCurrentKeyboardInfo()`](getCurrentKeyboardInfo)
+- [`getKeyboardIndex()`](getKeyboardIndex)
+- [`getKeyboardsList()`](getKeyboardsList)
+- [`keyboardExists()`](keyboardExists)
diff --git a/android/docs/engine/KMManager/getKeyboardOskFontFilename.md b/android/docs/engine/KMManager/getKeyboardOskFontFilename.md
new file mode 100644
index 00000000000..8465ea4e8c4
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardOskFontFilename.md
@@ -0,0 +1,41 @@
+---
+title: KMManager.getKeyboardOskFontFilename()
+---
+
+## Summary
+
+The **`getKeyboardOskFontFilename()`** method returns the selected
+keyboard's OSK font filename.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardOskFontFilename()
+```
+
+### Returns
+
+Returns the selected keyboard's OSK font filename as `String` if it has
+any, empty string otherwise.
+
+## Description
+
+Use this method to get the OSK font filename of the selected keyboard.
+
+## Examples
+
+### Example: Using `getKeyboardOskFontFilename()`
+
+The following script illustrate the use of
+`getKeyboardOskFontFilename()`:
+
+``` javascript
+ String oskFontFilename = KMManager.getKeyboardOskFontFilename();
+```
+
+## See also
+
+- [`getKeyboardTextFontFilename()`](getKeyboardTextFontFilename)
+- [`getKeyboardOskFontTypeface()`](getKeyboardOskFontTypeface)
+- [`getKeyboardTextFontTypeface()`](getKeyboardTextFontTypeface)
+- [`getFontTypeface()`](getFontTypeface)
diff --git a/android/docs/engine/KMManager/getKeyboardOskFontTypeface.md b/android/docs/engine/KMManager/getKeyboardOskFontTypeface.md
new file mode 100644
index 00000000000..4344ab7ce65
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardOskFontTypeface.md
@@ -0,0 +1,47 @@
+---
+title: KMManager.getKeyboardOskFontTypeface()
+---
+
+## Summary
+
+The **`getKeyboardOskFontTypeface()`** method creates a new typeface
+from the selected keyboard's OSK font.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardOskFontTypeface(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns the new typeface created from the selected keyboard's OSK font
+if it exists, `null` otherwise.
+
+## Description
+
+Use this method to create a new typeface from the selected keyboard's
+OSK font if it has any.
+
+## Examples
+
+### Example: Using `getKeyboardOskFontTypeface()`
+
+The following script illustrate the use of
+`getKeyboardOskFontTypeface()`:
+
+``` javascript
+ Typeface oskFontTypeface = KMManager.getKeyboardOskFontTypeface(this);
+```
+
+## See also
+
+- [`getKeyboardTextFontTypeface()`](getKeyboardTextFontTypeface)
+- [`getKeyboardOskFontFilename()`](getKeyboardOskFontFilename)
+- [`getKeyboardTextFontFilename()`](getKeyboardTextFontFilename)
+- [`getFontTypeface()`](getFontTypeface)
diff --git a/android/docs/engine/KMManager/getKeyboardState.md b/android/docs/engine/KMManager/getKeyboardState.md
new file mode 100644
index 00000000000..a05353e58cf
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardState.md
@@ -0,0 +1,68 @@
+---
+title: KMManager.getKeyboardState()
+---
+
+## Summary
+
+The **`getKeyboardState()`** method returns the specified keyboard's
+state.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardState(Context context, String keyboardID, String languageID)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`keyboardID`
+: ID of the keyboard.
+
+`languageID`
+: ID of the associated language.
+
+### Returns
+
+Returns the state of the specified keyboard as one of
+`KEYBOARD_STATE_UNDEFINED`, `KEYBOARD_STATE_NEEDS_DOWNLOAD`,
+`KEYBOARD_STATE_NEEDS_UPDATE` or `KEYBOARD_STATE_UP_TO_DATE`.
+
+## Description
+
+Use this method to get the state of the specified keyboard. It returns
+`KEYBOARD_STATE_UNDEFINED` if keyboardID or languageID is not specified
+(`null` or empty string). It returns `KEYBOARD_STATE_NEEDS_DOWNLOAD` if
+the specified keyboard does not exist in `assets/languages/` folder. In
+all other cases it returns either `KEYBOARD_STATE_UP_TO_DATE` or
+`KEYBOARD_STATE_NEEDS_UPDATE`. Note: Only keyboards provided by Keyman
+may return `KEYBOARD_STATE_NEEDS_UPDATE`. If you have a custom keyboard,
+you need to implement your own method to check whether or not it needs
+update. `KEYBOARD_STATE_UP_TO_DATE` does not necessarily mean that the
+keyboard is in fact up to date, you need to make sure language list has
+recently been displayed (see
+[`showLanguageList() (Deprecated)`](showLanguageList)) without failure
+to access Keyman server to be certain.
+
+## Examples
+
+### Example: Using `getKeyboardState()`
+
+The following script illustrate the use of `getKeyboardState()`:
+
+``` javascript
+ KeyboardState keyboardState = KMManager.getKeyboardState(this, "tamil99m", "ta");
+ switch (keyboardState) {
+ case KEYBOARD_STATE_NEEDS_DOWNLOAD:
+ // We need to download this keyboard or manually add it into assets/languages/ folder.
+ break;
+ case KEYBOARD_STATE_UP_TO_DATE:
+ case KEYBOARD_STATE_NEEDS_UPDATE:
+ // We can safely use this keyboard since it exists in assets/languages/ folder.
+ break;
+ default:
+ // Undefined state
+ }
+```
diff --git a/android/docs/engine/KMManager/getKeyboardTextFontFilename.md b/android/docs/engine/KMManager/getKeyboardTextFontFilename.md
new file mode 100644
index 00000000000..5c381cc23a0
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardTextFontFilename.md
@@ -0,0 +1,41 @@
+---
+title: KMManager.getKeyboardTextFontFilename()
+---
+
+## Summary
+
+The **`getKeyboardTextFontFilename()`** method returns the selected
+keyboard's text font filename.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardTextFontFilename()
+```
+
+### Returns
+
+Returns the selected keyboard's text font filename as `String` if it has
+any, empty string otherwise.
+
+## Description
+
+Use this method to get the text font filename of the selected keyboard.
+
+## Examples
+
+### Example: Using `getKeyboardTextFontFilename()`
+
+The following script illustrate the use of
+`getKeyboardTextFontFilename()`:
+
+``` javascript
+ String textFontFilename = KMManager.getKeyboardTextFontFilename();
+```
+
+## See also
+
+- [`getKeyboardOskFontFilename()`](getKeyboardOskFontFilename)
+- [`getKeyboardTextFontTypeface()`](getKeyboardTextFontTypeface)
+- [`getKeyboardOskFontTypeface()`](getKeyboardOskFontTypeface)
+- [`getFontTypeface()`](getFontTypeface)
diff --git a/android/docs/engine/KMManager/getKeyboardTextFontTypeface.md b/android/docs/engine/KMManager/getKeyboardTextFontTypeface.md
new file mode 100644
index 00000000000..05e5bcd8c99
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardTextFontTypeface.md
@@ -0,0 +1,49 @@
+---
+title: KMManager.getKeyboardTextFontTypeface()
+---
+
+## Summary
+
+The **`getKeyboardTextFontTypeface()`** method creates a new typeface
+from the selected keyboard's text font.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardTextFontTypeface(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns the new typeface created from the selected keyboard's text font
+if it exists, `null` otherwise.
+
+## Description
+
+Use this method to create a new typeface from the selected keyboard's
+text font if it has any.
+
+## Examples
+
+### Example: Using `getKeyboardTextFontTypeface()`
+
+The following script illustrate the use of
+`getKeyboardTextFontTypeface()`:
+
+``` javascript
+ KMTextView textView = (KMTextView) findViewById(R.id.kmTextView);
+ Typeface textFontTypeface = KMManager.getKeyboardTextFontTypeface(this);
+ textView.setTypeface(textFontTypeface);
+```
+
+## See also
+
+- [`getKeyboardOskFontTypeface()`](getKeyboardOskFontTypeface)
+- [`getKeyboardTextFontFilename()`](getKeyboardTextFontFilename)
+- [`getKeyboardOskFontFilename()`](getKeyboardOskFontFilename)
+- [`getFontTypeface()`](getFontTypeface)
diff --git a/android/docs/engine/KMManager/getKeyboardsList.md b/android/docs/engine/KMManager/getKeyboardsList.md
new file mode 100644
index 00000000000..26c520953b7
--- /dev/null
+++ b/android/docs/engine/KMManager/getKeyboardsList.md
@@ -0,0 +1,87 @@
+---
+title: KMManager.getKeyboardsList()
+---
+
+## Summary
+
+The **`getKeyboardsList()`** method returns the keyboards list.
+
+## Syntax
+
+``` javascript
+KMManager.getKeyboardsList(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+Returns keyboards list as `List` if it exists, `null`
+otherwise.
+
+## Description
+
+Use this method to get details of all keyboard's in keyboards menu.
+
+
+
+------------------------------------------------------------------------
+
+## Syntax (Deprecated)
+
+``` javascript
+KMManager.getKeyboardsList(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+
+(Deprecated) Returns keyboards list as
+`ArrayList>` if it exists, `null`
+otherwise.
+
+## Description
+
+Use this method to get details of all keyboard's in keyboards menu.
+
+
+
+------------------------------------------------------------------------
+
+## Examples
+
+### Example: Using `getKeyboardsList()`
+
+The following script illustrate the use of `getKeyboardsList()`:
+
+``` javascript
+ List keyboardsList = KMManager.getKeyboardsList(this);
+ if ((keyboardsList != null) && keyboardsList.size() < 2) {
+ // Add another Keyboard
+ Keyboard kbd = new Keyboard(...);
+ KMManager.addKeyboard(kbd);
+ }
+```
+
+## History
+
+Added syntax for returning list of Keyboard type in Keyman Engine for
+Android 14.0.
+
+Deprecated syntax for returning the list of HashMap<String key,
+String value> in Keyman Engine for Android 14.0
+
+## See also
+
+- [`getCurrentKeyboardIndex()`](getCurrentKeyboardIndex)
+- [`getCurrentKeyboardInfo()`](getCurrentKeyboardInfo)
+- [`getKeyboardIndex()`](getKeyboardIndex)
+- [`getKeyboardInfo()`](getKeyboardInfo)
+- [`keyboardExists()`](keyboardExists)
diff --git a/android/docs/engine/KMManager/getLanguageCorrectionPreferenceKey.md b/android/docs/engine/KMManager/getLanguageCorrectionPreferenceKey.md
new file mode 100644
index 00000000000..42f75f72745
--- /dev/null
+++ b/android/docs/engine/KMManager/getLanguageCorrectionPreferenceKey.md
@@ -0,0 +1,40 @@
+---
+title: KMManager.getLanguageCorrectionPreferenceKey()
+---
+
+## Summary
+A constant to use as a shared preference key to store whether the LMLayer should enable corrections for a given language.
+
+## Syntax
+```java
+KMManager.getLanguageCorrectionPreferenceKey(String langID)
+```
+
+### Parameters
+
+`langId`
+: The BCP 47 language ID
+
+### Returns
+Returns the language correction preference key as a String.
+
+## Description
+Use this method to get a String that can be used as a shared preference key.
+
+The usage of this preference key is experimental and likely to be deprecated in a future release of Keyman Engine for Android.
+
+## Examples
+
+### Example: Using `getLanguageCorrectionPreferenceKey()`
+The following script illustrates the use of `getLanguageCorrectionPreferenceKey()`:
+
+```java
+ SharedPreferences prefs = ...; // Get the app's shared preferences
+ String langID = "ta";
+ boolean key = KMManager.getLanguageCorrectionPreferenceKey(langID);
+
+ boolean mayCorrect = prefs.getBoolean(key, true);
+```
+
+## See also
+* [getLanguagePredictionPreferenceKey()](getLanguagePredictionPreferenceKey)
diff --git a/android/docs/engine/KMManager/getLanguagePredictionPreferenceKey.md b/android/docs/engine/KMManager/getLanguagePredictionPreferenceKey.md
new file mode 100644
index 00000000000..fe5102bcdb9
--- /dev/null
+++ b/android/docs/engine/KMManager/getLanguagePredictionPreferenceKey.md
@@ -0,0 +1,40 @@
+---
+title: KMManager.getLanguagePredictionPreferenceKey()
+---
+
+## Summary
+A constant to use as a shared preference key to store whether the LMLayer should enable suggestions for a given language.
+
+## Syntax
+```java
+KMManager.getLanguagePredictionPreferenceKey(String langID)
+```
+
+### Parameters
+
+`langId`
+: The BCP 47 language ID
+
+### Returns
+Returns the language prediction preference key as a String.
+
+## Description
+Use this method to get a String that can be used as a shared preference key.
+
+The usage of this preference key is experimental and likely to be deprecated in a future release of Keyman Engine for Android.
+
+## Examples
+
+### Example: Using `getLanguagePredictionPreferenceKey()`
+The following script illustrates the use of `getLanguagePredictionPreferenceKey()`:
+
+```java
+ SharedPreferences prefs = ...; // Get the app's shared preferences
+ String langID = "ta";
+ boolean key = KMManager.getLanguagePredictionPreferenceKey(langID);
+
+ boolean mayPredict = prefs.getBoolean(key, true);
+```
+
+## See also
+* [getLanguageCorrectionPreferenceKey()](getLanguageCorrectionPreferenceKey)
diff --git a/android/docs/engine/KMManager/getLatestKeyboardFileVersion.md b/android/docs/engine/KMManager/getLatestKeyboardFileVersion.md
new file mode 100644
index 00000000000..57b227e1b1b
--- /dev/null
+++ b/android/docs/engine/KMManager/getLatestKeyboardFileVersion.md
@@ -0,0 +1,61 @@
+---
+title: KMManager.getLatestKeyboardFileVersion()
+---
+
+## Summary
+
+The **`getLatestKeyboardFileVersion()`** method returns the specified
+keyboard's latest file version number.
+
+## Syntax
+
+``` javascript
+KMManager.getLatestKeyboardFileVersion(Context context, String packageID, String keyboardID)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`packageID`
+: ID of the package.
+
+`keyboardID`
+: ID of the keyboard.
+
+### Returns
+
+Returns the specified keyboard's latest file version number as `String`
+if the keyboard exists, `null` otherwise.
+
+## Description
+
+Use this method to get the latest file version number of the specified
+keyboard if it exists in the `assets/cloud/` or `assets/packages/`
+folder.
+
+If packageID is `cloud`, this method determines the latest file version
+by the filename.
+
+If packageID is something else, the metadata file
+assets/packageID/kmp.json is parsed to determine the keyboard version.
+
+## Examples
+
+### Example: Using `getLatestKeyboardFileVersion()`
+
+The following script illustrate the use of
+`getLatestKeyboardFileVersion()`:
+
+``` javascript
+ String latestVersion = getLatestKeyboardFileVersion(this, "cloud", "tamil99m");
+ if (latestVersion != null) {
+ // If we assume that there are 2 tamil99m keyboard files in assets/cloud/ folder
+ // with filenames; tamil99m-1.0.js and tamil99m-1.1.js
+ // then latestVersion = "1.1"
+ }
+ else {
+ // This keyboard does not exist in assets/cloud/ folder!
+ }
+```
diff --git a/android/docs/engine/KMManager/getLexicalModelInfo.md b/android/docs/engine/KMManager/getLexicalModelInfo.md
new file mode 100644
index 00000000000..38729696ea8
--- /dev/null
+++ b/android/docs/engine/KMManager/getLexicalModelInfo.md
@@ -0,0 +1,47 @@
+---
+title: KMManager.getLexicalModelInfo()
+---
+
+## Summary
+
+The `getLexicalModelInfo()` method returns returns dictionary information of the specified lexical model.
+
+## Syntax
+
+```java
+HashMap KMManager.getLexicalModelInfo(Context context, int index)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`index`
+: Zero-based index of the installed lexical model
+
+### Returns
+Returns a dictionary `Hashmap` containing information on the specified lexical model.
+The keys for the HashMap are:
+| Keys |
+|---------------------------------------------|
+| `KMManager.KMKey_PackageID` |
+| `KMManager.KMKey_LexicalModelID` |
+| `KMManager.KMKey_LexicalModelName` |
+| `KMManager.KMKey_LexicalModelVersion` |
+| `KMManager.KMKey_LanguageID` |
+| `KMManager.KMKey_LanguageName` |
+| `KMManager.KMKey_CustomHelpLink` (optional) |
+
+## Description
+Use this method to get details about a specified lexical model. (language and keyboard information)
+
+## Examples
+
+### Example: Using `getLexicalModelInfo()`
+
+The following code illustrates the use of `getLexicalModelInfo()`:
+```java
+ // Gets information on the first installed lexical model
+ HashMap lexicalModelInfo = KMManager.getLexicalModelInfo(context, 0);
+```
diff --git a/android/docs/engine/KMManager/getLexicalModelsList.md b/android/docs/engine/KMManager/getLexicalModelsList.md
new file mode 100644
index 00000000000..979a6620e45
--- /dev/null
+++ b/android/docs/engine/KMManager/getLexicalModelsList.md
@@ -0,0 +1,32 @@
+---
+title: KMManager.getLexicalModelsList()
+---
+
+## Summary
+The **getLexicalModelsList()** method returns the array of lexical models list.
+
+## Syntax
+```java
+ArrayList> KMManager.getLexicalModelsList(Context context)
+```
+
+### Parameters
+`context`
+: The context.
+
+### Returns
+Returns a list of all the installed lexical models.
+
+## Description
+Use this method to get details of all the installed lexical models.
+
+## Examples
+
+### Example: Using getLexicalModelsList()
+The following script illustrate the use of `getLexicalModelsList()`:
+```java
+ ArrayList> lexicalModelList = KMManager.getLexicalModelsList(context);
+```
+
+## See also
+* [getLexicalModelInfo()](getLexicalModelInfo)
diff --git a/android/docs/engine/KMManager/getLongpressDelay.md b/android/docs/engine/KMManager/getLongpressDelay.md
new file mode 100644
index 00000000000..6fb0e872dcb
--- /dev/null
+++ b/android/docs/engine/KMManager/getLongpressDelay.md
@@ -0,0 +1,38 @@
+---
+title: KMManager.getLongpressDelay()
+---
+
+## Summary
+
+The `getLongpressDelay()` method returns from stored preference the number of milliseconds to trigger a longpress gesture.
+Defaults to 500 milliseconds.
+
+## Syntax
+
+```java
+int KMManager.getLongpressDelay()
+```
+
+### Returns
+Returns the number of milliseconds to trigger a longpress gesture. This preference is stored at the app level and is applied to all Keyman keyboards.
+
+## Description
+Use this method to get details about how long to press a key for longpress keys to appear.
+
+## Examples
+
+### Example: Using `getLongpressDelay()`
+
+The following code illustrates the use of `getLongpressDelay()`:
+```java
+ int currentDelayTimeMS = KMManager.getLongpressDelay();
+
+ currentDelayTimeMS += 250; // ms
+```
+
+## History
+Keyman Engine for Android 18.0: New function.
+
+## See also
+* [sendOptionsToKeyboard](sendOptionsToKeyboard)
+* [setLongpressDelay](setLongpressDelay)
diff --git a/android/docs/engine/KMManager/getMaySendCrashReport.md b/android/docs/engine/KMManager/getMaySendCrashReport.md
new file mode 100644
index 00000000000..84a5ffd5654
--- /dev/null
+++ b/android/docs/engine/KMManager/getMaySendCrashReport.md
@@ -0,0 +1,23 @@
+---
+title: KMManager.getMaySendCrashReport()
+---
+
+## Summary
+The **getMaySendCrashReport()** method returns whether Keyman Engine is allowed to send crash reports over the network to sentry.keyman.com.
+
+## Syntax
+```java
+KMManager.getMaySendCrashReport()
+```
+
+### Returns
+Returns `true` if crash reports can be sent over the network to sentry.keyman.com, `false` otherwise.
+
+## Description
+Use this method to check if Keyman Engine will be accessing the network to send crash reports.
+
+## History
+Added syntax in Keyman Engine for Android 14.0.
+
+## See also
+* [setMaySendCrashReport](setMaySendCrashReport)
diff --git a/android/docs/engine/KMManager/getOrientation.md b/android/docs/engine/KMManager/getOrientation.md
new file mode 100644
index 00000000000..efdfd63db01
--- /dev/null
+++ b/android/docs/engine/KMManager/getOrientation.md
@@ -0,0 +1,37 @@
+---
+title: KMManager.getOrientation()
+---
+
+## Summary
+
+The `getOrientation()` method returns the current orientation of the device.
+
+## Syntax
+
+```java
+KMManager.getOrientation(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+### Returns
+Returns the device orientation as an int, one of:
+
+* `Configuration.ORIENTATION_PORTRAIT` (`1`)
+* `Configuration.ORIENTATION_LANDSCAPE` (`2`)
+* `Configuration.ORIENTATION_UNDEFINED` (`0`)
+
+## Description
+Use this method to get the current orientation of the device
+
+## Examples
+
+### Example: Using `getOrientation()`
+
+The following code illustrates the use of `getOrientation()`:
+```java
+ int orientation = KMManager.getOrientation(context);
+```
diff --git a/android/docs/engine/KMManager/getSpacebarText.md b/android/docs/engine/KMManager/getSpacebarText.md
new file mode 100644
index 00000000000..3b94ffe3be8
--- /dev/null
+++ b/android/docs/engine/KMManager/getSpacebarText.md
@@ -0,0 +1,45 @@
+---
+title: KMManager.getSpacebarText()
+---
+
+## Summary
+
+The `getSpacebarText()` method returns the current text display pattern for the
+spacebar.
+
+## Syntax
+
+```java
+KMManager.SpacebarText KMManager.getSpacebarText()
+```
+
+### Returns
+
+The current text display pattern for the spacebar, one of:
+
+ * `LANGUAGE` - the language name for the keyboard
+ * `KEYBOARD` - the keyboard name
+ * `LANGUAGE_KEYBOARD` - both the language name and the keyboard name,
+ separated by hyphen
+ * `BLANK` - no text to be displayed
+
+## Description
+
+The default text display pattern is `LANGUAGE_KEYBOARD`. The text shown on the
+keyboard may be overridden on a per-keyboard basis with the `displayName`
+parameter of the `setKeyboard()` function.
+
+---
+
+## Example: Using `getSpacebarText()`
+
+The following script illustrates the use of `getSpacebarText()`:
+
+```java
+// get the current spacebar text mode
+KMManager.SpacebarText mode = KMManager.getSpacebarText();
+```
+
+## See also
+
+* [`setSpacebarText()`](setSpacebarText)
diff --git a/android/docs/engine/KMManager/getVersion.md b/android/docs/engine/KMManager/getVersion.md
new file mode 100644
index 00000000000..8ac8b33412d
--- /dev/null
+++ b/android/docs/engine/KMManager/getVersion.md
@@ -0,0 +1,32 @@
+---
+title: KMManager.getVersion()
+---
+
+## Summary
+
+The **`getVersion()`** method returns the version number of Keyman
+Engine.
+
+## Syntax
+
+``` javascript
+KMManager.getVersion()
+```
+
+### Returns
+
+Returns Keyman Engine version number as `String`.
+
+## Description
+
+Use this method to get the version number of Keyman Engine.
+
+## Examples
+
+### Example: Using `getVersion()`
+
+The following script illustrate the use of `getVersion()`:
+
+``` javascript
+ String version = KMManager.getVersion();
+```
diff --git a/android/docs/engine/KMManager/getWindowDensity.md b/android/docs/engine/KMManager/getWindowDensity.md
new file mode 100644
index 00000000000..49ce59e2b21
--- /dev/null
+++ b/android/docs/engine/KMManager/getWindowDensity.md
@@ -0,0 +1,34 @@
+---
+title: KMManager.getWindowDensity()
+---
+
+## Summary
+
+The `getWindowDensity()` method returns the density of the window.
+
+## Syntax
+
+```java
+KMManager.getWindowDensity(Context context)
+```
+
+`context`
+: The context.
+
+### Returns
+Returns the density
+
+## Description
+Use this method to get the [density](https://developer.android.com/reference/android/util/DisplayMetrics#density) of the window. This is a scaling factor for the Density Independent Pixel (DIP) unit.
+
+## Examples
+
+### Example: Using `getWindowDensity()`
+
+The following code illustrates the use of `getWindowDensity()`:
+```java
+ float density = KMManager.getWindowDensity(context);
+```
+
+## See Also
+* [getWindowSize](getWindowSize)
diff --git a/android/docs/engine/KMManager/getWindowSize.md b/android/docs/engine/KMManager/getWindowSize.md
new file mode 100644
index 00000000000..f3bd0a87d51
--- /dev/null
+++ b/android/docs/engine/KMManager/getWindowSize.md
@@ -0,0 +1,39 @@
+---
+title: KMManager.getWindowSize()
+---
+
+## Summary
+
+The `getWindowSize()` method returns the size of the area the window would occupy.
+
+## Syntax
+
+```java
+KMManager.getWindowSize(Context context)
+```
+
+`context`
+: The context.
+
+### Returns
+Returns the window size (native resolution) of the display as (int x, int y).
+
+## Description
+Use this method to get the size of the entire display.
+
+For API level 29 and below, the size of the [entire display minus system decoration areas](https://developer.android.com/reference/android/view/Display#getSize(android.graphics.Point) is returned.
+
+For API level 30 and above, it may or may not include [system decoration areas](https://developer.android.com/reference/android/view/Display)
+
+## Examples
+
+### Example: Using `getWindowSize()`
+
+The following code illustrates the use of `getWindowSize()`:
+```java
+ Point size = KMManager.getWindowSize(context);
+ int screenHeight = size.y;
+```
+
+## See also
+* [getWindowDensity](getWindowDensity)
\ No newline at end of file
diff --git a/android/docs/engine/KMManager/hasConnection.md b/android/docs/engine/KMManager/hasConnection.md
new file mode 100644
index 00000000000..305ed41b09f
--- /dev/null
+++ b/android/docs/engine/KMManager/hasConnection.md
@@ -0,0 +1,38 @@
+---
+title: KMManager.hasConnection()
+---
+
+## Summary
+The **hasConnection()** method returns whether the device has active network connection.
+
+## Syntax
+```java
+KMManager.hasConnection(Context context)
+```
+
+## Parameters
+
+`context`
+: The context
+
+## Returns
+Returns `true` if application's AndroidManifest.xml file has granted
+[Manifest.permission.ACCESS_NETWORK_STATE](https://developer.android.com/reference/android/Manifest.permission#ACCESS_NETWORK_STATE)
+permission and the device has an active network connection, `false` otherwise.
+
+## Description
+Use this method to check if the device has an active network connection. It is important to make sure there is an active network connection before initiating a download or update.
+
+## Examples
+
+### Example: Using `hasConnection()`
+The following script illustrate the use of `hasConnection()`:
+
+```java
+ if (KMManager.hasConnection(this)) {
+ // has network connection
+ }
+ else {
+ // no network connection
+ }
+```
diff --git a/android/docs/engine/KMManager/hideSystemKeyboard.md b/android/docs/engine/KMManager/hideSystemKeyboard.md
new file mode 100644
index 00000000000..f79996c73cd
--- /dev/null
+++ b/android/docs/engine/KMManager/hideSystemKeyboard.md
@@ -0,0 +1,36 @@
+---
+title: KMManager.hideSystemKeyboard()
+---
+
+## Summary
+
+The **`hideSystemKeyboard()`** method hides the system OSK.
+
+## Syntax
+
+``` javascript
+KMManager.hideSystemKeyboard()
+```
+
+## Description
+
+Use this method to hide the system OSK. A common usage is to prevent
+your app from displaying both in-app OSK and the system OSK.
+
+## Examples
+
+### Example: Using `hideSystemKeyboard()`
+
+The following script illustrate the use of `hideSystemKeyboard()`:
+
+``` javascript
+protected void onResume() {
+ super.onResume();
+ KMManager.onResume();
+ KMManager.hideSystemKeyboard();
+ // ...
+```
+
+## See also
+
+- [`onResume()`](onResume)
diff --git a/android/docs/engine/KMManager/index.md b/android/docs/engine/KMManager/index.md
new file mode 100644
index 00000000000..9a8cc4a7969
--- /dev/null
+++ b/android/docs/engine/KMManager/index.md
@@ -0,0 +1,285 @@
+---
+title: KMManager class
+---
+
+## Summary
+
+The **`KMManager`** class provides methods for controlling Keyman Engine
+
+## Syntax
+
+```java
+KMManager.methodName()
+```
+```java
+KMManager.CONSTANT
+```
+
+## Description
+
+The KMManager is the core class which provides most of the methods and constants you will need to develop your apps with Keyman Engine
+
+
+
+## Methods
+
+[`addKeyboard()`](addKeyboard)
+: adds a keyboard into the keyboards list
+
+[`addKeyboardDownloadEventListener()`](addKeyboardDownloadEventListener)
+: adds the specified listener into the list of keyboard download event listeners
+
+[`addKeyboardEventListener()`](addKeyboardEventListener)
+: adds the specified listener into the list of keyboard event listeners
+
+[`addLexicalModel()`](addLexicalModel)
+: adds a lexical model into the lexical models list
+
+[`advanceToNextInputMode()`](advanceToNextInputMode)
+: switch to the next system keyboard input mode
+
+[`advanceToPreviousInputMethod()`](advanceToPreviousInputMethod)
+: switch to the previous system keyboard input mode
+
+[`applyKeyboardHeight()`](applyKeyboardHeight)
+: sets the height of keyboard frame
+
+[`canAddNewKeyboard()`](canAddNewKeyboard)
+: returns whether adding a new keyboard is enabled, like in the keyboard picker menu
+
+[`canRemoveKeyboard()`](canRemoveKeyboard)
+: returns whether removing a keyboard is enabled, like in the keyboard picker menu
+
+[`copyHTMLBannerAssets()`](copyHTMLBannerAssets)
+: copies a folder of HTML banner assets so it's available for your keyboard app's resources
+
+[`createInputView()`](createInputView)
+: creates the input view to be used in InputMethodService
+
+[`deregisterLexicalModel()`](deregisterLexicalModel)
+: deregisters the specified lexical model from the LMLayer so it isn't used
+
+[`executeHardwareKeystroke()`](executeHardwareKeystroke)
+: process the keystroke generated from a physical keyboard
+
+[`getAssociatedLexicalModel()`](getAssociatedLexicalModel)
+: search the installed lexical models list and see if there's an associated model for a given language
+
+[`getBannerHeight()`](getBannerHeight)
+: returns the height of the suggestion banner
+
+[`getCurrentKeyboardIndex()`](getCurrentKeyboardIndex)
+: returns index number of the current keyboard in keyboards list
+
+[`getCurrentKeyboardInfo()`](getCurrentKeyboardInfo)
+: returns information dictionary of the current keyboard
+
+[`getDefaultKeyboard()`](getDefaultKeyboard)
+: returns the keyboard information for the fallback keyboard
+
+[`getFontTypeface()`](getFontTypeface)
+: creates a new typeface from the specified font filename
+
+[`getGlobeKeyAction()`](getGlobeKeyAction)
+: returns the action type of the 'Globe' key
+
+[`getHapticFeedback()`](getHapticFeedback)
+: returns whether the device vibrates as the user types
+
+~~`getKeyboardFontFilename()`~~ `(Deprecated)`
+: use getKeyboardTextFontFilename or getKeyboardOskFontFilename instead
+
+~~`getKeyboardFontTypeface()`~~ `(Deprecated)`
+: use getKeyboardTextFontTypeface or getKeyboardOskFontTypeface instead
+
+[`getKeyboardHeight()`](getKeyboardHeight)
+: returns the height of the keyboard frame
+
+[`getKeyboardIndex()`](getKeyboardIndex)
+: returns index number of the specified keyboard in keyboards list
+
+[`getKeyboardInfo()`](getKeyboardInfo)
+: returns information dictionary of the specified keyboard
+
+[`getKeyboardsList()`](getKeyboardsList)
+: returns the array of keyboards list
+
+[`getKeyboardOskFontFilename()`](getKeyboardOskFontFilename)
+: returns the selected keyboard's OSK font filename
+
+[`getKeyboardOskFontTypeface()`](getKeyboardOskFontTypeface)
+: creates a new typeface from the selected keyboard's OSK font
+
+[`getKeyboardState()`](getKeyboardState)
+: returns the specified keyboard's state
+
+[`getKeyboardTextFontFilename()`](getKeyboardTextFontFilename)
+: returns the selected keyboard's text font filename
+
+[`getKeyboardTextFontTypeface()`](getKeyboardTextFontTypeface)
+: creates a new typeface from the selected keyboard's text font
+
+[`getKMKeyboard()`](getKMKeyboard)
+: returns the KMKeyboard depending whether it's an in-app or system keyboard
+
+[`getLatestKeyboardFileVersion()`](getLatestKeyboardFileVersion)
+: returns the specified keyboard's latest file version number
+
+[`getLexicalModelInfo()`](getLexicalModelInfo)
+: returns dictionary information of the specified lexical model
+
+[`getLexicalModelsList()`](getLexicalModelsList)
+: returns the array of lexical models list
+
+[`getLanguageCorrectionPreferenceKey()`](getLanguageCorrectionPreferenceKey)
+: returns a String to use as a shared preference key to store whether the LMLayer should enable corrections for a given language
+
+[`getLanguagePredictionPreferenceKey()`](getLanguagePredictionPreferenceKey)
+: returns a String to use as a shared preference key to store whether the LMLayer should enable suggestions for a given language.
+
+[`getLongpressDelay()`](getLongpressDelay)
+: returns from stored preference the number of milliseconds to trigger a longpress gesture
+
+[`getMaySendCrashReport()`](getMaySendCrashReport)
+: returns whether Keyman Engine is allowed to send crash reports over the network to sentry.keyman.com
+
+[`getOrientation()`](getOrientation)
+: returns the device's current orientation (Portrait vs Landscape)
+
+[`getSpacebarText()`](getSpacebarText)
+: returns the current text display pattern for the spacebar
+
+[`getVersion()`](getVersion)
+: returns the version number of Keyman Engine
+
+[`getWindowDensity()`](getWindowDensity)
+: returns the density of the window
+
+[`getWindowSize()`](getWindowSize)
+: returns the size of an area the window would occupy
+
+[`hasConnection()`](hasConnection)
+: returns whether the device has active network connection
+
+[`hideSystemKeyboard()`](hideSystemKeyboard)
+: hides the system OSK
+
+[`initialize()`](initialize)
+: initializes the Keyman manager
+
+[`isDebugMode()`](isDebugMode)
+: returns whether debugging of Keyman Engine is enabled
+
+~~`isHelpBubbleEnabled()`~~ `(Deprecated)`
+: returns whether the help bubble is enabled
+
+[`isKeyboardLoaded()`](isKeyboardLoaded)
+: returns whether the specified in-app or system keyboard is loaded
+
+[`keyboardExists()`](keyboardExists)
+: returns whether the specified keyboard exists in keyboards list
+
+[`lexicalModelExists()`](lexicalModelExists)
+: returns whether the specified lexical model exists in lexical models list
+
+[`onConfigurationChanged()`](onConfigurationChanged)
+: performs necessary actions in an InputMethodService's onConfigurationChanged()
+
+[`onDestroy()`](onDestroy)
+: performs necessary actions in an InputMethodService's onDestroy()
+
+[`onPause()`](onPause)
+: performs necessary actions in an Activity's onPause()
+
+[`onResume()`](onResume)
+: performs necessary actions in an Activity's onResume()
+
+[`onStartInput()`](onStartInput)
+: performs necessary actions in an InputMethodService's onStartInput()
+
+[`registerAssociatedLexicalModel()`](registerAssociatedLexicalModel)
+: registers a lexical model with the associated language ID
+
+[`registerLexicalModel()`](registerLexicalModel)
+: registers a lexical model to use with the LMLayer
+
+[`removeKeyboard()`](removeKeyboard)
+: removes the keyboard at specified position from the keyboards list
+
+[`removeKeyboardDownloadEventListener()`](removeKeyboardDownloadEventListener)
+: removes the specified listener from the list of keyboard download event listeners
+
+[`removeKeyboardEventListener()`](removeKeyboardEventListener)
+: removes the specified listener from the list of keyboard event listeners
+
+[`sendOptionsToKeyboard()`](sendOptionsToKeyboard)
+: sends options like longpress delay to the KeymanWeb keyboard
+
+[`setCanAddNewKeyboard()`](setCanAddNewKeyboard)
+: sets whether adding a new keyboard is allowed
+
+[`setCanRemoveKeyboard()`](setCanRemoveKeyboard)
+: sets whether removing a keyboard is allowed, like in the keyboard picker menu
+
+[`setDebugMode()`](setDebugMode)
+: enables or disables debugging of Keyman Engine
+
+[`setDefaultKeyboard()`](setDefaultKeyboard)
+: sets the keyboard information for the fallback keyboard
+
+[`setGlobeKeyAction()`](setGlobeKeyAction)
+: sets an action type for the 'Globe' key
+
+[`setHapticFeedback()`](setHapticFeedback)
+: sets whether the device vibrates as the user types
+
+~~`setHelpBubbleEnabled()`~~ `(deprecated)`
+: enables or disables the help bubble
+
+[`setHTMLBanner()`](setHTMLBanner)
+: sets the contents of an HTML banner for Keyman Engine to display when suggestions aren't available
+
+[`setKeyboard()`](setKeyboard)
+: sets the keyboard to be used
+
+[`setKeyboardPickerFont()`](setKeyboardPickerFont)
+: sets the font for the keyboard picker menu
+
+~~`setKeymanLicense()`~~ `(Deprecated)`
+: sets the developer license/key pair to unlock Keyman Engine
+
+[`setLongpressDelay()`](setLongpressDelay)
+: stores the longpress delay in milliseconds as a preference.
+
+[`setMaySendCrashReport()`](setMaySendCrashReport)
+: sets whether Keyman Engine can send crash reports over the network to sentry.keyman.com
+
+[`setShouldAllowSetKeyboard()`](setShouldAllowSetKeyboard)
+: sets whether Keyman Engine allows setting a keyboard other than the default keyboard
+
+[`setShouldCheckKeyboardUpdates()`](setShouldCheckKeyboardUpdates)
+: sets whether Keyman Engine should check for keyboard updates
+
+[`setSpacebarText()`](setSpacebarText)
+: sets the current text display pattern for the spacebar
+
+[`shouldAllowSetKeyboard()`](shouldAllowSetKeyboard)
+: returns whether Keyman Engine allows setting a keyboard other than the default keyboard
+
+[`shouldCheckKeyboardUpdates()`](shouldCheckKeyboardUpdates)
+: returns whether Keyman Engine should check for keyboard updates
+
+[`showKeyboardPicker()`](showKeyboardPicker)
+: displays the keyboard picker menu
+
+~~`showLanguageList()`~~ `(Deprecated)`
+: displays the language list
+
+[`switchToNextKeyboard()`](switchToNextKeyboard)
+: loads the next available keyboard in keyboards list
+
+[`updateSelectionRange()`](updateSelectionRange)
+: updates the selection range of the current context
+
+[`updateText()`](updateText)
+: updates the current context with the specified text
diff --git a/android/docs/engine/KMManager/initialize.md b/android/docs/engine/KMManager/initialize.md
new file mode 100644
index 00000000000..e4ddd5327d4
--- /dev/null
+++ b/android/docs/engine/KMManager/initialize.md
@@ -0,0 +1,47 @@
+---
+title: KMManager.initialize()
+---
+
+## Summary
+
+The **`initialize()`** method initializes the Keyman manager.
+
+## Syntax
+
+``` javascript
+KMManager.initialize(Context context, KeyboardType keyboardType)
+```
+
+### Parameters
+
+`context`
+: The context that starts the initialization. This is normally an
+ Activity or the application context of an InputMethodService.
+
+`keyboardType`
+: KeyboardType to be used. `KEYBOARD_TYPE_INAPP` or
+ `KEYBOARD_TYPE_SYSTEM`.
+
+## Description
+
+This method is normally called from `onCreate()` method of an Activity
+or InputMethodService.
+
+## Examples
+
+### Example: Using `initialize()`
+
+The following script illustrate the use of `initialize()`:
+
+``` javascript
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // ...
+ KMManager.initialize(this, KeyboardType.KEYBOARD_TYPE_INAPP);
+ // ...
+ // do not call setContentView before initializing the Keyman manager.
+ setContentView(R.layout.activity_main);
+ // ...
+}
+```
diff --git a/android/docs/engine/KMManager/isDebugMode.md b/android/docs/engine/KMManager/isDebugMode.md
new file mode 100644
index 00000000000..5971ef1b9ce
--- /dev/null
+++ b/android/docs/engine/KMManager/isDebugMode.md
@@ -0,0 +1,36 @@
+---
+title: KMManager.isDebugMode()
+---
+
+## Summary
+
+The **`isDebugMode()`** method returns whether debugging of Keyman
+Engine is enabled.
+
+## Syntax
+
+``` javascript
+KMManager.isDebugMode()
+```
+
+### Returns
+
+Returns `true` if debugging is enabled, `false` otherwise.
+
+## Description
+
+Use this method to check if debugging of Keyman Engine is enabled.
+
+## Examples
+
+### Example: Using `isDebugMode()`
+
+The following script illustrate the use of `isDebugMode()`:
+
+``` javascript
+ boolean isDebugEnabled = KMManager.isDebugMode();
+```
+
+## See also
+
+- [`setDebugMode()`](setDebugMode)
diff --git a/android/docs/engine/KMManager/isHelpBubbleEnabled.md b/android/docs/engine/KMManager/isHelpBubbleEnabled.md
new file mode 100644
index 00000000000..a031aee5c91
--- /dev/null
+++ b/android/docs/engine/KMManager/isHelpBubbleEnabled.md
@@ -0,0 +1,42 @@
+---
+title: KMManager.isHelpBubbleEnabled() (Deprecated)
+---
+
+## Summary
+
+The **`isHelpBubbleEnabled()`** method returns whether the help bubble
+is enabled.
+
+## Syntax
+
+``` javascript
+KMManager.isHelpBubbleEnabled()
+```
+
+### Returns
+
+Returns `true` if the help bubble is enabled, `false` otherwise.
+
+## Description
+
+Use this method to check if the help bubble is enabled. This method only
+works for the in-app keyboard since the system-wide keyboard never
+displays a help bubble.
+
+## Examples
+
+### Example: Using `isHelpBubbleEnabled()`
+
+The following script illustrate the use of `isHelpBubbleEnabled()`:
+
+``` javascript
+ boolean isHelpBubbleEnabled = KMManager.isHelpBubbleEnabled();
+```
+
+## History
+
+Deprecated syntax in Keyman Engine for Android 16.0
+
+## See also
+
+- [`setHelpBubbleEnabled()`](setHelpBubbleEnabled) (Deprecated)
diff --git a/android/docs/engine/KMManager/isKeyboardLoaded.md b/android/docs/engine/KMManager/isKeyboardLoaded.md
new file mode 100644
index 00000000000..8e390c739ec
--- /dev/null
+++ b/android/docs/engine/KMManager/isKeyboardLoaded.md
@@ -0,0 +1,44 @@
+---
+title: KMManager.isKeyboardLoaded()
+---
+
+## Summary
+The **isKeyboardLoaded()** method returns whether the specified in-app or system keyboard is loaded.
+
+## Syntax
+```java
+KMManager.isKeyboardLoaded(KeyboardType type)
+```
+
+### Parameters
+type
+
+: `KeyboardType.KEYBOARD_TYPE_INAPP` or `KeyboardType.KEYBOARD_TYPE_SYSTEM`
+
+If type is `KeyboardType.KEYBOARD_TYPE_UNDEFINED`, the function will return `false`.
+
+### Returns
+Returns `true` if the specified keyboard is loaded, `false` otherwise.
+
+## Description
+Use this method to check if a keyboard is loaded and ready to use.
+
+---
+
+## Examples
+
+### Example: Using isKeyboardLoaded
+The following script illustrate the use of `isKeyboardLoaded`
+
+```java
+if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) {
+ // Get the keyboard info at index 0
+ Keyboard keyboardInfo = KMManager.getKeyboardInfo(this, 0)
+}
+```
+
+## History
+Added syntax in Keyman Engine for Android 14.0.
+
+## See also
+* [setKeyboard](setKeyboard)
diff --git a/android/docs/engine/KMManager/keyboardExists.md b/android/docs/engine/KMManager/keyboardExists.md
new file mode 100644
index 00000000000..cb27b305f64
--- /dev/null
+++ b/android/docs/engine/KMManager/keyboardExists.md
@@ -0,0 +1,53 @@
+---
+title: KMManager.keyboardExists()
+---
+
+## Summary
+
+The **`keyboardExists()`** method returns whether the specified keyboard
+exists in keyboards list.
+
+## Syntax
+
+``` javascript
+KMManager.keyboardExists(Context context, String keyboardID, String languageID)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`keyboardID`
+: ID of the keyboard.
+
+`languageID`
+: ID of the associated language.
+
+### Returns
+
+Returns `true` if the keyboard exists in keyboards list, `false`
+otherwise.
+
+## Description
+
+Use this method to check if the keyboard with given keyboard ID and
+language ID exists in keyboards list.
+
+## Examples
+
+### Example: Using `keyboardExists()`
+
+The following script illustrate the use of `keyboardExists()`:
+
+``` javascript
+ boolean keyboardExists = KMManager.keyboardExists(this, "tamil99m", "ta");
+```
+
+## See also
+
+- [`getCurrentKeyboardIndex()`](getCurrentKeyboardIndex)
+- [`getCurrentKeyboardInfo()`](getCurrentKeyboardInfo)
+- [`getKeyboardIndex()`](getKeyboardIndex)
+- [`getKeyboardInfo()`](getKeyboardInfo)
+- [`getKeyboardsList()`](getKeyboardsList)
diff --git a/android/docs/engine/KMManager/lexicalModelExists.md b/android/docs/engine/KMManager/lexicalModelExists.md
new file mode 100644
index 00000000000..2ec768e4371
--- /dev/null
+++ b/android/docs/engine/KMManager/lexicalModelExists.md
@@ -0,0 +1,42 @@
+---
+title: KMManager.lexicalModelExists()
+---
+
+## Summary
+The **lexicalModelExists()** method returns whether the specified lexical model exists in lexical models list.
+
+## Syntax
+```java
+bool KMManager.lexicalModelExists(Context context, String packageID, String languageID, String modelID)
+```
+
+### Parameters
+`context`
+: The context.
+
+`packageID`
+: ID of the package
+
+`languageID`
+: ID of the associated language
+
+`modelID`
+: ID of the lexical model
+
+### Returns
+Returns `true` if the lexical model exists in the lexical models list, otherwise `false`.
+
+## Description
+Use this method to determine if a lexical model with given package ID, language ID, and model ID exists in the lexical models list.
+
+## Examples
+
+### Example: Using lexicalModelExists()
+The following script illustrate the use of `lexicalModelExists()`:
+```java
+ // Determine if the specified lexical model exists for English (en)
+ boolean exists = KMManager.lexicalModelExists(context, "nrc.en.mtnt", "en", "nrc.en.mtnt");
+```
+
+## See also
+* [`getLexicalModelInfo()`](getLexicalModelInfo)
diff --git a/android/docs/engine/KMManager/onConfigurationChanged.md b/android/docs/engine/KMManager/onConfigurationChanged.md
new file mode 100644
index 00000000000..f83b9634002
--- /dev/null
+++ b/android/docs/engine/KMManager/onConfigurationChanged.md
@@ -0,0 +1,45 @@
+---
+title: KMManager.onConfigurationChanged()
+---
+
+## Summary
+
+The **`onConfigurationChanged()`** method performs necessary actions in
+an InputMethodService's `onConfigurationChanged()`.
+
+## Syntax
+
+``` javascript
+KMManager.onConfigurationChanged(Configuration newConfig)
+```
+
+### Parameters
+
+`newConfig`
+: The new device configuration.
+
+## Description
+
+To be called from an InputMethodService's `onConfigurationChanged()`
+method.
+
+## Examples
+
+### Example: Using `onConfigurationChanged()`
+
+The following script illustrate the use of `onConfigurationChanged()`:
+
+``` javascript
+@Override
+public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ KMManager.onConfigurationChanged(newConfig);
+ // ...
+}
+```
+
+## See also
+
+- [`createInputView()`](createInputView)
+- [`onStartInput()`](onStartInput)
+- [`onDestroy()`](onDestroy)
diff --git a/android/docs/engine/KMManager/onDestroy.md b/android/docs/engine/KMManager/onDestroy.md
new file mode 100644
index 00000000000..84ce45f14fd
--- /dev/null
+++ b/android/docs/engine/KMManager/onDestroy.md
@@ -0,0 +1,39 @@
+---
+title: KMManager.onDestroy()
+---
+
+## Summary
+
+The **`onDestroy()`** method performs necessary actions in an
+InputMethodService's `onDestroy()`.
+
+## Syntax
+
+``` javascript
+KMManager.onDestroy()
+```
+
+## Description
+
+To be called from an InputMethodService's `onDestroy()` method.
+
+## Examples
+
+### Example: Using `onDestroy()`
+
+The following script illustrate the use of `onDestroy()`:
+
+``` javascript
+@Override
+public void onDestroy() {
+ // ...
+ KMManager.onDestroy();
+ super.onDestroy();
+}
+```
+
+## See also
+
+- [`createInputView()`](createInputView)
+- [`onStartInput()`](onStartInput)
+- [`onConfigurationChanged()`](onConfigurationChanged)
diff --git a/android/docs/engine/KMManager/onPause.md b/android/docs/engine/KMManager/onPause.md
new file mode 100644
index 00000000000..e2ff7711b81
--- /dev/null
+++ b/android/docs/engine/KMManager/onPause.md
@@ -0,0 +1,37 @@
+---
+title: KMManager.onPause()
+---
+
+## Summary
+
+The **`onPause()`** method performs necessary actions in an Activity's
+`onPause()`.
+
+## Syntax
+
+``` javascript
+KMManager.onPause()
+```
+
+## Description
+
+To be called from an Activity's `onPause()` method.
+
+## Examples
+
+### Example: Using `onPause()`
+
+The following script illustrate the use of `onPause()`:
+
+``` javascript
+@Override
+protected void onPause() {
+ super.onPause();
+ KMManager.onPause();
+ // ...
+}
+```
+
+## See also
+
+- [`onResume()`](onResume)
diff --git a/android/docs/engine/KMManager/onResume.md b/android/docs/engine/KMManager/onResume.md
new file mode 100644
index 00000000000..0260756a8ac
--- /dev/null
+++ b/android/docs/engine/KMManager/onResume.md
@@ -0,0 +1,37 @@
+---
+title: KMManager.onResume()
+---
+
+## Summary
+
+The **`onResume()`** method performs necessary actions in an Activity's
+`onResume()`.
+
+## Syntax
+
+``` javascript
+KMManager.onResume()
+```
+
+## Description
+
+To be called from an Activity's `onResume()` method.
+
+## Examples
+
+### Example: Using `onResume()`
+
+The following script illustrate the use of `onResume()`:
+
+``` javascript
+@Override
+protected void onResume() {
+ super.onResume();
+ KMManager.onResume();
+ // ...
+}
+```
+
+## See also
+
+- [`onPause()`](onPause)
diff --git a/android/docs/engine/KMManager/onStartInput.md b/android/docs/engine/KMManager/onStartInput.md
new file mode 100644
index 00000000000..17e6c986da6
--- /dev/null
+++ b/android/docs/engine/KMManager/onStartInput.md
@@ -0,0 +1,49 @@
+---
+title: KMManager.onStartInput()
+---
+
+## Summary
+
+The **`onStartInput()`** method performs necessary actions in an
+InputMethodService's `onStartInput()`.
+
+## Syntax
+
+``` javascript
+KMManager.onStartInput(EditorInfo attribute, boolean restarting)
+```
+
+### Parameters
+
+`attribute`
+: Description of the type of text being edited.
+
+`restarting`
+: Set to true if we are restarting input on the same text field as
+ before.
+
+## Description
+
+To be called from an InputMethodService's `onStartInput()` method.
+
+## Examples
+
+### Example: Using `onStartInput()`
+
+The following script illustrate the use of `onStartInput()`:
+
+``` javascript
+@Override
+public void onStartInput(EditorInfo attribute, boolean restarting) {
+ // ...
+ super.onStartInput(attribute, restarting);
+ KMManager.onStartInput(attribute, restarting);
+ // ...
+}
+```
+
+## See also
+
+- [`createInputView()`](createInputView)
+- [`onConfigurationChanged()`](onConfigurationChanged)
+- [`onDestroy()`](onDestroy)
diff --git a/android/docs/engine/KMManager/registerAssociatedLexicalModel.md b/android/docs/engine/KMManager/registerAssociatedLexicalModel.md
new file mode 100644
index 00000000000..32943839d1f
--- /dev/null
+++ b/android/docs/engine/KMManager/registerAssociatedLexicalModel.md
@@ -0,0 +1,36 @@
+---
+title: KMManager.registerAssociatedLexicalModel()
+---
+
+## Summary
+The **registerAssociatedLexicalModel()** method registers a lexical model with the associated language ID.
+
+## Syntax
+```java
+KMManager.registerAssociatedLexicalModel(String langId)
+```
+
+### Parameters
+
+`langId`
+: The BCP 47 language ID
+
+### Returns
+Returns `true` if a new lexical model is loaded (different from the currently loaded lexical model), `false` otherwise.
+
+## Description
+Use this method after switching keyboard languages so the LMLayer will load and use the correct lexical model for generating suggestions.
+
+## Examples
+
+### Example: Using `registerAssociatedLexicalModel()`
+The following script illustrates the use of `registerAssociatedLexicalModel()`:
+
+```java
+ String langId = "ta";
+ KMManager.registerAssociatedLexicalModel(langId);
+```
+
+## See also
+* [getAssociatedLexicalModel()](getAssociatedLexicalModel)
+* [registerLexicalModel()](registerLexicalModel)
diff --git a/android/docs/engine/KMManager/registerLexicalModel.md b/android/docs/engine/KMManager/registerLexicalModel.md
new file mode 100644
index 00000000000..3c8a14589d2
--- /dev/null
+++ b/android/docs/engine/KMManager/registerLexicalModel.md
@@ -0,0 +1,45 @@
+---
+title: KMManager.registerLexicalModel()
+---
+
+## Summary
+The **registerLexicalModel()** method registers a lexical model to use with the LMLayer.
+
+## Syntax
+```java
+KMManager.registerLexicalModel(HashMap lexicalModelInfo)
+```
+
+### Parameters
+`lexicalModelInfo`
+
+: A dictionary of lexical model information with keys and values defined as `HashMaplexicalModelInfo = new HashMap();
+ lexicalModelInfo.put(KMManager.KMKey_PackageID, "example.ta.wordlist");
+ lexicalModelInfo.put(KMManager.KMKey_LanguageID, "ta");
+ lexicalModelInfo.put(KMManager.KMKey_LexicalModelID, "example.ta.wordlist");
+ lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, "1.0");
+ KMManager.addLexicalModel(this, lexicalModelInfo);
+
+ KMManager.registerLexicalModel(lexicalModelInfo);
+```
+
+## See also
+* [addLexicalModel()](addLexicalModel)
+* [deregisterLexicalModel()](deregisterLexicalModel)
diff --git a/android/docs/engine/KMManager/removeKeyboard.md b/android/docs/engine/KMManager/removeKeyboard.md
new file mode 100644
index 00000000000..44bc937126e
--- /dev/null
+++ b/android/docs/engine/KMManager/removeKeyboard.md
@@ -0,0 +1,48 @@
+---
+title: KMManager.removeKeyboard()
+---
+
+## Summary
+
+The **`removeKeyboard()`** method removes the keyboard at specified
+position from the keyboards list.
+
+## Syntax
+
+``` javascript
+KMManager.removeKeyboard(Context context, int position)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`position`
+: 0-based position of the keyboard in the keyboards list.
+
+### Returns
+
+Returns `true` if the keyboard was removed successfully, `false`
+otherwise.
+
+## Description
+
+Use this method to remove a keyboard from the keyboards list. If the
+position is invalid, it will be ignored and the method will return
+`false` without altering the keyboards list.
+
+## Examples
+
+### Example: Using `removeKeyboard()`
+
+The following script illustrate the use of `removeKeyboard()`:
+
+``` javascript
+ // Remove the second keyboard in the list
+ KMManager.removeKeyboard(this, 1);
+```
+
+## See also
+
+- [`addKeyboard()`](addKeyboard)
diff --git a/android/docs/engine/KMManager/removeKeyboardDownloadEventListener.md b/android/docs/engine/KMManager/removeKeyboardDownloadEventListener.md
new file mode 100644
index 00000000000..1da041a6125
--- /dev/null
+++ b/android/docs/engine/KMManager/removeKeyboardDownloadEventListener.md
@@ -0,0 +1,46 @@
+---
+title: KMManager.removeKeyboardDownloadEventListener()
+---
+
+## Summary
+
+The **`removeKeyboardDownloadEventListener()`** method removes the
+specified listener from the list of keyboard download event listeners.
+
+## Syntax
+
+``` javascript
+KMManager.removeKeyboardDownloadEventListener(OnKeyboardDownloadEventListener listener)
+```
+
+### Parameters
+
+`listener`
+: The listener to be removed.
+
+## Description
+
+Use this method to remove the listener to stop receiving keyboard
+download event notifications. The listener must be removed once you
+finished with it.
+
+## Examples
+
+### Example: Using `removeKeyboardDownloadEventListener()`
+
+The following script illustrate the use of
+`removeKeyboardDownloadEventListener()`:
+
+``` javascript
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // ...
+ KMManager.removeKeyboardDownloadEventListener(this);
+ // ...
+ }
+```
+
+## See also
+
+- [`addKeyboardDownloadEventListener()`](addKeyboardDownloadEventListener)
diff --git a/android/docs/engine/KMManager/removeKeyboardEventListener.md b/android/docs/engine/KMManager/removeKeyboardEventListener.md
new file mode 100644
index 00000000000..dc999144d27
--- /dev/null
+++ b/android/docs/engine/KMManager/removeKeyboardEventListener.md
@@ -0,0 +1,45 @@
+---
+title: KMManager.removeKeyboardEventListener()
+---
+
+## Summary
+
+The **`removeKeyboardEventListener()`** method removes the specified
+listener from the list of keyboard event listeners.
+
+## Syntax
+
+``` javascript
+KMManager.removeKeyboardEventListener(OnKeyboardEventListener listener)
+```
+
+### Parameters
+
+`listener`
+: The listener to be removed.
+
+## Description
+
+Use this method to remove the listener to stop receiving keyboard event
+notifications. The listener must be removed once you finished with it.
+
+## Examples
+
+### Example: Using `removeKeyboardEventListener()`
+
+The following script illustrate the use of
+`removeKeyboardEventListener()`:
+
+``` javascript
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // ...
+ KMManager.removeKeyboardEventListener(this);
+ // ...
+ }
+```
+
+## See also
+
+- [`addKeyboardEventListener()`](addKeyboardEventListener)
diff --git a/android/docs/engine/KMManager/sample_template.md b/android/docs/engine/KMManager/sample_template.md
new file mode 100644
index 00000000000..4c013865377
--- /dev/null
+++ b/android/docs/engine/KMManager/sample_template.md
@@ -0,0 +1,41 @@
+---
+title: Summary
+---
+
+The **`method()`** method does . . .
+
+## Syntax
+
+``` javascript
+KMManager.method(Param param1, Param param2)
+```
+
+### Parameters
+
+`param1`
+: Param details.
+
+`param2`
+: Param details.
+
+### Returns
+
+Details of return value if any
+
+## Description
+
+Description.
+
+## Examples
+
+### Example: Using `method()`
+
+The following script illustrate the use of `method()`:
+
+``` javascript
+//Code here
+```
+
+## See also
+
+- [`...`](sample_template)
diff --git a/android/docs/engine/KMManager/sendOptionsToKeyboard.md b/android/docs/engine/KMManager/sendOptionsToKeyboard.md
new file mode 100644
index 00000000000..399dfc79123
--- /dev/null
+++ b/android/docs/engine/KMManager/sendOptionsToKeyboard.md
@@ -0,0 +1,41 @@
+---
+title: KMManager.sendOptionsToKeyboard()
+---
+
+## Summary
+
+The `sendOptionsToKeyboard()` method sends options like longpress delay to the KeymanWeb keyboard.
+
+## Syntax
+
+```java
+KMManager.sendOptionsToKeyboard()
+```
+
+## Description
+Use this method to update options in the KeymanWeb keyboard.
+* Number of milliseconds to trigger a longpress gesture
+
+This method requires a keyboard to be loaded for the values to take effect.
+
+## Examples
+
+### Example: Using `sendOptionsToKeyboard()`
+
+The following code illustrates the use of `sendOptionsToKeyboard()`:
+```java
+ int currentDelayTimeMS = 250;
+
+ // Store currentDelayTimeMS
+ KMManager.setLongpressDelay(currentDelayTimeMS);
+
+ // Apply the keyboard options
+ KMManager.sendOptionsToKeyboard();
+```
+
+## History
+Keyman Engine for Android 18.0: New function.
+
+## See also
+* [getLongpressDelay](getLongpressDelay)
+* [setLongpressDelay](setLongpressDelay)
diff --git a/android/docs/engine/KMManager/setCanAddNewKeyboard.md b/android/docs/engine/KMManager/setCanAddNewKeyboard.md
new file mode 100644
index 00000000000..0b265903b12
--- /dev/null
+++ b/android/docs/engine/KMManager/setCanAddNewKeyboard.md
@@ -0,0 +1,39 @@
+---
+title: KMManager.setCanAddNewKeyboard()
+---
+
+## Summary
+
+The **`setCanAddNewKeyboard()`** method sets whether adding a new
+keyboard is allowed.
+
+## Syntax
+
+``` javascript
+KMManager.setCanAddNewKeyboard(boolean newValue)
+```
+
+### Parameters
+
+`newValue`
+: If `false`, adding a new keyboard is disabled.
+
+## Description
+
+Use this method to enable or disable '+' (add new keyboard) button in
+the keyboard picker menu.
+
+## Examples
+
+### Example: Using `setCanAddNewKeyboard()`
+
+The following script illustrate the use of `setCanAddNewKeyboard()`:
+
+``` javascript
+ // Disable add new keyboard button.
+ KMManager.setCanAddNewKeyboard(false);
+```
+
+## See also
+
+- [`canAddNewKeyboard`](canAddNewKeyboard)
diff --git a/android/docs/engine/KMManager/setCanRemoveKeyboard.md b/android/docs/engine/KMManager/setCanRemoveKeyboard.md
new file mode 100644
index 00000000000..cad610bd7cf
--- /dev/null
+++ b/android/docs/engine/KMManager/setCanRemoveKeyboard.md
@@ -0,0 +1,33 @@
+---
+title: KMManager.setCanRemoveKeyboard()
+---
+
+## Summary
+The **setCanRemoveKeyboard()** method sets whether removing a keyboard is allowed, like in the keyboard picker menu.
+
+## Syntax
+
+```java
+ void KMManager.setCanRemoveKeyboard(boolean newValue)
+```
+
+## Parameters
+
+`newValue`
+: `true` if removing a keyboard is allowed, `false` otherwise.
+
+## Description
+Use this method to grant the end user permission to remove keyboards from the keyboard list. If set to `false`, the installed keyboard list is permanent.
+
+## Examples
+
+### Example: Using `setCanRemoveKeyboard()`
+
+The following script illustrates the use of `setCanRemoveKeyboard()`:
+```java
+ // Disable removing keyboards
+ KMManager.setCanRemoveKeyboard(false);
+```
+
+## See also
+* [`canRemoveKeyboard()`](canRemoveKeyboard)
diff --git a/android/docs/engine/KMManager/setDebugMode.md b/android/docs/engine/KMManager/setDebugMode.md
new file mode 100644
index 00000000000..b1b02620eb2
--- /dev/null
+++ b/android/docs/engine/KMManager/setDebugMode.md
@@ -0,0 +1,39 @@
+---
+title: KMManager.setDebugMode()
+---
+
+## Summary
+
+The **`setDebugMode()`** enables or disables debugging of Keyman Engine.
+
+## Syntax
+
+``` javascript
+KMManager.setDebugMode(boolean value)
+```
+
+### Parameters
+
+`value`
+: Set `true` to enable debugging, `false` to disable.
+
+## Description
+
+Use this method to enable or disable log output generated by Keyman
+Engine. By default debugging is disabled.
+
+## Examples
+
+### Example: Using `setDebugMode()`
+
+The following script illustrate the use of `setDebugMode()`:
+
+``` javascript
+ KMManager.setDebugMode(true);
+ // Debugging is now on
+ // Initialize KMManager here
+```
+
+## See also
+
+- [isDebugMode()](isDebugMode)
diff --git a/android/docs/engine/KMManager/setDefaultKeyboard.md b/android/docs/engine/KMManager/setDefaultKeyboard.md
new file mode 100644
index 00000000000..ac6684dca47
--- /dev/null
+++ b/android/docs/engine/KMManager/setDefaultKeyboard.md
@@ -0,0 +1,55 @@
+---
+title: KMManager.setDefaultKeyboard()
+---
+
+## Summary
+The **setDefaultKeyboard()** method sets the keyboard information for the fallback keyboard.
+
+## Syntax
+```java
+KMManager.setDefaultKeyboard(Keyboard keyboardInfo)
+```
+
+### Parameters
+keyboardInfo
+
+The keyboard information for the default keyboard.
+
+## Description
+The **setDefaultKeyboard()** method sets the keyboard information for the fallback keyboard. If Keyman Engine
+has issues with a current keyboard, KMManager will switch to this fallback keyboard.
+
+A fallback keyboard should also be defined if an app doesn't automatically add a keyboard (requires user selection).
+
+**setDefaultKeyboard** should be called after KMManager.initialize().
+
+## Examples
+
+### Example: Using setDefaultKeyboard()
+The following script illustrates the use of `setDefaultKeyboard()`:
+```java
+ KMManager.initialize(getApplicationContext(), KMManager.KeyboardType.KEYBOARD_TYPE_INAPP);
+
+ // Set the default (fallback) keyboard if this app doesn't automatically call addKeyboard().
+ KMManager.setDefaultKeyboard(
+ new Keyboard(
+ "basic_kbdtam99", // Package ID - filename of the .kmp file
+ "basic_kbdtam99", // Keyboard ID
+ "Tamil 99 Basic", // Keyboard Name
+ "ta", // Language ID
+ "Tamil", // Language Name
+ "1.0", // Keyboard Version
+ null, // URL to help documentation if available
+ "", // URL to latest .kmp file
+ true, // Boolean to show this is a new keyboard in the keyboard picker
+
+ KMManager.KMDefault_KeyboardFont, // Font for the keyboard
+ KMManager.KMDefault_KeyboardFont) // Font for OSK
+ );
+```
+
+## History
+Added syntax in Keyman Engine for Android 14.0.
+
+## See also
+* [getDefaultKeyboard](getDefaultKeyboard)
diff --git a/android/docs/engine/KMManager/setGlobeKeyAction.md b/android/docs/engine/KMManager/setGlobeKeyAction.md
new file mode 100644
index 00000000000..4b56dbb4dcb
--- /dev/null
+++ b/android/docs/engine/KMManager/setGlobeKeyAction.md
@@ -0,0 +1,58 @@
+---
+title: KMManager.setGlobeKeyAction()
+---
+
+## Summary
+The **setGlobeKeyAction()** method sets the short-press action type for the 'Globe' key.
+
+## Syntax
+```java
+KMManager.setGlobeKeyAction(KeyboardType kbType, int action)
+```
+
+### Parameters
+type
+
+: `KeyboardType.KEYBOARD_TYPE_INAPP` or `KeyboardType.KEYBOARD_TYPE_SYSTEM`
+
+action
+
+: The action type. `GLOBE_KEY_ACTION_SHOW_MENU`, `GLOBE_KEY_ACTION_SWITCH_TO_NEXT_KEYBOARD`,
+ `GLOBE_KEY_ACTION_ADVANCE_TO_PREVIOUS_SYSTEM_KEYBOARD`, `GLOBE_KEY_ACTION_ADVANCE_TO_NEXT_SYSTEM_KEYBOARD`,
+ `GLOBE_KEY_ACTION_SHOW_SYSTEM_KEYBOARDS`, `GLOBE_KEY_ACTION_DO_NOTHING`.
+
+## Description
+Use this method to set the short-press action type for the 'Globe' key. Even when the default action type is
+set, Keyman Engine will still use the following default action types when only one Keyman keyboard is installed:
+
+| KeyboardType | # Keyman Keyboards Installed | Globe Key Action Type |
+|----------------------|:----------------------------:|------------------------------------------------------|
+| KEYBOARD_TYPE_INAPP | 1 | GLOBE_KEY_ACTION_SHOW_MENU |
+| KEYBOARD_TYPE_SYSTEM | 1 | GLOBE_KEY_ACTION_ADVANCE_TO_PREVIOUS_SYSTEM_KEYBOARD |
+
+The action `GLOBE_KEY_ACTION_SHOW_MENU` displays the Keyman keyboard picker menu.
+Other enabled system keyboards are also listed at the end of the menu
+
+The action `GLOBE_KEY_ACTION_SWITCH_TO_NEXT_KEYBOARD` switches to the next Keyman keyboard (if more than 1 are installed).
+Otherwise, the Keyman keyboard picker menu is displayed.
+
+The action `GLOBE_KEY_ACTION_ADVANCE_TO_PREVIOUS_SYSTEM_KEYBOARD` switches to the previous system keyboard.
+
+The action `GLOBE_KEY_ACTION_ADVANCE_TO_NEXT_SYSTEM_KEYBOARD` switches to the next system keyboard.
+
+The action `GLOBE_KEY_ACTION_SHOW_SYSTEM_KEYBOARDS` brings up the Android input method picker and
+can only be set for `KEYBOARD_TYPE_SYSTEM`.
+
+## Examples
+
+### Example: Using setGlobeKeyAction
+The following script illustrate the use of `setGlobeKeyAction`
+
+```java
+// Tapping 'Globe' key will switch to the next keyboard
+KMManager.setGlobeKeyAction(KeyboardType.KEYBOARD_TYPE_SYSTEM,
+ GlobeKeyAction.GLOBE_KEY_ACTION_SWITCH_TO_NEXT_KEYBOARD);
+```
+
+## See also
+* [getGlobeKeyAction](getGlobeKeyAction)
diff --git a/android/docs/engine/KMManager/setHTMLBanner.md b/android/docs/engine/KMManager/setHTMLBanner.md
new file mode 100644
index 00000000000..5fcc2c70033
--- /dev/null
+++ b/android/docs/engine/KMManager/setHTMLBanner.md
@@ -0,0 +1,45 @@
+---
+title: KMManager.setHTMLBanner
+---
+
+## Summary
+The **setHTMLBanner()** method sets the HTML banner content for Keyman Engine to display when suggestions aren't available.
+
+## Syntax
+
+```javascript
+KMManager.setHTMLBanner(KeyboardType keyboardType, String content)
+```
+
+### Parameters
+
+`keyboardType`
+: KeyboardType to be used. `KEYBOARD_TYPE_INAPP` or `KEYBOARD_TYPE_SYSTEM`.
+
+`content`
+: HTML content formatted as a string.
+
+### Returns
+Returns `true` if HTML banner is set, `false` otherwise.
+
+## Description
+When suggestions aren't available for a keyboard, an HTML banner is displayed instead.
+Use this method to specify the HTML content to display in the banner to theme your keyboard app.
+
+If the banner theme references assets (like .svg or .css files), ensure you've also called [copyHTMLBannerAssets()](copyHTMLBannerAssets).
+
+Note: The HTML banner needs to be updated whenever the keyboard is reloaded, so call this in `SystemKeyboard.onInitializeInterface(()`.
+
+## Examples
+
+### Example: Using `setHTMLBanner()`
+
+The following script illustrates the use of `setHTMLBanner()`:
+```javascript
+ String KMGRAY_BANNER = "
";
+ // Sets the HTML banner content
+ KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMGRAY_BANNER);
+```
+
+## See also
+* [copyHTMLBannerAssets()](copyHTMLBannerAssets)
diff --git a/android/docs/engine/KMManager/setHapticFeedback.md b/android/docs/engine/KMManager/setHapticFeedback.md
new file mode 100644
index 00000000000..2e3c4422fa8
--- /dev/null
+++ b/android/docs/engine/KMManager/setHapticFeedback.md
@@ -0,0 +1,35 @@
+---
+title: KMManager.setHapticFeedback()
+---
+
+## Summary
+The **setHapticFeedback()** method sets whether the device vibrates as the user types.
+
+## Syntax
+```java
+KMManager.setHapticFeedback(boolean value)
+```
+
+### Parameters
+value
+
+Set `true` to enable device vibrating as the user types, `false` to disable.
+
+## Description
+Use this method to enable or disable haptic feedback. This determines if the device vibrates as the user types.
+By default, haptic feedback is disabled.
+
+## Examples
+
+### Example: Using setHapticFeedback()
+The following script illustrates the use of `setHapticFeedback()`:
+```java
+ // Enable haptic feeedback
+ KMManager.setHapticFeedback(true);
+```
+
+## History
+Keyman Engine for Android 15.0: New function.
+
+## See also
+* [getHapticFeedback](getHapticFeedback)
diff --git a/android/docs/engine/KMManager/setHelpBubbleEnabled.md b/android/docs/engine/KMManager/setHelpBubbleEnabled.md
new file mode 100644
index 00000000000..91839d972f7
--- /dev/null
+++ b/android/docs/engine/KMManager/setHelpBubbleEnabled.md
@@ -0,0 +1,44 @@
+---
+title: KMManager.setHelpBubbleEnabled() (Deprecated)
+---
+
+## Summary
+
+The **`setHelpBubbleEnabled()`** enables or disables the help bubble.
+
+## Syntax
+
+``` javascript
+KMManager.setHelpBubbleEnabled(boolean newValue)
+```
+
+### Parameters
+
+`newValue`
+: Set `true` to enable the help bubble, `false` to disable.
+
+## Description
+
+Use this method to enable or disable the help bubble which displays "Tap
+here to change keyboard" over the 'Globe' key if the user has never used
+it yet. By default it is enabled. This method only works for the in-app
+keyboard since the system-wide keyboard never displays a help bubble.
+
+## Examples
+
+### Example: Using `setHelpBubbleEnabled()`
+
+The following script illustrate the use of `setHelpBubbleEnabled()`:
+
+``` javascript
+ KMManager.setHelpBubbleEnabled(false);
+ // Help bubble is now disabled
+```
+
+## History
+
+Deprecated syntax in Keyman Engine for Android 16.0
+
+## See also
+
+- [isHelpBubbleEnabled()](isHelpBubbleEnabled) (Deprecated)
diff --git a/android/docs/engine/KMManager/setKeyboard.md b/android/docs/engine/KMManager/setKeyboard.md
new file mode 100644
index 00000000000..4a152f2480d
--- /dev/null
+++ b/android/docs/engine/KMManager/setKeyboard.md
@@ -0,0 +1,155 @@
+---
+title: KMManager.setKeyboard()
+---
+
+## Summary
+
+The `setKeyboard()` method sets the active keyboard.
+
+## Syntax
+
+```java
+KMManager.setKeyboard(Context context, Keyboard keyboardInfo)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`keyboardInfo`
+: `Keyboard` type of the keyboard information.
+
+### Returns
+
+Returns `true` if the keyboard was set successfully, `false` otherwise.
+
+## Description
+
+Selects the keyboard identified by the keyboard information, which is
+normally returned by [`getKeyboardInfo()`](getKeyboardInfo).
+
+---
+
+## Syntax
+
+```java
+KMManager.setKeyboard(String packageID, String keyboardID, String languageID)
+KMManager.setKeyboard(String packageID, String keyboardID, String languageID, String keyboardName, String languageName, String kFont, String kOskFont)
+KMManager.setKeyboard(String packageID, String keyboardID, String languageID, String keyboardName, String languageName, String kFont, String kOskFont, String displayName)
+```
+
+### Parameters
+
+`packageID`
+: ID of the keyboard package.
+
+`keyboardID`
+: ID of the keyboard.
+
+`languageID`
+: ID of the associated language.
+
+`keyboardName`
+: Name of the keyboard.
+
+`languageName`
+: Name of the associated language.
+
+`kFont`
+: Filename or description of the font to type with the keyboard. Can be `null`
+ or empty string.
+
+`kOskFont`
+: Filename or description of the font displayed on the keyboard. Can be `null`
+ or empty string.
+
+`displayName`
+: A text string to display on the spacebar identifying this keyboard; if `null`,
+ uses engine default from [`setSpacebarText()`](setSpacebarText); if `""`,
+ shows no text on the spacebar.
+
+### Returns
+
+Returns `true` if the keyboard was set successfully, `false` otherwise.
+
+## Description
+
+Sets the currently active keyboard, along with font and display details.
+
+---
+
+## Syntax
+
+```
+KMManager.setKeyboard(Context context, int position)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`position`
+: 0-based position of the keyboard in the keyboards list.
+
+### Returns
+
+Returns `true` if the keyboard was set successfully, `false` otherwise.
+
+## Description
+
+Sets the currently active keyboard by position in the keyboards list, as
+returned by [`getKeyboardsList()`](getKeyboardsList)
+
+---
+
+## Examples
+
+### Example 1: Using `setKeyboard()`
+
+The following script illustrates the use of `setKeyboard()` with keyboard information:
+
+```java
+// Setting a Keyman keyboard
+Keyboard keyboardInfo = KMManager.getDefaultKeyboard();
+KMManager.setKeyboard(getApplicationContext(), keyboardInfo);
+```
+
+### Example 2: Using `setKeyboard()`
+
+The following script illustrates the use of `setKeyboard()` with package ID,
+keyboard ID, and language ID:
+
+```java
+// Setting a Keyman keyboard
+KMManager.setKeyboard("sil_euro_latin", "sil_euro_latin", "en");
+```
+
+### Example 3: Using `setKeyboard()`
+
+The following script illustrates the use of `setKeyboard()`, providing
+additional font detail:
+
+```java
+// Setting a custom keyboard from the tamil99m keyboard package
+KMManager.setKeyboard("tamil99m", "tamil99m", "ta", "Tamil 99M", "Tamil", "aava1.ttf", "aava1.ttf");
+```
+
+### Example 4: Using `setKeyboard()`
+
+The following script illustrates the use of `setKeyboard()` with keyboard index:
+
+```java
+// Setting a custom keyboard which exists in keyboards list
+int kbIndex = KMManager.getKeyboardIndex(this, "tamil99m", "ta");
+KMManager.setKeyboard(this, kbIndex);
+```
+
+## See also
+
+* [`addKeyboard()`](addKeyboard)
+* [`getDefaultKeyboard()`](getDefaultKeyboard)
+* [`getKeyboardInfo()`](getKeyboardInfo)
+* [`getKeyboardsList()`](getKeyboardsList)
+* [`switchToNextKeyboard()`](switchToNextKeyboard)
diff --git a/android/docs/engine/KMManager/setKeyboardPickerFont.md b/android/docs/engine/KMManager/setKeyboardPickerFont.md
new file mode 100644
index 00000000000..66f73a8926e
--- /dev/null
+++ b/android/docs/engine/KMManager/setKeyboardPickerFont.md
@@ -0,0 +1,33 @@
+---
+title: KMManager.setKeyboardPickerFont()
+---
+
+## Summary
+
+The **`setKeyboardPickerFont()`** sets the font for the keyboard picker
+menu.
+
+## Syntax
+
+``` javascript
+KMManager.setKeyboardPickerFont(Typeface typeface)
+```
+
+### Parameters
+
+`typeface`
+: The font
+
+## Description
+
+Use this method to set a font for the keyboard picker menu.
+
+## Examples
+
+### Example: Using `setKeyboardPickerFont()`
+
+The following script illustrate the use of `setKeyboardPickerFont()`:
+
+``` javascript
+ KMManager.setKeyboardPickerFont(Typeface.createFromAsset(getAssets(), "fonts/custom_font.ttf"));
+```
diff --git a/android/docs/engine/KMManager/setKeymanLicense.md b/android/docs/engine/KMManager/setKeymanLicense.md
new file mode 100644
index 00000000000..37a8980f7bf
--- /dev/null
+++ b/android/docs/engine/KMManager/setKeymanLicense.md
@@ -0,0 +1,47 @@
+---
+title: KMManager.setKeymanLicense() (Deprecated)
+---
+
+## Summary
+
+(Deprecated) The **`setKeymanLicense()`** method sets the developer
+license/key pair to unlock Keyman Engine.
+
+## Syntax
+
+``` javascript
+KMManager.setKeymanLicense(String license, String key)
+```
+
+### Parameters
+
+`license`
+: Your developer license for Keyman Engine for Android.
+
+`key`
+: Your developer key for Keyman Engine for Android.
+
+## Description
+
+You must use this method to set the developer license/key pair before
+initializing the KMManager. You will receive the license/key pair when
+you purchase Keyman Engine for Android.
+
+## Examples
+
+### Example: Using `setKeymanLicense()`
+
+The following script illustrate the use of `setKeymanLicense()`:
+
+``` javascript
+ KMManager.setLicense(“YourLicense”,”YourKey”);
+ // Initialize KMManager here after setting the license
+```
+
+## History
+
+Deprecated in Keyman Engine for Android 12.0
+
+## See also
+
+- [`KMManager.initialize()`](initialize)
diff --git a/android/docs/engine/KMManager/setLongpressDelay.md b/android/docs/engine/KMManager/setLongpressDelay.md
new file mode 100644
index 00000000000..e4666c867bf
--- /dev/null
+++ b/android/docs/engine/KMManager/setLongpressDelay.md
@@ -0,0 +1,42 @@
+---
+title: KMManager.setLongpressDelay()
+---
+
+## Summary
+
+The `setLongpressDelay()` method sets the number of milliseconds to trigger a longpress gesture as a stored preference.
+
+## Syntax
+
+```java
+KMManager.setLongpressDelay(int longpressDelay)
+```
+### Parameter
+`longpressDelay`
+: The number of milliseconds, ranging from 300 ms to 1500 ms.
+
+## Description
+Use this method to store how many milliseconds to trigger a longpress as a preference.
+This preference is stored at the Keyman app level, and is applied to all Keyman keyboards.
+
+## Examples
+
+### Example: Using `setLongpressDelay()`
+
+The following code illustrates the use of `setLongpressDelay()`:
+```java
+ int currentDelayTimeMS = 300;
+
+ // Store currentDelayTimeMS
+ KMManager.setLongpressDelay(currentDelayTimeMS);
+
+ // Apply the keyboard options
+ KMManager.sendOptionsToKeyboard();
+```
+
+## History
+Keyman Engine for Android 18.0: New function.
+
+## See also
+* [getLongpressDelay](getLongpressDelay)
+* [sendOptionsToKeyboard](sendOptionsToKeyboard)
diff --git a/android/docs/engine/KMManager/setMaySendCrashReport.md b/android/docs/engine/KMManager/setMaySendCrashReport.md
new file mode 100644
index 00000000000..0c5cf650ede
--- /dev/null
+++ b/android/docs/engine/KMManager/setMaySendCrashReport.md
@@ -0,0 +1,40 @@
+---
+title: KMManager.setMaySendCrashReport()
+---
+
+## Summary
+The **setMaySendCrashReport()** enables or disables whether Keyman Engine can send crash reports over the
+network to sentry.keyman.com.
+
+## Syntax
+```java
+KMManager.setMaySendCrashReport(boolean value)
+```
+
+### Parameters
+value
+
+Set `true` to enable crash reports to be sent, `false` to disable.
+
+## Description
+Use this method to enable or disable whether Keyman Engine can send crash reports over the network.
+By default sending crash reports is enabled.
+
+If you don't want crash reports to be sent, your app must disable this before `KMManager.initialize()`.
+
+## Examples
+
+### Example: Using setMaySendCrashReport()
+The following script illustrates the use of `setMaySendCrashReport()`:
+```java
+ // Disable crash reports from being sent
+ KMManager.setMaySendCrashReport(false);
+ // Initialize KMManager
+ KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP);
+```
+
+## History
+Added syntax in Keyman Engine for Android 14.0.
+
+## See also
+* [getMaySendCrashReport](getMaySendCrashReport)
diff --git a/android/docs/engine/KMManager/setShouldAllowSetKeyboard.md b/android/docs/engine/KMManager/setShouldAllowSetKeyboard.md
new file mode 100644
index 00000000000..ddec5afae25
--- /dev/null
+++ b/android/docs/engine/KMManager/setShouldAllowSetKeyboard.md
@@ -0,0 +1,44 @@
+---
+title: KMManager.setShouldAllowSetKeyboard()
+---
+
+## Summary
+
+The **`setShouldAllowSetKeyboard()`** method sets whether Keyman Engine
+allows setting a keyboard other than the default keyboard.
+
+## Syntax
+
+``` javascript
+KMManager.setShouldAllowSetKeyboard(boolean value)
+```
+
+### Parameters
+
+`value`
+: If `false`, Keyman Engine will not allow setting a keyboard.
+
+## Description
+
+Use this method to enable or disable setting a keyboard other than the
+default keyboard. If set to `false` Keyman Engine will immediately load
+the default keyboard, and ignore calls to setKeyboard method. It is
+particularly useful if used with Google Play Licensing service in order
+to put the Keyman on-screen keyboard in a locked state if the paid app
+is unlicensed.
+
+## Examples
+
+### Example: Using `setShouldAllowSetKeyboard()`
+
+The following script illustrate the use of
+`setShouldAllowSetKeyboard()`:
+
+``` javascript
+ // Put Keyman on-screen keyboard in locked state if the app is unlicensed.
+ KMManager.setShouldAllowSetKeyboard(false);
+```
+
+## See also
+
+- [`shouldAllowSetKeyboard`](shouldAllowSetKeyboard)
diff --git a/android/docs/engine/KMManager/setShouldCheckKeyboardUpdates.md b/android/docs/engine/KMManager/setShouldCheckKeyboardUpdates.md
new file mode 100644
index 00000000000..91192c59c07
--- /dev/null
+++ b/android/docs/engine/KMManager/setShouldCheckKeyboardUpdates.md
@@ -0,0 +1,40 @@
+---
+title: KMManager.setShouldCheckKeyboardUpdates()
+---
+
+## Summary
+
+The **`setShouldCheckKeyboardUpdates()`** method sets whether Keyman
+Engine should check for keyboard updates.
+
+## Syntax
+
+``` javascript
+KMManager.setShouldCheckKeyboardUpdates(boolean newValue)
+```
+
+### Parameters
+
+`newValue`
+: If `false`, Keyman Engine will not check for keyboard updates.
+
+## Description
+
+Use this method to enable or disable keyboard updates when the keyboard
+picker menu is displayed.
+
+## Examples
+
+### Example: Using `setShouldCheckKeyboardUpdates()`
+
+The following script illustrate the use of
+`setShouldCheckKeyboardUpdates()`:
+
+``` javascript
+ // Disable keyboard updates.
+ KMManager.setShouldCheckKeyboardUpdates(false);
+```
+
+## See also
+
+- [`shouldCheckKeyboardUpdates`](shouldCheckKeyboardUpdates)
diff --git a/android/docs/engine/KMManager/setSpacebarText.md b/android/docs/engine/KMManager/setSpacebarText.md
new file mode 100644
index 00000000000..63788e77df8
--- /dev/null
+++ b/android/docs/engine/KMManager/setSpacebarText.md
@@ -0,0 +1,50 @@
+---
+title: KMManager.setSpacebarText()
+---
+
+## Summary
+
+The `setSpacebarText()` method sets the text display pattern for the
+spacebar.
+
+## Syntax
+
+```java
+KMManager.setSpacebarText(KMManager.SpacebarText mode)
+```
+
+### Parameters
+
+`mode`
+: The display pattern to use for the spacebar, one of:
+
+ * `LANGUAGE` - the language name for the keyboard
+ * `KEYBOARD` - the keyboard name
+ * `LANGUAGE_KEYBOARD` - both the language name and the keyboard name,
+ separated by hyphen
+ * `BLANK` - no text to be displayed
+
+### Returns
+
+No return value.
+
+## Description
+
+The default text display pattern is `LANGUAGE_KEYBOARD`. The text shown on the
+keyboard may be overridden on a per-keyboard basis with the `displayName`
+parameter of the `setKeyboard()` function.
+
+---
+
+## Example: Using `setSpacebarText()`
+
+The following script illustrates the use of `setSpacebarText()`:
+
+```java
+// don't show anything on the spacebar
+KMManager.setSpacebarText(KMManager.SpacebarText.BLANK);
+```
+
+## See also
+
+* [`getSpacebarText()`](getSpacebarText)
diff --git a/android/docs/engine/KMManager/shouldAllowSetKeyboard.md b/android/docs/engine/KMManager/shouldAllowSetKeyboard.md
new file mode 100644
index 00000000000..d816d20c213
--- /dev/null
+++ b/android/docs/engine/KMManager/shouldAllowSetKeyboard.md
@@ -0,0 +1,43 @@
+---
+title: KMManager.shouldAllowSetKeyboard()
+---
+
+## Summary
+
+The **`shouldAllowSetKeyboard()`** method returns whether Keyman Engine
+allows setting a keyboard other than the default keyboard.
+
+## Syntax
+
+``` javascript
+KMManager.shouldAllowSetKeyboard()
+```
+
+### Returns
+
+Returns `true` if Keyman Engine allows setting a keyboard, `false`
+otherwise.
+
+## Description
+
+Use this method to check if Keyman Engine allows setting a keyboard
+other than the default keyboard.
+
+## Examples
+
+### Example: Using `shouldAllowSetKeyboard()`
+
+The following script illustrate the use of `shouldAllowSetKeyboard()`:
+
+``` javascript
+ if (KMManager.shouldAllowSetKeyboard()) {
+ // setting a keyboard is allowed
+ }
+ else {
+ // setting a keyboard is not allowed
+ }
+```
+
+## See also
+
+- [`setShouldAllowSetKeyboard`](setShouldAllowSetKeyboard)
diff --git a/android/docs/engine/KMManager/shouldCheckKeyboardUpdates.md b/android/docs/engine/KMManager/shouldCheckKeyboardUpdates.md
new file mode 100644
index 00000000000..717ca7809ed
--- /dev/null
+++ b/android/docs/engine/KMManager/shouldCheckKeyboardUpdates.md
@@ -0,0 +1,41 @@
+---
+title: KMManager.shouldCheckKeyboardUpdates()
+---
+
+## Summary
+
+The **`shouldCheckKeyboardUpdates()`** method returns whether Keyman
+Engine should check for keyboard updates.
+
+## Syntax
+
+``` javascript
+KMManager.shouldCheckKeyboardUpdates()
+```
+
+### Returns
+
+Returns `true` if Keyman Engine should check for keyboard updates,
+`false` otherwise.
+
+## Description
+
+Use this method to check if Keyman Engine should check for keyboard
+updates when the keyboard picker menu is displayed.
+
+## Examples
+
+### Example: Using `shouldCheckKeyboardUpdates()`
+
+The following script illustrate the use of
+`shouldCheckKeyboardUpdates()`:
+
+``` javascript
+ if (KMManager.shouldCheckKeyboardUpdates()) {
+ // checking keyboard updates is enabled
+ }
+```
+
+## See also
+
+- [`setShouldCheckKeyboardUpdates`](setShouldCheckKeyboardUpdates)
diff --git a/android/docs/engine/KMManager/showKeyboardPicker.md b/android/docs/engine/KMManager/showKeyboardPicker.md
new file mode 100644
index 00000000000..099288dc40c
--- /dev/null
+++ b/android/docs/engine/KMManager/showKeyboardPicker.md
@@ -0,0 +1,51 @@
+---
+title: KMManager.showKeyboardPicker()
+---
+
+## Summary
+
+The **`showKeyboardPicker()`** method displays the keyboard picker menu.
+
+## Syntax
+
+``` javascript
+KMManager.showKeyboardPicker(Context context, KeyboardType kbType)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+`kbType`
+: The keyboard type. `KEYBOARD_TYPE_INAPP` or `KEYBOARD_TYPE_SYSTEM`.
+
+## Description
+
+Use this method to display keyboard picker menu. Normally you do not
+need to call this method explicitly since, by default, Keyman on-screen
+keyboard calls this method to display the keyboard picker menu whenever
+'globe' key is tapped. Multiple calls to this method is unsafe and may
+result in multiple instances of keyboard picker menu being displayed at
+the same time.
+
+## Examples
+
+### Example: Using `showKeyboardPicker()`
+
+The following script illustrate the use of `showKeyboardPicker()`:
+
+``` javascript
+ final Context context = this;
+ ImageButton globeButton = (ImageButton) findViewById(R.id.globeButton);
+ globeButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ KMManager.showKeyboardPicker(context, KMManager.KeyboardType.KEYBOARD_TYPE_INAPP);
+ }
+ });
+```
+
+## See also
+
+- [`showLanguageList() (Deprecated)`](showLanguageList)
diff --git a/android/docs/engine/KMManager/showLanguageList.md b/android/docs/engine/KMManager/showLanguageList.md
new file mode 100644
index 00000000000..3d9dd0db6c5
--- /dev/null
+++ b/android/docs/engine/KMManager/showLanguageList.md
@@ -0,0 +1,50 @@
+---
+title: KMManager.showLanguageList() (Deprecated)
+---
+
+## Summary
+
+(Deprecated) The **`showLanguageList()`** method displays the language
+list.
+
+## Syntax
+
+``` javascript
+KMManager.showLanguageList(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+## Description
+
+The Keyman keyboard picker menu used to call this method to display the
+language list whenever the '+' button was tapped. As of 14.0, keyboards
+are no longer added this way, and this call is removed.
+
+## Examples
+
+### Example: Using `showLanguageList()`
+
+The following script illustrate the use of `showLanguageList()`:
+
+``` javascript
+ final Context context = this;
+ ImageButton languageButton = (ImageButton) findViewById(R.id.languageButton);
+ languageButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ KMManager.showLanguageList(context);
+ }
+ });
+```
+
+## History
+
+Deprecated syntax in Keyman Engine for Android 14.0
+
+## See also
+
+- [`showKeyboardPicker()`](showKeyboardPicker)
diff --git a/android/docs/engine/KMManager/switchToNextKeyboard.md b/android/docs/engine/KMManager/switchToNextKeyboard.md
new file mode 100644
index 00000000000..dfd7d3328c4
--- /dev/null
+++ b/android/docs/engine/KMManager/switchToNextKeyboard.md
@@ -0,0 +1,46 @@
+---
+title: KMManager.switchToNextKeyboard()
+---
+
+## Summary
+
+The **`switchToNextKeyboard()`** method loads the next available
+keyboard in keyboards list.
+
+## Syntax
+
+``` javascript
+KMManager.switchToNextKeyboard(Context context)
+```
+
+### Parameters
+
+`context`
+: The context.
+
+## Description
+
+Use this method to switch to next keyboard in the keyboards list. If the
+next keyboard does not exists, then it loads the first keyboard in the
+keyboards list.
+
+## Examples
+
+### Example: Using `switchToNextKeyboard()`
+
+The following script illustrate the use of `switchToNextKeyboard()`:
+
+``` javascript
+ final Context context = this;
+ ImageButton nextButton = (ImageButton) findViewById(R.id.nextButton);
+ nextButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ KMManager.switchToNextKeyboard(context);
+ }
+ });
+```
+
+## See also
+
+- [`setKeyboard()`](setKeyboard)
diff --git a/android/docs/engine/KMManager/updateSelectionRange.md b/android/docs/engine/KMManager/updateSelectionRange.md
new file mode 100644
index 00000000000..b57f97dad5a
--- /dev/null
+++ b/android/docs/engine/KMManager/updateSelectionRange.md
@@ -0,0 +1,77 @@
+---
+title: KMManager.updateSelectionRange()
+---
+
+## Summary
+
+The `updateSelectionRange()` method updates the selection range of the current context.
+
+## Syntax
+
+
+```java
+KMManager.updateSelectionRange(KeyboardType kbType)
+```
+
+### Parameters
+
+`kbType`
+: Keyboard type requesting the selection range update. `KEYBOARD_TYPE_INAPP` or `KEYBOARD_TYPE_SYSTEM`.
+
+### Returns
+
+Returns `true` if the selection range was updated successfully, `false` otherwise.
+
+## Description
+
+Use this method to update the selection range of the current context. It must be called in response to InputMethodService's onUpdateSelection method and whenever InputMethodService's onStartInput method has been called. Normally you do not need to call this method for in-app keyboards.
+
+----
+
+## Syntax (Deprecated)
+
+```javascript
+KMManager.updateSelectionRange(KeyboardType kbType, int selStart, int selEnd)
+```
+
+### Parameters
+
+`kbType`
+: Keyboard type requesting the selection range update. `KEYBOARD_TYPE_INAPP` or `KEYBOARD_TYPE_SYSTEM`.
+
+`selStart`
+: The new selection start location.
+
+`selEnd`
+: The new selection end location.
+
+### Returns
+
+(Deprecated) Returns `true` if the selection range was updated successfully, `false` otherwise.
+
+## Description
+
+Use this method to update the selection range of the current context. It must be called in response to InputMethodService's onUpdateSelection method and whenever InputMethodService's onStartInput method has been called. Normally you do not need to call this method for in-app keyboards.
+
+Since this syntax is deprecated, use `KMManager.updateSelectionRange(KeyboardType kbType)` syntax instead.
+
+## Example: Using `updateSelectionRange()`
+
+The following script illustrates the use of `updateSelectionRange()`:
+
+```javascript
+ @Override
+ public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
+ super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);
+ KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM);
+ }
+```
+
+## History
+
+Deprecated `updateSelectionRange(KeyboardType kbType, int selStart, int selEnd)` syntax in Keyman Engine for Android 17.0
+
+
+## See also
+
+* [`updateText()`](updateText)
diff --git a/android/docs/engine/KMManager/updateText.md b/android/docs/engine/KMManager/updateText.md
new file mode 100644
index 00000000000..05e54f4358f
--- /dev/null
+++ b/android/docs/engine/KMManager/updateText.md
@@ -0,0 +1,62 @@
+---
+title: KMManager.updateText()
+---
+
+## Summary
+
+The **`updateText()`** method updates the current context with the
+specified text.
+
+## Syntax
+
+``` javascript
+KMManager.updateText(KeyboardType kbType, String text)
+```
+
+### Parameters
+
+`kbType`
+: Keyboard type requesting the context update. `KEYBOARD_TYPE_INAPP`
+ or `KEYBOARD_TYPE_SYSTEM`.
+
+`text`
+: The text to replace the current context.
+
+### Returns
+
+Returns `true` if the current context was updated successfully, `false`
+otherwise.
+
+## Description
+
+Use this method to update the current context. It must be called in
+InputMethodService's onStartInput method to match the current context
+with the text in the editor. Normally you do not need to call this
+method for in-app keyboards.
+
+## Examples
+
+### Example: Using `updateText()`
+
+The following script illustrate the use of `updateText()`:
+
+``` javascript
+ @Override
+ public void onStartInput(EditorInfo attribute, boolean restarting) {
+ super.onStartInput(attribute, restarting);
+ // ...
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0);
+ if (icText != null) {
+ KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString());
+ // ...
+ }
+ }
+ // ...
+ }
+```
+
+## See also
+
+- [updateSelectionRange()](updateSelectionRange)
diff --git a/android/docs/engine/KeyboardEventHandler/index.md b/android/docs/engine/KeyboardEventHandler/index.md
new file mode 100644
index 00000000000..57125e100b0
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/index.md
@@ -0,0 +1,38 @@
+---
+title: KeyboardEventHandler class
+---
+
+## Description
+
+The KeyboardEventHandler class provides keyboard events and methods to
+notify registered listeners on keyboard and lexical-model events.
+
+## Events
+
+### OnKeyboardEventListener Interface
+
+[`onKeyboardLoaded()`](onKeyboardLoaded)
+: is called when the keyboard has been loaded for the first time
+
+[`onKeyboardChanged()`](onKeyboardChanged)
+: is called when another keyboard has been set
+
+[`onKeyboardShown()`](onKeyboardShown)
+: is called when the keyboard has been shown
+
+[`onKeyboardDismissed()`](onKeyboardDismissed)
+: is called when the keyboard has been dismissed
+
+### OnKeyboardDownloadEventListener Interface
+
+[`onKeyboardDownloadStarted()`](onKeyboardDownloadStarted)
+: is called when a keyboard download has started
+
+[`onKeyboardDownloadFinished()`](onKeyboardDownloadFinished)
+: is called when a keyboard download has finished
+
+[`onPackageInstalled()`](onPackageInstalled)
+: is called when a keyboard package has been installed
+
+[`onLexicalModelInstalled()`](onLexicalModelInstalled)
+: is called when a lexical-model package has been installed
diff --git a/android/docs/engine/KeyboardEventHandler/onKeyboardChanged.md b/android/docs/engine/KeyboardEventHandler/onKeyboardChanged.md
new file mode 100644
index 00000000000..8be5f6e4745
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onKeyboardChanged.md
@@ -0,0 +1,42 @@
+---
+title: onKeyboardChanged()
+---
+
+## Summary
+
+The **`onKeyboardChanged()`** event is called when another keyboard has
+been set.
+
+## Syntax
+
+``` javascript
+public void onKeyboardChanged(String newKeyboard)
+```
+
+### Parameters
+
+`newKeyboard`
+: New keyboard identifier as languageID_keyboardID (e.g. eng_us).
+
+## Description
+
+Implement this method to handle keyboard changed event.
+
+## Examples
+
+### Example: Using `onKeyboardChanged()`
+
+The following script illustrate the use of `onKeyboardChanged()`:
+
+``` javascript
+ @Override
+ public void onKeyboardChanged(String newKeyboard) {
+ // handle keyboard changed event here
+ }
+```
+
+## See also
+
+- [`onKeyboardDismissed()`](onKeyboardDismissed)
+- [`onKeyboardLoaded()`](onKeyboardLoaded)
+- [`onKeyboardShown()`](onKeyboardShown)
diff --git a/android/docs/engine/KeyboardEventHandler/onKeyboardDismissed.md b/android/docs/engine/KeyboardEventHandler/onKeyboardDismissed.md
new file mode 100644
index 00000000000..97a8441c28d
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onKeyboardDismissed.md
@@ -0,0 +1,37 @@
+---
+title: onKeyboardDismissed()
+---
+
+## Summary
+
+The **`onKeyboardDismissed()`** event is called when the keyboard has
+been dismissed.
+
+## Syntax
+
+``` javascript
+public void onKeyboardDismissed()
+```
+
+## Description
+
+Implement this method to handle keyboard dismissed event.
+
+## Examples
+
+### Example: Using `onKeyboardDismissed()`
+
+The following script illustrate the use of `onKeyboardDismissed()`:
+
+``` javascript
+ @Override
+ public void onKeyboardDismissed() {
+ // handle keyboard dismissed event here
+ }
+```
+
+## See also
+
+- [`onKeyboardChanged()`](onKeyboardChanged)
+- [`onKeyboardLoaded()`](onKeyboardLoaded)
+- [`onKeyboardShown()`](onKeyboardShown)
diff --git a/android/docs/engine/KeyboardEventHandler/onKeyboardDownloadFinished.md b/android/docs/engine/KeyboardEventHandler/onKeyboardDownloadFinished.md
new file mode 100644
index 00000000000..d6286f40665
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onKeyboardDownloadFinished.md
@@ -0,0 +1,46 @@
+---
+title: onKeyboardDownloadFinished()
+---
+
+## Summary
+
+The **`onKeyboardDownloadFinished()`** event is called when a keyboard
+download has finished.
+
+## Syntax
+
+``` javascript
+public void onKeyboardDownloadFinished(HashMap keyboardInfo, int result)
+```
+
+### Parameters
+
+`keyboardInfo`
+: The information dictionary of the keyboard with keys and values
+ defined as `HashMap`.
+
+`result`
+: The result of the download (result > 0 if successful, < 0 if
+ failed).
+
+## Description
+
+Implement this method to handle keyboard download finished event.
+
+## Examples
+
+### Example: Using `onKeyboardDownloadFinished()`
+
+The following script illustrate the use of
+`onKeyboardDownloadFinished()`:
+
+``` javascript
+ @Override
+ public void onKeyboardDownloadFinished(HashMap keyboardInfo, int result) {
+ // handle keyboard download finished event here
+ }
+```
+
+## See also
+
+- [`onKeyboardDownloadStarted()`](onKeyboardDownloadStarted)
diff --git a/android/docs/engine/KeyboardEventHandler/onKeyboardDownloadStarted.md b/android/docs/engine/KeyboardEventHandler/onKeyboardDownloadStarted.md
new file mode 100644
index 00000000000..c1a34451bf9
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onKeyboardDownloadStarted.md
@@ -0,0 +1,42 @@
+---
+title: onKeyboardDownloadStarted()
+---
+
+## Summary
+
+The **`onKeyboardDownloadStarted()`** event is called when a keyboard
+download has started.
+
+## Syntax
+
+``` javascript
+public void onKeyboardDownloadStarted(HashMap keyboardInfo)
+```
+
+### Parameters
+
+`keyboardInfo`
+: The information dictionary of the keyboard with keys and values
+ defined as `HashMap`.
+
+## Description
+
+Implement this method to handle keyboard download started event.
+
+## Examples
+
+### Example: Using `onKeyboardDownloadStarted()`
+
+The following script illustrate the use of
+`onKeyboardDownloadStarted()`:
+
+``` javascript
+ @Override
+ public void onKeyboardDownloadStarted(HashMap keyboardInfo) {
+ // handle keyboard download started event here
+ }
+```
+
+## See also
+
+- [`onKeyboardDownloadFinished()`](onKeyboardDownloadFinished)
diff --git a/android/docs/engine/KeyboardEventHandler/onKeyboardLoaded.md b/android/docs/engine/KeyboardEventHandler/onKeyboardLoaded.md
new file mode 100644
index 00000000000..26622fa87ad
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onKeyboardLoaded.md
@@ -0,0 +1,43 @@
+---
+title: onKeyboardLoaded()
+---
+
+## Summary
+
+The **`onKeyboardLoaded()`** event is called when the keyboard has been
+loaded for the first time.
+
+## Syntax
+
+``` javascript
+public void onKeyboardLoaded(KeyboardType keyboardType)
+```
+
+### Parameters
+
+`keyboardType`
+: The keyboard type that has been loaded. `KEYBOARD_TYPE_INAPP` or
+ `KEYBOARD_TYPE_SYSTEM`.
+
+## Description
+
+Implement this method to handle keyboard loaded event.
+
+## Examples
+
+### Example: Using `onKeyboardLoaded()`
+
+The following script illustrate the use of `onKeyboardLoaded()`:
+
+``` javascript
+ @Override
+ public void onKeyboardLoaded(KeyboardType keyboardType) {
+ // handle keyboard loaded event here
+ }
+```
+
+## See also
+
+- [`onKeyboardChanged()`](onKeyboardChanged)
+- [`onKeyboardDismissed()`](onKeyboardDismissed)
+- [`onKeyboardShown()`](onKeyboardShown)
diff --git a/android/docs/engine/KeyboardEventHandler/onKeyboardShown.md b/android/docs/engine/KeyboardEventHandler/onKeyboardShown.md
new file mode 100644
index 00000000000..940c788fae2
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onKeyboardShown.md
@@ -0,0 +1,37 @@
+---
+title: onKeyboardShown()
+---
+
+## Summary
+
+The **`onKeyboardShown()`** event is called when the keyboard has been
+shown.
+
+## Syntax
+
+``` javascript
+public void onKeyboardShown()
+```
+
+## Description
+
+Implement this method to handle keyboard shown event.
+
+## Examples
+
+### Example: Using `onKeyboardShown()`
+
+The following script illustrate the use of `onKeyboardShown()`:
+
+``` javascript
+ @Override
+ public void onKeyboardShown() {
+ // handle keyboard shown event here
+ }
+```
+
+## See also
+
+- [`onKeyboardChanged()`](onKeyboardChanged)
+- [`onKeyboardDismissed()`](onKeyboardDismissed)
+- [`onKeyboardLoaded()`](onKeyboardLoaded)
diff --git a/android/docs/engine/KeyboardEventHandler/onLexicalModelInstalled.md b/android/docs/engine/KeyboardEventHandler/onLexicalModelInstalled.md
new file mode 100644
index 00000000000..7a3ee582578
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onLexicalModelInstalled.md
@@ -0,0 +1,34 @@
+---
+title: onLexicalModelInstalled()
+---
+
+## Summary
+The `onLexicalModelInstalled()` event is called when a lexical-model package has been installed.
+
+## Syntax
+```java
+onLexicalModelInstalled(List> lexicalModelsInstalled)
+```
+
+### Parameters
+`lexicalModelsInstalled`
+
+The information dictionaries of the lexical-models installed from a lexical-model package with keys and values defined as `List>`.
+
+
+## Description
+Implement this method to handle lexical-model package installed event.
+
+## Examples
+
+### Example: Using `onLexicalModelInstalled()`
+The following script illustrate the use of `onLexicalModelInstalled()`:
+```java
+ @Override
+ public void onLexicalModelInstalled(List> lexicalModelsInstalled) {
+ // handle lexical-model package installed event here
+ }
+```
+
+## See also
+* [onPackageInstalled()](onPackageInstalled)
diff --git a/android/docs/engine/KeyboardEventHandler/onPackageInstalled.md b/android/docs/engine/KeyboardEventHandler/onPackageInstalled.md
new file mode 100644
index 00000000000..3653dfc8186
--- /dev/null
+++ b/android/docs/engine/KeyboardEventHandler/onPackageInstalled.md
@@ -0,0 +1,35 @@
+---
+title: onPackageInstalled()
+---
+
+## Summary
+The `onPackageInstalled()` is called when a keyboard package has been installed.
+
+## Syntax
+```java
+onPackageInstalled(List> keyboardsInstalled)
+```
+
+### Parameters
+`keyboardsInstalled`
+
+The information dictionaries of the keyboards installed from a keyboard package with keys and values defined as `List>`.
+
+## Description
+Implement this method to handle keyboard package installed event.
+
+## Examples ##
+
+### Example: Using `onPackageInstalled()` ###
+The following script illustrate the use of `onPackageInstalled()`:
+
+```java
+ @Override
+ public void onPackageInstalled(List> keyboardsInstalled) {
+ // handle keyboard package installed event here
+ }
+```
+
+## See also
+* [onLexicalModelInstalled()](onLexicalModelInstalled)
+
diff --git a/android/docs/engine/guides/in-app/index.md b/android/docs/engine/guides/in-app/index.md
new file mode 100644
index 00000000000..bfa37d46227
--- /dev/null
+++ b/android/docs/engine/guides/in-app/index.md
@@ -0,0 +1,135 @@
+---
+title: Guide: build an in-app keyboard
+---
+
+Keyman Engine for Android allows you to use any Keyman touch keyboard in your Android app, or even to create your own
+system keyboard app for purchase in the Play Store.
+This guide will walk you through the steps for creating your first Android app with Keyman Engine for Android.
+
+If you are not familiar with Android development, you will find the
+[Android Developer online training](https://developer.android.com/training/index.html) an invaluable
+resource, and working through some of their tutorials first will help you with the rest of this guide.
+
+### 1. Install Free Tools
+* Install [Keyman Developer 17 or later](https://keyman.com/developer/).
+* Install [OpenJDK 11](https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-11).
+* Install [Android Studio](https://developer.android.com/studio/index.html).
+ Android Studio runs on several platforms. Keyman Developer runs on Windows 10 or later.
+
+### 2. Configure Android Studio
+* For Windows users, from **Control Panel>System>System Properties>Environment Variables**,
+ create a system variable **ANDROID_HOME** to the location of your Android SDK. The default
+ installation location is
+ c:\Users\[USER]\AppData\Local\Android\sdk where [USER] is your Windows username.
+* For Linux users, add the following to ~/.bashrc
+```bash
+export ANDROID_HOME=$HOME/Android/Sdk
+export PATH=$PATH:$ANDROID_HOME/tools
+```
+* In a command prompt or terminal, accept all the SDK license agreements
+```bash
+cd c:\Users\[USER]\AppData\Local\Android\sdk\tools\bin
+yes | ./sdkmanager.bat --licenses
+```
+
+### 3. Download Keyman Engine for Android and Build Sample projects
+* Download the [Keyman for Android SDK](https://keyman.com/engine)
+ and extract the files to a new folder.
+
+The archive includes two sample projects and an Android .aar library file. This guide will use the first
+ example project, **KMSample1**.
+
+* If **KMSample1.zip** exists, extract it to a new folder. Otherwise, the KMSample1 project
+ can be found at **Samples/KMSample1**
+* In Android Studio, select **File>Open** and choose the KMSample1 project folder from
+ the previous step.
+
+* When the project loads, you may be prompted to install Android SDKs; go ahead and follow the prompts to
+ fixup any missing SDK dependencies.
+
+### 4. Create a keyboard layout
+Use Keyman Developer to build a touch layout. The following blog posts walk through some of the development and
+ testing for creating a touch keyboard layout:
+
+* [Creating a Touch Keyboard Layout for Amharic with Keyman Developer 13](/developer/13.0/guides/develop/creating-a-touch-keyboard-layout-for-amharic-with-keyman-developer-10")
+* [Creating A Touch Keyboard Layout For Amharic — The Nitty Gritty](/developer/13.0/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+* [How to test your keyboard layout with Keyman Developer 13 — touch and desktop](/developer/13.0/guides/test/how-to-test-your-keyboard-layout-with-keyman-developer-10-touch-and-desktop)
+* [How to test your touch layout in the Google Chrome mobile emulator](/developer/13.0/guides/test/how-to-test-your-touch-layout-in-the-google-chrome-mobile-emulator)
+
+When your keyboard is ready, you should have a compiled keyboard package file. The example below shows the
+ Tamil 99 Basic touch layout.
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/touch-layout.png)
+
+From the package editor, open the containing folder for the keyboard package to find the .kmp file to copy over; its name will be based on
+ your source keyboard name. If your keyboard project settings is configured to output to **$PROJECTPATH\build**,
+ you may need to navigate up a folder and into the build folder.
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/open-containing-folder.png)
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/compiled-keyboard-file.png)
+
+### 5. Add your keyboard package to the project
+Copy your compiled keyboard package file (in this example **basic_kbdtam99.kmp**) to the
+ **KMSample1\app\src\main\assets\** folder. If you have an associated dictionary, then copy that to the
+ same **assets** folder. This example uses **example.ta.wordlist.model.kmp**.
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/copy-keyboard-file.png)
+
+When you switch back into Android Studio, you should see the assets folder
+ with your keyboard and dictionary files:
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/android-studio-assets.png)
+
+Next, edit `MainActivity.onCreate()` to add the keyboard (tamil99m) with `KMManager`.
+ If your keyboard package is not using a custom font, you can set KMKey_Font to the default font:
+```java
+ // Add a custom keyboard
+ Keyboard kbInfo = new Keyboard(
+ "basic_kbdtam99", // Package ID - filename of the .kmp file
+ "basic_kbdtam99", // Keyboard ID
+ "Tamil 99 Basic", // Keyboard Name
+ "ta", // Language ID
+ "Tamil", // Language Name
+ "1.0", // Keyboard Version
+ null, // URL to help documentation if available
+ "", // URL to latest .kmp file
+ true, // Boolean to show this is a new keyboard in the keyboard picker
+
+ // Font information of the .ttf font to use in KMSample1 (for example "aava1.ttf").
+ // basic_kbdtam99 doesn't include a font. Can set blank "" or KMManager.KMDefault_KeyboardFont
+ KMManager.KMDefault_KeyboardFont, // Font for KMSample1 text field
+ KMManager.KMDefault_KeyboardFont); // Font for OSK
+ KMManager.addKeyboard(this, kbInfo);
+```
+If you included a dictionary in the sample app, add and register it with `KMManager`.
+```java
+ // Add a dictionary
+ HashMaplexicalModelInfo = new HashMap();
+ lexicalModelInfo.put(KMManager.KMKey_PackageID, "example.ta.wordlist");
+ lexicalModelInfo.put(KMManager.KMKey_LanguageID, "ta");
+ lexicalModelInfo.put(KMManager.KMKey_LexicalModelID, "example.ta.wordlist");
+ lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, "1.0");
+ KMManager.addLexicalModel(context, lexicalModelInfo);
+ KMManager.registerAssociatedLexicalModel("ta");
+```
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/android-studio-adding-keyboard.png)
+
+### 6. Build and run the app
+You can run your app on a [created Virtual Device](https://developer.android.com/studio/run/managing-avds.html),
+ or connect an Android device via USB to your computer to test. In either case, click the green Run button to start
+ the app. The steps below show how to create a new Virtual Device
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/android-studio-starting-debug.png)
+
+Click the **[Create Virtual Device]** button to add a new virtual device, and follow the prompts. Any recent emulated
+ device should work fine. You will be prompted to download additional resources for the emulator when this runs.
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/in-app/android-emulator-tamil.png)
+
+And there you have it: your first Keyman Engine for Android app!
+
+## See Also
+* [Guide: Build a system keyboard app](../system-keyboard/)
+* [Keyman Developer Documentation](/developer/17.0/)
+* [Keyman Engine for Android Documentation](/developer/engine/android/18.0/)
+* [Android Developer Home](https://developer.android.com/index.html)
diff --git a/android/docs/engine/guides/index.md b/android/docs/engine/guides/index.md
new file mode 100644
index 00000000000..43a7c3e6efb
--- /dev/null
+++ b/android/docs/engine/guides/index.md
@@ -0,0 +1,6 @@
+---
+title: Keyman Engine for Android Developer Guides
+---
+
+- [Build an in-app keyboard](in-app/)
+- [Build a system keyboard app](system-keyboard/)
diff --git a/android/docs/engine/guides/system-keyboard/index.md b/android/docs/engine/guides/system-keyboard/index.md
new file mode 100644
index 00000000000..227bb4f33ca
--- /dev/null
+++ b/android/docs/engine/guides/system-keyboard/index.md
@@ -0,0 +1,69 @@
+---
+title: Guide: build a system keyboard app
+---
+
+In [part 1](../in-app/) of this series, we looked at the steps involved
+in creating a basic Android app which included a Keyman in-app keyboard.
+In this post, we'll work with the second sample included in the Keyman
+Engine for Android package, which is a very basic System Keyboard app.
+This post builds on concepts and setup from the first post, so be sure
+you are familiar with that before you start on this one.
+
+### 1. Basic configuration
+
+If **KMSample2.zip** exists from the downloaded **Keyman Engine for
+Android**, extract it to a new folder. Otherwise, the KMSample2 project
+can be found at **Samples/KMSample2**
+
+### 2. Run the sample app
+
+The KMSample2 app includes the Tamil 99 Basic keyboard as an example
+keyboard. Without any further modifications, the app should run and
+you'll be able to configure your device to use your app as the system
+keyboard.
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/system-keyboard/emulator-800wi.png)
+
+### 3. Extending the app
+
+From here, you will no doubt want to replace the keyboard with your own
+one; again, follow the instructions from Part 1 to make this change.
+
+You can also customise the look and feel of the on screen keyboard by
+including a custom CSS file in your keyboard through Keyman Developer.
+
+[ ](/cdn/dev/img/engine/android/10.0/guides/system-keyboard/add-embedded-css.png)
+
+The CSS rules required to style your keyboard are beyond the scope of
+this post, but the **kmwosk.css** file included in the Keyman Developer
+16.0 distribution and in the [KeymanWeb 16.0
+source](https://github.com/keymanapp/keyman/blob/master/web/source/resources/osk/kmwosk.css) is
+a good place to start. See this
+[reference](/developer/engine/web/15.0/reference/osk/classes) for more
+information about the On-Screen Keyboard.
+
+Do take the time to read through the source of **KMSample2**, as it
+includes some boilerplate code required to link Keyman Engine through to
+the Android Input Method services and vice versa.
+
+Finally, you will want to improve the style and branding of the main
+activity in KMSample2. The sample includes just two buttons to link to
+the Android Input Method Settings and Input Method Menu, as a pointer to
+the two configuration steps that your users will need to undertake in
+order to start using your keyboard.
+
+[ ](/cdn/dev/img/engine/android/14.0/guides/system-keyboard/main-activity-layout-800wi.png)
+
+You will probably want to make that a lot prettier, and include more
+detailed instructions!
+
+That's all there is to creating a System Keyboard with Keyman Engine for
+Android. We've taken care of all the complex details of keyboarding in
+the Keyman Engine, so you can focus on the look and feel and the layout
+of your keyboard.
+
+### Further links
+
+- [Part 1 of this series](../in-app/)
+- [Keyman Developer Documentation](/developer/16.0/)
+- [Android Developer Home](http://developer.android.com/index.html)
diff --git a/android/docs/engine/index.md b/android/docs/engine/index.md
new file mode 100644
index 00000000000..fa549e4b345
--- /dev/null
+++ b/android/docs/engine/index.md
@@ -0,0 +1,43 @@
+---
+title: Keyman Engine for Android
+---
+
+## Overview
+
+Keyman Engine for Android 18.0 is a Java library for Android 5.0 and later versions which enables a fully customisable keyboard layout, both within an app and system-wide.
+Keyboard layouts for Keyman Engine can be created with [Keyman Developer](/developer/17.0), and a [library of existing keyboard layouts](http://keyman.com/developer/keymanweb/keyboards)
+is also available.
+
+For keyboard developers updating from an older versions of Keyman Engine for Android,
+see [What's New](whatsnew) for breaking changes
+
+## [Guides](guides/)
+
+* [Build an in-app keyboard](guides/in-app/)
+* [Build a system keyboard app](guides/system-keyboard/)
+
+## Classes
+
+[KMManager](KMManager/)
+: The core class for controlling Keyman Engine
+
+[KMKeyboard](KMKeyboard/)
+: Keyman's child class of an Android [WebView](https://developer.android.com/reference/android/webkit/WebView) for a keyboard
+
+[KeyboardEventHandler](KeyboardEventHandler/)
+: Provides keyboard and lexical-model events and methods to notify registered listeners
+
+## See also
+
+* [Keyman Engine for Android 17.0](/developer/engine/android/17.0/)
+* [Keyman Engine for Android 16.0](/developer/engine/android/16.0/)
+* [Keyman Engine for Android 15.0](/developer/engine/android/15.0/)
+* [Keyman Engine for Android 14.0](/developer/engine/android/14.0/)
+* [Keyman Engine for Android 13.0](/developer/engine/android/13.0/)
+* [Keyman Engine for Android 12.0](/developer/engine/android/12.0/)
+* [Keyman Engine for Android 11.0](/developer/engine/android/11.0/)
+* [Keyman Engine for Android 10.0](/developer/engine/android/10.0/)
+* [Keyman Engine for Android 2.0](/developer/engine/android/2.0)
+* [Keyman Developer 17.0](/developer/17.0/)
+* [Keyboard Library](http://keyman.com/developer/keymanweb/keyboards)
+* [Keyman Engine for iPhone and iPad](/developer/engine/iphone-and-ipad/current-version/)
diff --git a/android/docs/engine/whatsnew.md b/android/docs/engine/whatsnew.md
new file mode 100644
index 00000000000..e8c2a3d694a
--- /dev/null
+++ b/android/docs/engine/whatsnew.md
@@ -0,0 +1,11 @@
+---
+title: What's New
+---
+
+## Keyman Engine for Android Breaking Changes: ##
+
+* Change package name from `com.tavultesoft.kmea` to `com.keyman.engine` #7881
+* Update to Java 11 #8543
+
+## See Also
+* [Keyman Engine for Android Documentation](index)
\ No newline at end of file
diff --git a/android/docs/help/about/system-requirements.md b/android/docs/help/about/system-requirements.md
index 8badb875755..16efbf28af6 100644
--- a/android/docs/help/about/system-requirements.md
+++ b/android/docs/help/about/system-requirements.md
@@ -9,7 +9,10 @@ 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 of [Google Chrome](https://play.google.com/store/apps/details?id=com.android.chrome) 37.0.
+Keyman for Android requires a minimum version 53.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.
### Device Permissions
Network and [external storage permissions](../troubleshooting/grant-storage-permission) are required to download and install keyboards/dictionaries from the Keyman servers.
diff --git a/android/docs/internal/README.md b/android/docs/internal/README.md
new file mode 100644
index 00000000000..ef51df7f60f
--- /dev/null
+++ b/android/docs/internal/README.md
@@ -0,0 +1,5 @@
+# Keyman for Android and Keyman Engine for Android
+
+## Internal Documents
+
+This folder is for storing design documents of new features pertaining to Keyman for Android and Keyman Engine for Android
diff --git a/common/include/test_assert.h b/common/include/test_assert.h
index 75c586bcc67..9669c16a022 100644
--- a/common/include/test_assert.h
+++ b/common/include/test_assert.h
@@ -12,10 +12,10 @@
#include
#include "test_color.h"
-#ifdef _assert_failed
-#undef _assert_failed
+#ifdef _test_assert_failed
+#undef _test_assert_failed
#endif
-#define _assert_failed(result, exprText) { \
+#define _test_assert_failed(result, exprText) { \
std::wcerr << console_color::fg(console_color::BRIGHT_RED) \
<< "Test failed with " << (result) \
<< " at " << __FILE__ << ":" << __LINE__ << ":" \
@@ -31,23 +31,23 @@
#define try_status(expr) { \
auto __s = (expr); \
if (__s != KM_CORE_STATUS_OK) { \
- _assert_failed(__s, u ## #expr); \
+ _test_assert_failed(__s, u ## #expr); \
} \
}
-#ifdef assert
-#undef assert
+#ifdef test_assert
+#undef test_assert
#endif
-#define assert(expr) { \
+#define test_assert(expr) { \
if (!(expr)) { \
- _assert_failed(0, u ## #expr); \
+ _test_assert_failed(0, u ## #expr); \
} \
}
-#ifdef assert_equal
-#undef assert_equal
+#ifdef test_assert_equal
+#undef test_assert_equal
#endif
-#define assert_equal(actual, expected) { \
+#define test_assert_equal(actual, expected) { \
if ((actual) != (expected)) { \
std::wcerr << console_color::fg(console_color::BRIGHT_RED) \
<< "Test failed at " << __FILE__ << ":" << __LINE__ << ":" \
@@ -59,10 +59,10 @@
} \
}
-#ifdef assert_string_equal
-#undef assert_string_equal
+#ifdef test_assert_string_equal
+#undef test_assert_string_equal
#endif
-#define assert_string_equal(actual, expected) { \
+#define test_assert_string_equal(actual, expected) { \
if (u16cmp((actual), (expected)) != 0) { \
std::wcerr << console_color::fg(console_color::BRIGHT_RED) \
<< "Test failed at " << __FILE__ << ":" << __LINE__ << ":" \
diff --git a/common/include/test_color.h b/common/include/test_color.h
index f06f94c562f..71267dc3a94 100644
--- a/common/include/test_color.h
+++ b/common/include/test_color.h
@@ -8,6 +8,12 @@
#include
+#ifdef _MSC_VER
+#include
+#else
+#include
+#endif
+
namespace console_color {
enum ansi_code {
@@ -65,12 +71,10 @@ __define_ansi_code__(reversed, "7");
#undef __define_ansi_code__
#ifdef _MSC_VER
-#include
inline bool isaterminal() {
return _isatty(_fileno(stdout));
}
#else
-#include
inline bool isaterminal() {
return isatty(STDOUT_FILENO);
}
diff --git a/common/tools/hextobin/build.sh b/common/tools/hextobin/build.sh
index 9e73f8c4101..073b008bf2e 100755
--- a/common/tools/hextobin/build.sh
+++ b/common/tools/hextobin/build.sh
@@ -11,7 +11,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
builder_describe "Build hextobin" clean configure build
builder_describe_outputs \
- configure /common/tools/hextobin/node_modules/commander \
+ configure /common/tools/hextobin/.configured \
build /common/tools/hextobin/build/index.js
builder_parse "$@"
diff --git a/common/tools/hextobin/package.json b/common/tools/hextobin/package.json
index fb651481f7a..708dace5028 100644
--- a/common/tools/hextobin/package.json
+++ b/common/tools/hextobin/package.json
@@ -15,5 +15,8 @@
"main": "build/index.js",
"bin": {
"hextobin": "build/hextobin.js"
+ },
+ "scripts": {
+ "postinstall": "echo configured > .configured"
}
}
diff --git a/common/web/types/.c8rc.json b/common/web/types/.c8rc.json
new file mode 100644
index 00000000000..f7137ed999b
--- /dev/null
+++ b/common/web/types/.c8rc.json
@@ -0,0 +1,12 @@
+{
+ "exclude": [
+ "src/keyboard-object.ts",
+ "src/lexical-model-types.ts",
+ "src/outputTarget.interface.ts",
+ "src/*.d.ts",
+ "src/main.ts",
+ "src/schemas/*",
+ "src/schema-validators.ts",
+ "src/schemas.ts"
+ ]
+}
diff --git a/common/web/types/.eslintrc.cjs b/common/web/types/.eslintrc.cjs
index b28ba0941b1..4fd5954e495 100644
--- a/common/web/types/.eslintrc.cjs
+++ b/common/web/types/.eslintrc.cjs
@@ -1,13 +1,13 @@
module.exports = {
parserOptions: {
- project: ["./tsconfig.json", "./test/tsconfig.json"],
+ project: ["./tsconfig.json", "./tests/tsconfig.json"],
},
ignorePatterns: [
".*/*",
"build/*",
"coverage/*",
"node_modules/*",
- "test/fixtures/*",
+ "tests/fixtures/*",
"tools/*",
"src/schemas/*"
],
diff --git a/common/web/types/.gitignore b/common/web/types/.gitignore
index 2f943a2f4e9..dcb567b832b 100644
--- a/common/web/types/.gitignore
+++ b/common/web/types/.gitignore
@@ -1,2 +1,3 @@
src/schemas/
-obj/
\ No newline at end of file
+obj/
+coverage/
diff --git a/common/web/types/build.sh b/common/web/types/build.sh
index 7141bc10720..418840d1a85 100755
--- a/common/web/types/build.sh
+++ b/common/web/types/build.sh
@@ -80,17 +80,27 @@ function do_configure() {
}
function do_test() {
+ local MOCHA_FLAGS=
+
+ if [[ "${TEAMCITY_GIT_PATH:-}" != "" ]]; then
+ # we're running in TeamCity
+ MOCHA_FLAGS="-reporter mocha-teamcity-reporter"
+ fi
+
eslint .
- tsc --build test
- readonly C8_THRESHOLD=50
- c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha "${builder_extra_params[@]}"
+ tsc --build tests
+
+ readonly C8_THRESHOLD=60
+
+ # Excludes are defined in .c8rc.json
+ c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha ${MOCHA_FLAGS} "${builder_extra_params[@]}"
builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal."
builder_echo warning "Please increase threshold in build.sh as test coverage improves."
}
#-------------------------------------------------------------------------------------------------------------------
-builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo
+builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo ./src/schemas/ ./node_modules/ ./obj/
builder_run_action configure do_configure
builder_run_action build tsc --build
builder_run_action test do_test
diff --git a/common/web/types/package.json b/common/web/types/package.json
index 1ba487aafdf..d6efc635039 100644
--- a/common/web/types/package.json
+++ b/common/web/types/package.json
@@ -23,7 +23,7 @@
"build": "tsc -b",
"build:schema": "ajv compile",
"lint": "eslint .",
- "test": "npm run lint && cd test && tsc -b && cd .. && c8 --skip-full --reporter=lcov --reporter=text mocha"
+ "test": "npm run lint && cd tests && tsc -b && cd .. && c8 --skip-full --reporter=lcov --reporter=text mocha"
},
"author": "Marc Durdin (https://github.com/mcdurdin)",
"license": "MIT",
@@ -36,7 +36,6 @@
"restructure": "3.0.1"
},
"devDependencies": {
- "@types/chai": "^4.1.7",
"@types/mocha": "^5.2.7",
"@types/node": "^20.4.1",
"ajv": "^8.12.0",
@@ -49,7 +48,7 @@
"typescript": "^5.4.5"
},
"mocha": {
- "spec": "build/test/**/test-*.js",
+ "spec": "build/tests/**/*.tests.js",
"require": [
"source-map-support/register"
]
@@ -77,7 +76,7 @@
"src/keyman-touch-layout/keyman-touch-layout-file-writer.ts",
"src/osk/osk.ts",
"src/schemas/*",
- "test/"
+ "tests/"
]
},
"sideEffects": false
diff --git a/common/web/types/src/keyboard-object.ts b/common/web/types/src/keyboard-object.ts
new file mode 100644
index 00000000000..afd0029af0d
--- /dev/null
+++ b/common/web/types/src/keyboard-object.ts
@@ -0,0 +1,180 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
+import { TouchLayoutPlatform as LayoutFormFactorSpec } from './keyman-touch-layout/keyman-touch-layout-file.js';
+
+export type ComplexKeyboardStore = (string | { t: 'd', d: number } | { ['t']: 'b' })[];
+
+// A stub for KeyEvent which is properly defined in KeymanWeb
+type KeyEventStub = {};
+
+// A stub for OutputTarget which is properly defined in KeymanWeb
+type OutputTargetStub = {};
+
+export interface EncodedVisualKeyboard {
+ /** Represents CSS font styling to use for VisualKeyboard text */
+ F: string;
+ /** Should there be a 102nd key? */
+ K102?: boolean,
+ /**
+ * Keyboard Layer Specification: an object-based map of layer name to the keycaps for its
+ * 65 keys. The 65 keys are ordered from left to right, then top to bottom.
+ *
+ * The key ID corresponding to each index of the array is specified within `Codes.dfltCodes`.
+ * Entries corresponding to `K_*` in `Codes.dfltCodes` are reserved for future use.
+ */
+ KLS?: { [layerName: string]: string[] },
+ /**
+ * @deprecated
+ * The older form for data in KLS - defines keycaps for 'default' keys, then 'shift' keys,
+ * in a single concatenated array.
+ */
+ BK?: string[];
+}
+
+export type LayoutSpec = {
+ "desktop"?: LayoutFormFactorSpec,
+ "phone"?: LayoutFormFactorSpec,
+ "tablet"?: LayoutFormFactorSpec
+}
+
+export type KeyboardObject = {
+ /**
+ * group-start: the function triggering processing for the keyboard's
+ * "Unicode" start group, corresponding to `begin Unicode > use(_____)` in
+ * Keyman keyboard language.
+ * @param outputTarget The context to which the keystroke applies
+ * @param keystroke The full, pre-processed keystroke triggering
+ * keyboard-rule application.
+ */
+ gs(outputTarget: OutputTargetStub, keystroke: KeyEventStub): boolean;
+
+ /**
+ * group-newcontext: the function triggering processing for the keyboard's
+ * "NewContext" start group, corresponding to `begin NewContext > use(_____)`
+ * in Keyman keyboard language.
+ * @param outputTarget The new context to be used with future keystrokes
+ * @param keystroke A 'null' `KeyEvent` providing current modifier + state information.
+ */
+ gn?(outputTarget: OutputTargetStub, keystroke: KeyEventStub): boolean;
+
+ /**
+ * group-postkeystroke: the function triggering processing for the keyboard's
+ * "PostKeystroke" start group, corresponding to `begin PostKeystroke >
+ * use(_____)` in Keyman keyboard language.
+ * @param outputTarget The context altered by a recent keystroke. As a
+ * precondition, all changes due to `gs` / `begin Unicode` should already be
+ * applied.
+ * @param keystroke A 'null' `KeyEvent` providing current modifier + state information.
+ */
+ gpk?(outputTarget: OutputTargetStub, keystroke: KeyEventStub): boolean;
+
+ /**
+ * Keyboard ID: the uniquely-identifying name for this keyboard. Includes the standard
+ * `Keyboard_` prefix. May be 'namespaced' with a prefix corresponding to a package name
+ * within app/webview.
+ */
+ KI: string;
+ /**
+ * Keyboard Name: the human-readable name of the keyboard.
+ */
+ KN: string;
+ /**
+ * Encoded data usable to construct a desktop/hardware-oriented on-screen keyboard.
+ */
+ KV: EncodedVisualKeyboard;
+ /**
+ * Keyboard Language Code: set within select keyboards.
+ *
+ * Currently, it's only used to determine the need for CJK-picker support. Is missing
+ * in most compiled keyboards.
+ */
+ KLC?: string;
+ /**
+ * @deprecated
+ * Keyboard Language Code: set within select keyboards.
+ *
+ * Currently, it's only used to determine the need for CJK-picker support.
+ * Is (probably) an older name of KLC with the identical purpose. Is missing
+ * in most compiled keyboards.
+ */
+ LanguageCode?: string;
+ /**
+ * Keyboard CSS: provides the definition for custom keyboard style sheets
+ */
+ KCSS?: string;
+ /**
+ * Keyboard is RTL: a simple flag noting if the keyboard's script is RTL.
+ */
+ KRTL?: boolean;
+ /**
+ * Keyboard Modifier BitMask: a set of bitflags indicating which modifiers
+ * the keyboard's rules utilize. See also: `ModifierKeyConstants`.
+ */
+ KMBM?: number;
+ /**
+ * Keyboard Supplementary plane: set to 1 if the keyboard uses non-BMP Unicode
+ * characters.
+ */
+ KS?: number;
+ /**
+ * Keyman Visual Keyboard Layout: defines the touch-layout definitions used for
+ * 'phone' and 'tablet' form-factors.
+ */
+ KVKL?: LayoutSpec;
+ /**
+ * Keyboard is Mnemonic: set to 1 if the keyboard uses a mnemonic layout.
+ */
+ KM?: number;
+ /**
+ * KeyBoard VERsion: the version of this keyboard.
+ */
+ KBVER?: string;
+ /**
+ * Keyman VERsion: the version of Keyman Developer used to compile this keyboard.
+ */
+ KVER?: string;
+ /**
+ * Keyman Variable Stores: an array of the names of all variable stores used by the
+ * keyboard.
+ */
+ KVS?: (`s${number}`)[];
+ /**
+ * Keyboard Help: HTML help text, as specified by either the &kmw_helptext or &kmw_helpfile system stores.
+ *
+ * Reference: https://help.keyman.com/developer/language/reference/kmw_helptext,
+ * https://help.keyman.com/developer/language/reference/kmw_helpfile
+ */
+ KH?: string;
+ /**
+ * Keyboard Virtual Key Dictionary: the Developer-compiled, minified dictionary of virtual-key codes
+ */
+ KVKD?: string;
+ /**
+ * Keyboard Display Underlying: set to 1 if the desktop form of the keyboard
+ * should show the US QWERTY underlying keycaps. These may also appear on
+ * touch layouts if set and no touch-layout information is available.
+ */
+ KDU?: number;
+ /**
+ * Keyboard Help File: Embedded JS script designed for use with a keyboard's
+ * HTML help text. Always defined within the file referenced by &kmw_embedjs
+ * in a keyboard's source, though that file may also contain _other_ script
+ * definitions as well. (`KHF` must be explicitly defined within that file.)
+ * @param e Will be provided with the root element (a ) of the On-Screen Keyboard.
+ * @returns
+ */
+ KHF?: (e: any) => string;
+
+ /**
+ * Keyboard Notify Shift: Provided by CJK-picker keyboards to properly
+ * interface them with Keyman Engine for Web.
+ * @param {number} _PCommand event code (16,17,18) or 0; 16-18
+ * correspond to modifier codes when pressed, while 0 corresponds to loss of focus
+ * @param {Object} _PTarget target element
+ * @param {number} _PData 1 or 0
+ * @returns
+ */
+ KNS?: (_PCommand: number, _PTarget: OutputTargetStub, _PData: number) => void;
+} & Record<`s${number}`, 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 0cb49e527c7..a88688f7ab7 100644
--- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
+++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
@@ -174,12 +174,7 @@ export class Strs extends Section {
*/
allocString(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
// Run the string processing pipeline
- s = Strs.processString(s, opts, sections);
-
- // add to the set, for testing
- if (s) {
- this.allProcessedStrings.add(s);
- }
+ s = this.processString(s, opts, sections);
// if it's a single char, don't push it into the strs table
if (opts?.singleOk && isOneChar(s)) {
@@ -196,8 +191,8 @@ export class Strs extends Section {
return result;
}
- /** process everything according to opts */
- static processString(s: string, opts: StrsOptions, sections: DependencySections) {
+ /** process everything according to opts, and add the string to this.allProcessedStrings */
+ private processString(s: string, opts: StrsOptions, sections: DependencySections) {
s = s ?? '';
// type check everything else
if (typeof s !== 'string') {
@@ -215,6 +210,12 @@ export class Strs extends Section {
if (opts?.unescape) {
s = unescapeString(s);
}
+
+ if (s) {
+ // add all processed strings here, so that we catch denormalized strings in the input
+ this.allProcessedStrings.add(s);
+ }
+
// nfd
if (opts?.nfd) {
if (!sections?.meta?.normalizationDisabled) {
diff --git a/common/web/types/src/ldml-keyboard/pattern-parser.ts b/common/web/types/src/ldml-keyboard/pattern-parser.ts
index 3e56096ec67..1f1be6b3d92 100644
--- a/common/web/types/src/ldml-keyboard/pattern-parser.ts
+++ b/common/web/types/src/ldml-keyboard/pattern-parser.ts
@@ -81,7 +81,12 @@ export class MarkerParser {
/**
* Pattern for matching a marker reference, OR the special marker \m{.}
*/
- public static readonly REFERENCE = /\\m{([0-9A-Za-z_]{1,32}|\.)}/g;
+ public static readonly REFERENCE = /(?= Uni_PUA_16_START && ch <= Uni_PUA_16_END));
}
+/** @returns false if s is NEITHER NFC nor NFD. (Returns true for falsy) */
+export function isNormalized(s: string) : boolean {
+ if(!s) return true; // empty or null
+ const nfc = s.normalize("NFC");
+ const nfd = s.normalize("NFD");
+ if (s !== nfc && s !== nfd) return false;
+ return true;
+}
+
class BadStringMap extends Map
> {
public toString() : string {
if (!this.size) {
diff --git a/common/web/types/test/lexical-model-types.tests.ts b/common/web/types/test/lexical-model-types.tests.ts
deleted file mode 100644
index 8944342ed6e..00000000000
--- a/common/web/types/test/lexical-model-types.tests.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * This file "tests" the exports from the main module.
- *
- * Since the exports are all types, the "test" here is that the type can be
- * imported and compiled without any compiler errors.
- */
-
-import { USVString, Transform, Suggestion, SuggestionTag, Context, Capabilities, Configuration, Distribution, WordBreakingFunction, Span, LexicalModelPunctuation, ElementString, KMXPlus } from '@keymanapp/common-types';
-
-export let u: USVString;
-export let l: Transform
-export let s: Suggestion;
-export let st: SuggestionTag;
-export let c: Context;
-export let cap: Capabilities;
-export let conf: Configuration;
-export let d: Distribution;
-export let wbf: WordBreakingFunction;
-export let sp: Span;
-export let lmp: LexicalModelPunctuation;
-
-
-// try some of the other types - that should still work
-export let elemString: ElementString;
-export let section: KMXPlus.Section;
diff --git a/common/web/types/test/fixtures/kmx/khmer_angkor.kmx b/common/web/types/tests/fixtures/kmx/khmer_angkor.kmx
similarity index 100%
rename from common/web/types/test/fixtures/kmx/khmer_angkor.kmx
rename to common/web/types/tests/fixtures/kmx/khmer_angkor.kmx
diff --git a/common/web/types/test/fixtures/kvk/balochi_inpage.kvk b/common/web/types/tests/fixtures/kvk/balochi_inpage.kvk
similarity index 100%
rename from common/web/types/test/fixtures/kvk/balochi_inpage.kvk
rename to common/web/types/tests/fixtures/kvk/balochi_inpage.kvk
diff --git a/common/web/types/test/fixtures/kvk/khmer_angkor.kvk b/common/web/types/tests/fixtures/kvk/khmer_angkor.kvk
similarity index 100%
rename from common/web/types/test/fixtures/kvk/khmer_angkor.kvk
rename to common/web/types/tests/fixtures/kvk/khmer_angkor.kvk
diff --git a/common/web/types/tests/helpers/index.ts b/common/web/types/tests/helpers/index.ts
new file mode 100644
index 00000000000..717b2e75191
--- /dev/null
+++ b/common/web/types/tests/helpers/index.ts
@@ -0,0 +1,13 @@
+import * as path from "path";
+import { fileURLToPath } from "url";
+
+/**
+ * Builds a path to the fixture with the given path components.
+ *
+ * e.g., makePathToFixture('basic.xml')
+ *
+ * @param components One or more path components.
+ */
+export function makePathToFixture(...components: string[]): string {
+ return fileURLToPath(new URL(path.join('..', '..', '..', 'tests', 'fixtures', ...components), import.meta.url));
+}
diff --git a/common/web/types/test/kmx/test-keyman-targets.ts b/common/web/types/tests/kmx/keyman-targets.tests.ts
similarity index 100%
rename from common/web/types/test/kmx/test-keyman-targets.ts
rename to common/web/types/tests/kmx/keyman-targets.tests.ts
diff --git a/common/web/types/test/kmx/test-kmx-file.ts b/common/web/types/tests/kmx/kmx-file.tests.ts
similarity index 100%
rename from common/web/types/test/kmx/test-kmx-file.ts
rename to common/web/types/tests/kmx/kmx-file.tests.ts
diff --git a/common/web/types/test/kvk/test-kvk-file.ts b/common/web/types/tests/kvk/kvk-file.tests.ts
similarity index 90%
rename from common/web/types/test/kvk/test-kvk-file.ts
rename to common/web/types/tests/kvk/kvk-file.tests.ts
index 8a1179b67b2..8e0387fefe3 100644
--- a/common/web/types/test/kvk/test-kvk-file.ts
+++ b/common/web/types/tests/kvk/kvk-file.tests.ts
@@ -2,7 +2,7 @@ import * as fs from 'fs';
import 'mocha';
import { makePathToFixture } from '../helpers/index.js';
import KvkFileReader from "../../src/kvk/kvk-file-reader.js";
-import { verify_balochi_inpage, verify_khmer_angkor } from './test-kvk-utils.js';
+import { verify_balochi_inpage, verify_khmer_angkor } from './kvk-utils.tests.js';
describe('kvk-file-reader', function () {
it('kvk-file-reader should read a valid file', function() {
diff --git a/common/web/types/test/kvk/test-kvk-utils.ts b/common/web/types/tests/kvk/kvk-utils.tests.ts
similarity index 100%
rename from common/web/types/test/kvk/test-kvk-utils.ts
rename to common/web/types/tests/kvk/kvk-utils.tests.ts
diff --git a/common/web/types/test/ldml-keyboard/test-pattern-parser.ts b/common/web/types/tests/ldml-keyboard/pattern-parser.tests.ts
similarity index 95%
rename from common/web/types/test/ldml-keyboard/test-pattern-parser.ts
rename to common/web/types/tests/ldml-keyboard/pattern-parser.tests.ts
index 01d5a183c25..bf45933d158 100644
--- a/common/web/types/test/ldml-keyboard/test-pattern-parser.ts
+++ b/common/web/types/tests/ldml-keyboard/pattern-parser.tests.ts
@@ -33,7 +33,7 @@ describe('Test of Pattern Parsers', () => {
// indirectly tests REFERENCE
it('should match reference strings', () => {
const cases: string[][] = [
- ['\\m{acute}', 'acute'],
+ ['\\m{acute} but not \\\\m{chronic}', 'acute'], // second marker is escaped
['\\m{acute}≈\\m{acute}', 'acute acute'], // not deduped
['\\m{grave}≠\\m{acute}', 'grave acute'],
[MarkerParser.ANY_MARKER, MarkerParser.ANY_MARKER_ID],
@@ -55,6 +55,19 @@ describe('Test of Pattern Parsers', () => {
assert.deepEqual(MarkerParser.allReferences(str), [], `expected no markers: ${str}`);
}
});
+ it('should match broken reference strings', () => {
+ const cases: string[][] = [
+ // hyphenated marker id - illegal
+ ['\\m{chronic} \\m{a-cute} \\\\m{a-choo}', 'a-cute'], // \\m{a-choo} is literal
+ // marker through end of line
+ ['\\m{chronic} \\m{oopsIforGot to terminate it', 'oopsIforGot to terminate it'],
+ // marker terminated by other valid marker
+ ['\\m{chronic} \\m{what \\m{does} \\m{this button do?', 'what ', 'this button do?'],
+ ];
+ for (const [str, ...reflist] of cases) {
+ assert.sameDeepMembers(MarkerParser.allBrokenReferences(str), reflist, `for ${str}`);
+ }
+ });
it('should be able to emit sentinel values', () => {
assert.equal(MarkerParser.markerOutput(295), '\uFFFF\u0008\u0127', 'Wrong sentinel value emitted');
assert.equal(MarkerParser.markerOutput(MarkerParser.ANY_MARKER_INDEX), '\uFFFF\u0008\uD7FF', 'Wrong sentinel value emitted for ANY_MARKER_INDEX');
diff --git a/common/web/types/tests/ldml-keyboard/string-list.tests.ts b/common/web/types/tests/ldml-keyboard/string-list.tests.ts
new file mode 100644
index 00000000000..58365bda66b
--- /dev/null
+++ b/common/web/types/tests/ldml-keyboard/string-list.tests.ts
@@ -0,0 +1,216 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * Created by Dr Mark C. Sinclair on 2024-11-28
+ *
+ * Test code for string-lists.ts
+ */
+
+import 'mocha';
+import { assert } from 'chai';
+import { StrsItem, StrsOptions, DependencySections, Strs } from '../../src/kmx/kmx-plus/kmx-plus.js';
+import { ListIndex, ListItem } from '../../src/ldml-keyboard/string-list.js';
+
+describe('Test of String-List', () => {
+ describe('Test ListIndex', () => {
+ it('can construct a ListIndex', () => {
+ const strsItem = new StrsItem("abc");
+ const actual = new ListIndex(strsItem);
+ assert.deepEqual(actual.value, strsItem);
+ });
+ it('can check two ListIndex for equality', () => {
+ const listItemOne = new ListIndex(new StrsItem("abc"));
+ const listItemTwo = new ListIndex(new StrsItem("abc"));
+ assert.isTrue(listItemOne.isEqual(listItemTwo));
+ });
+ it('can check two different ListIndex are not equal', () => {
+ const listItemOne = new ListIndex(new StrsItem("abc"));
+ const listItemTwo = new ListIndex(new StrsItem("def"));
+ assert.isFalse(listItemOne.isEqual(listItemTwo));
+ });
+ it('can check a ListIndex and string for equality', () => {
+ const listItem = new ListIndex(new StrsItem("abc"));
+ const aString = "abc";
+ assert.isTrue(listItem.isEqual(aString));
+ });
+ it('can check a ListIndex and string for inequality', () => {
+ const listItem = new ListIndex(new StrsItem("abc"));
+ const aString = "def";
+ assert.isFalse(listItem.isEqual(aString));
+ });
+ it('can provide a correct string representation', () => {
+ const strsItem = new StrsItem("abc");
+ const listItem = new ListIndex(strsItem);
+ const expected = "abc";
+ assert.deepEqual(listItem.toString(), expected);
+ });
+ });
+ describe('Test ListItem', () => {
+ describe('Test fromStrings()', () => {
+ it('should return an empty ListItem if source is null', () => {
+ const actual = ListItem.fromStrings(null, null, null);
+ const expected = new ListItem();
+ assert.deepEqual(actual, expected);
+ });
+ it('should return a valid ListItem from a single source string', () => {
+ const source = ["abc"];
+ const sections = { strs: new Strs };
+ sections.strs.allocString = stubSectionsStrsAllocString;
+ const actual = ListItem.fromStrings(source, null, sections);
+ const expected = initListItem(source);
+ assert.deepEqual(actual, expected);
+ });
+ it('should return a valid ListItem from a longer source', () => {
+ const source = ["abc", "def", "ghi"];
+ const sections = { strs: new Strs };
+ sections.strs.allocString = stubSectionsStrsAllocString;
+ const actual = ListItem.fromStrings(source, null, sections);
+ const expected = initListItem(source);
+ assert.deepEqual(actual, expected);
+ });
+ });
+ describe('Test getItemOrder()', () => {
+ it('should return a valid index for the first item', () => {
+ const listItem = initListItem(["abc", "def", "ghi"]);
+ const index = listItem.getItemOrder("abc");
+ assert.equal(index, 0);
+ });
+ it('should return a valid index for a later item', () => {
+ const listItem = initListItem(["abc", "def", "ghi"]);
+ const index = listItem.getItemOrder("ghi");
+ assert.equal(index, 2);
+ });
+ it('should return -1 for a missing item', () => {
+ const listItem = initListItem(["abc", "def", "ghi"]);
+ const index = listItem.getItemOrder("jkl");
+ assert.equal(index, -1);
+ });
+ });
+ describe('Test isEqual()', () => {
+ it('should return true for two empty ListItems', () => {
+ const listItemOne = new ListItem();
+ const listItemTwo = new ListItem();
+ assert.isTrue(listItemOne.isEqual(listItemTwo));
+ });
+ it('should return false for empty and non-empty ListItems', () => {
+ const listItemOne = new ListItem();
+ const listItemTwo = initListItem(["abc"]);
+ assert.isFalse(listItemOne.isEqual(listItemTwo));
+ });
+ it('should return false for non-empty and empty ListItems', () => {
+ const listItemOne = initListItem(["abc"]);
+ const listItemTwo = new ListItem();
+ assert.isFalse(listItemOne.isEqual(listItemTwo));
+ });
+ it('should return true for identical ListItems', () => {
+ const listItemOne = initListItem(["abc", "def", "ghi"]);
+ const listItemTwo = initListItem(["abc", "def", "ghi"]);
+ assert.isTrue(listItemOne.isEqual(listItemTwo));
+ });
+ it('should return false for different ListItems', () => {
+ const listItemOne = initListItem(["abc", "def", "ghi"]);
+ const listItemTwo = initListItem(["abd", "def", "ghi"]);
+ assert.isFalse(listItemOne.isEqual(listItemTwo));
+ });
+ it('should return false for different length ListItems', () => {
+ const listItemOne = initListItem(["abc", "def"]);
+ const listItemTwo = initListItem(["abc", "def", "ghi"]);
+ assert.isFalse(listItemOne.isEqual(listItemTwo));
+ });
+ it('should return true for empty ListItem and string[]', () => {
+ const listItem = new ListItem();
+ assert.isTrue(listItem.isEqual([]));
+ });
+ it('should return false for empty ListItem and non-empty string[]', () => {
+ const listItem = new ListItem();
+ assert.isFalse(listItem.isEqual(["abc"]));
+ });
+ it('should return false for non-empty ListItem and empty string[]', () => {
+ const listItem = initListItem(["abc"]);;
+ assert.isFalse(listItem.isEqual([]));
+ });
+ it('should return true for identical ListItem and string[]', () => {
+ const listItem = initListItem(["abc", "def", "ghi"]);
+ assert.isTrue(listItem.isEqual(["abc", "def", "ghi"]));
+ });
+ it('should return false for different ListItem and string[]', () => {
+ const listItem = initListItem(["abc", "def", "ghi"]);
+ assert.isFalse(listItem.isEqual(["abd", "def", "ghi"]));
+ });
+ it('should return false for different length ListItem and string[]', () => {
+ const listItem = initListItem(["abc", "def"]);
+ assert.isFalse(listItem.isEqual(["abc", "def", "ghi"]));
+ });
+ });
+ describe('Test compareTo()', () => {
+ it('should return 0 for identical ListItems', () => {
+ const listItemOne = initListItem(["abc", "def", "ghi"]);
+ const listItemTwo = initListItem(["abc", "def", "ghi"]);
+ assert.equal(listItemOne.compareTo(listItemTwo), 0);
+ });
+ it('should return -1 for ListItems with different first items (smallest first)', () => {
+ const listItemOne = initListItem(["abc", "def", "ghi"]);
+ const listItemTwo = initListItem(["abd", "def", "ghi"]);
+ assert.equal(listItemOne.compareTo(listItemTwo), -1);
+ });
+ it('should return 1 for ListItems with different first items (smallest second)', () => {
+ const listItemOne = initListItem(["abd", "def", "ghi"]);
+ const listItemTwo = initListItem(["abc", "def", "ghi"]);
+ assert.equal(listItemOne.compareTo(listItemTwo), 1);
+ });
+ it('should return -1 for ListItems with different later items (smallest first)', () => {
+ const listItemOne = initListItem(["abc", "def", "ghi"]);
+ const listItemTwo = initListItem(["abc", "def", "ghj"]);
+ assert.equal(listItemOne.compareTo(listItemTwo), -1);
+ });
+ it('should return 1 for ListItems with different later items (smallest second)', () => {
+ const listItemOne = initListItem(["abc", "def", "ghj"]);
+ const listItemTwo = initListItem(["abc", "def", "ghi"]);
+ assert.equal(listItemOne.compareTo(listItemTwo), 1);
+ });
+ it('should return -1 for identical ListItems, except shorter first', () => {
+ const listItemOne = initListItem(["abc", "def", "ghi"]);
+ const listItemTwo = initListItem(["abc", "def", "ghi", "jkl"]);
+ assert.equal(listItemOne.compareTo(listItemTwo), -1);
+ });
+ it('should return 1 for identical ListItems, except longer first', () => {
+ const listItemOne = initListItem(["abc", "def", "ghi", "jkl"]);
+ const listItemTwo = initListItem(["abc", "def", "ghi"]);
+ assert.equal(listItemOne.compareTo(listItemTwo), 1);
+ });
+ });
+ describe('Test toString()', () => {
+ it('should return correct string', () => {
+ const listItem = initListItem(["abc", "def", "ghi"]);
+ assert.deepEqual(listItem.toString(), "abc def ghi");
+ });
+ it('should return correct string for empty ListItem', () => {
+ const listItem = new ListItem;
+ assert.deepEqual(listItem.toString(), "");
+ });
+ });
+ describe('Test toStringArray()', () => {
+ it('should return correct string[]', () => {
+ const source = ["abc", "def", "ghi"];
+ const listItem = initListItem(source);
+ assert.deepEqual(listItem.toStringArray(), source);
+ });
+ it('should return correct string[] for empty ListItem', () => {
+ const listItem = new ListItem;
+ assert.deepEqual(listItem.toStringArray(), []);
+ });
+ });
+ });
+});
+
+function stubSectionsStrsAllocString(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
+ return new StrsItem(s);
+}
+
+function initListItem(source: Array): ListItem {
+ const listItem = new ListItem();
+ for (const s of source) {
+ listItem.push(new ListIndex(new StrsItem(s)));
+ }
+ return listItem;
+}
diff --git a/common/web/types/tests/ldml-keyboard/unicodeset-parser-api.tests.ts b/common/web/types/tests/ldml-keyboard/unicodeset-parser-api.tests.ts
new file mode 100644
index 00000000000..763bc6d5bb9
--- /dev/null
+++ b/common/web/types/tests/ldml-keyboard/unicodeset-parser-api.tests.ts
@@ -0,0 +1,24 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * Created by Dr Mark C. Sinclair on 2024-11-29
+ *
+ * Test code for unicodeset-parser-api.ts
+ */
+
+import 'mocha';
+import { assert } from 'chai';
+import { UnicodeSet } from '../../src/ldml-keyboard/unicodeset-parser-api.js';
+
+describe('Test of Unicode-Parser-API', () => {
+ describe('Test UnicodeSet', () => {
+ it('can provide a correct ranges length', () => {
+ const unicodeSet = new UnicodeSet("[ħa-z]", [[0x41, 0x7A], [0x0127, 0x0127]]);
+ assert.equal(unicodeSet.length, 2);
+ });
+ it('can provide a correct string representation', () => {
+ const unicodeSet = new UnicodeSet("[ħa-z]", [[0x41, 0x7A], [0x0127, 0x0127]]);
+ assert.deepEqual(unicodeSet.toString(), "[ħa-z]");
+ });
+ });
+});
diff --git a/common/web/types/tests/lexical-model-types.tests.ts b/common/web/types/tests/lexical-model-types.tests.ts
new file mode 100644
index 00000000000..ef8bed28a50
--- /dev/null
+++ b/common/web/types/tests/lexical-model-types.tests.ts
@@ -0,0 +1,25 @@
+/**
+ * This file "tests" the exports from the main module.
+ *
+ * Since the exports are all types, the "test" here is that the type can be
+ * imported and compiled without any compiler errors.
+ */
+
+import { KMXPlus, LdmlKeyboardTypes, LexicalModelTypes } from "@keymanapp/common-types";
+
+export let u: LexicalModelTypes.USVString;
+export let l: LexicalModelTypes.Transform;
+export let s: LexicalModelTypes.Suggestion;
+export let st: LexicalModelTypes.SuggestionTag;
+export let c: LexicalModelTypes.Context;
+export let cap: LexicalModelTypes.Capabilities;
+export let conf: LexicalModelTypes.Configuration;
+export let d: LexicalModelTypes.Distribution;
+export let wbf: LexicalModelTypes.WordBreakingFunction;
+export let sp: LexicalModelTypes.Span;
+export let lmp: LexicalModelTypes.LexicalModelPunctuation;
+
+
+// try some of the other types - that should still work
+export let elemString: LdmlKeyboardTypes.ElementString;
+export let section: KMXPlus.Section;
diff --git a/common/web/types/test/tsconfig.json b/common/web/types/tests/tsconfig.json
similarity index 90%
rename from common/web/types/test/tsconfig.json
rename to common/web/types/tests/tsconfig.json
index 9678c49945f..45e5311fdc0 100644
--- a/common/web/types/test/tsconfig.json
+++ b/common/web/types/tests/tsconfig.json
@@ -4,13 +4,12 @@
"compilerOptions": {
"rootDir": ".",
"rootDirs": ["./", "../src/"],
- "outDir": "../build/test",
+ "outDir": "../build/tests",
"baseUrl": ".",
"strictNullChecks": false, // TODO: get rid of this as some point
"allowSyntheticDefaultImports": true
},
"include": [
- "**/test-*.ts",
"**/*.tests.ts",
"./helpers/*.ts",
],
diff --git a/common/web/types/tests/util/file-types.tests.ts b/common/web/types/tests/util/file-types.tests.ts
new file mode 100644
index 00000000000..3f4d6870918
--- /dev/null
+++ b/common/web/types/tests/util/file-types.tests.ts
@@ -0,0 +1,234 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * Created by Dr Mark C. Sinclair on 2024-11-29
+ *
+ * Test code for file-types.ts
+ */
+
+import 'mocha';
+import { assert } from 'chai';
+import {
+ ALL,
+ ALL_SOURCE,
+ ALL_BINARY,
+ Binary,
+ fromFilename,
+ removeExtension,
+ sourceOrBinaryTypeFromFilename,
+ sourceTypeFromFilename,
+ binaryTypeFromFilename,
+ filenameIs,
+ replaceExtension,
+} from '../../src/util/file-types.js';
+
+describe('Test of File-Types', () => {
+ describe('Test of fromFilename()', () => {
+ it('can extract Source file extension', () => {
+ ALL_SOURCE.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = fromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ it('can extract Binary file extension', () => {
+ ALL_BINARY.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = fromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ it('can extract unmatched file extension', () => {
+ const ext = ".cpp";
+ assert.isFalse((Object.values(ALL_SOURCE) as string[]).includes(ext));
+ const filename = `file${ext}`;
+ const actual = fromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ it('returns empty string for no file extension', () => {
+ const filename = `file`;
+ const actual = fromFilename(filename);
+ assert.deepEqual(actual, "");
+ });
+ it('can extract upper case file extension', () => {
+ const ext = ALL_SOURCE[0];
+ const upperCaseExt = ext.toUpperCase();
+ const filename = `file${upperCaseExt}`;
+ const actual = fromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ describe('Test of removeExtension()', () => {
+ it('can remove Source file extension', () => {
+ ALL_SOURCE.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = removeExtension(filename);
+ assert.deepEqual(actual, "file");
+ });
+ });
+ it('can remove Binary file extension', () => {
+ ALL_BINARY.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = removeExtension(filename);
+ assert.deepEqual(actual, "file");
+ });
+ });
+ it('can handle no file extension', () => {
+ const filename = removeExtension("file");
+ assert.deepEqual(filename, "file");
+ });
+ });
+ describe('Test of sourceOrBinaryTypeFromFilename()', () => {
+ it('can extract Source file extension', () => {
+ ALL_SOURCE.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = sourceOrBinaryTypeFromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ it('can extract Binary file extension', () => {
+ ALL_BINARY.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = sourceOrBinaryTypeFromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ it('returns null for unmatched file extension', () => {
+ const ext = ".cpp";
+ assert.isFalse((Object.values(ALL) as string[]).includes(ext));
+ const filename = `file${ext}`;
+ const actual = sourceOrBinaryTypeFromFilename(filename);
+ assert.isNull(actual);
+ });
+ it('can extract upper case file extension', () => {
+ const ext = ALL[0];
+ const upperCaseExt = ext.toUpperCase();
+ const filename = `file${upperCaseExt}`;
+ const actual = sourceOrBinaryTypeFromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ describe('Test of sourceTypeFromFilename()', () => {
+ it('can extract Source file extension', () => {
+ ALL_SOURCE.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = sourceTypeFromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ it('returns null for a Binary file extension', () => {
+ ALL_BINARY.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = sourceTypeFromFilename(filename);
+ assert.isNull(actual);
+ });
+ });
+ it('returns null for unmatched file extension', () => {
+ const ext = ".cpp";
+ assert.isFalse((Object.values(ALL_SOURCE) as string[]).includes(ext));
+ const filename = `file${ext}`;
+ const actual = sourceTypeFromFilename(filename);
+ assert.isNull(actual);
+ });
+ it('can extract upper case file extension', () => {
+ const ext = ALL_SOURCE[0];
+ const upperCaseExt = ext.toUpperCase();
+ const filename = `file${upperCaseExt}`;
+ const actual = sourceTypeFromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ describe('Test of binaryTypeFromFilename()', () => {
+ it('returns null for a Source file extension', () => {
+ ALL_SOURCE.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = binaryTypeFromFilename(filename);
+ assert.isNull(actual);
+ });
+ });
+ it('can extract Binary file extension', () => {
+ ALL_BINARY.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = binaryTypeFromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ it('returns null for unmatched file extension', () => {
+ const ext = ".cpp";
+ assert.isFalse((Object.values(ALL_BINARY) as string[]).includes(ext));
+ const filename = `file${ext}`;
+ const actual = binaryTypeFromFilename(filename);
+ assert.isNull(actual);
+ });
+ it('can extract upper case file extension', () => {
+ const ext = ALL_BINARY[0];
+ const upperCaseExt = ext.toUpperCase();
+ const filename = `file${upperCaseExt}`;
+ const actual = binaryTypeFromFilename(filename);
+ assert.deepEqual(actual, ext);
+ });
+ });
+ describe('Test of filenameIs()', () => {
+ it('can identify Source file extension', () => {
+ ALL_SOURCE.forEach((ext) => {
+ const filename = `file${ext}`;
+ const actual = filenameIs(filename, ext);
+ assert.isTrue(actual);
+ });
+ });
+ it('can identify Binary file extension', () => {
+ ALL_BINARY.forEach((ext) => {
+ const filename = `file${ext}`;
+ if (ext == Binary.Model) { // Special case for .model.js
+ const actual = filenameIs(filename, Binary.WebKeyboard);
+ assert.isFalse(actual);
+ }
+ const actual = filenameIs(filename, ext);
+ assert.isTrue(actual);
+ });
+ });
+ it('can identify upper case file extension', () => {
+ const ext = ALL[0];
+ const upperCaseExt = ext.toUpperCase();
+ const filename = `file${upperCaseExt}`;
+ const actual = filenameIs(filename, ext);
+ assert.isTrue(actual);
+ });
+ });
+ describe('Test of replaceExtension()', () => {
+ it('can replace an extension', () => {
+ const oldExt = ".cpp";
+ const newExt = ".js";
+ const oldFilename = `file${oldExt}`;
+ const newFilename = `file${newExt}`;
+ const actual = replaceExtension(oldFilename, oldExt, newExt);
+ assert.deepEqual(actual, newFilename);
+ });
+ it('should return null for incorrect old extension (too short)', () => {
+ const oldExt = ".ts";
+ const newExt = ".js";
+ const oldFilename = `file.c`;
+ const actual = replaceExtension(oldFilename, oldExt, newExt);
+ assert.isNull(actual);
+ });
+ it('should return null for incorrect old extension (too long)', () => {
+ const oldExt = ".ts";
+ const newExt = ".js";
+ const oldFilename = `file.cpp`;
+ const actual = replaceExtension(oldFilename, oldExt, newExt);
+ assert.isNull(actual);
+ });
+ // it('should return null for null old extension', () => {
+ // const newExt = ".js";
+ // const oldFilename = `file.ts`;
+ // const actual = replaceExtension(oldFilename, null, newExt);
+ // assert.isNull(actual);
+ // });
+ // it('should return null for null new extension', () => {
+ // const oldExt = ".ts";
+ // const oldFilename = `file.ts`;
+ // const actual = replaceExtension(oldFilename, oldExt, null);
+ // assert.isNull(actual);
+ // });
+ });
+});
diff --git a/common/web/types/test/util/test-unescape.ts b/common/web/types/tests/util/unescape.tests.ts
similarity index 94%
rename from common/web/types/test/util/test-unescape.ts
rename to common/web/types/tests/util/unescape.tests.ts
index 8c75b491824..3eaafd7150f 100644
--- a/common/web/types/test/util/test-unescape.ts
+++ b/common/web/types/tests/util/unescape.tests.ts
@@ -1,6 +1,6 @@
import 'mocha';
import {assert} from 'chai';
-import {unescapeString, UnescapeError, isOneChar, toOneChar, unescapeOneQuadString, BadStringAnalyzer, isValidUnicode, describeCodepoint, isPUA, BadStringType, unescapeStringToRegex, unescapeQuadString, NFDAnalyzer} from '../../src/util/util.js';
+import {unescapeString, UnescapeError, isOneChar, toOneChar, unescapeOneQuadString, BadStringAnalyzer, isValidUnicode, describeCodepoint, isPUA, BadStringType, unescapeStringToRegex, unescapeQuadString, NFDAnalyzer, isNormalized} from '../../src/util/util.js';
describe('test UTF32 functions()', function() {
it('should properly categorize strings', () => {
@@ -186,6 +186,24 @@ describe('test bad char functions', () => {
assert.isTrue(isPUA(ch), describeCodepoint(ch));
}
});
+ describe('test isDenormalized()', () => {
+ it('should correctly categorize strings', () => {
+ [
+ undefined,
+ null,
+ '',
+ 'ABC',
+ 'fa\u1E69cinating', // NFC
+ 'fas\u0323\u0307cinating', // NFD
+ 'd\u0323\u0307', // NFD
+ '\u1e0d\u0307', // NFC
+ ].map(s => assert.isTrue(isNormalized(s), `for string ${s}`));
+ [
+ 'd\u0307\u0323', // NFD but reversed marks
+ 'fas\u0307\u0323cinating', // not-NFD
+ ].map(s => assert.isFalse(isNormalized(s), `for string ${s}`));
+ });
+ });
});
describe('test BadStringAnalyzer', () => {
diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas
index bc534b9f526..fbfa9961d70 100644
--- a/common/windows/delphi/general/KeymanPaths.pas
+++ b/common/windows/delphi/general/KeymanPaths.pas
@@ -436,12 +436,18 @@ class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean;
class function TKeymanPaths.KeymanCoreLibraryPath(const Filename: string): string;
var
keyman_root: string;
+ configuration: string;
begin
// Look up KEYMAN_ROOT development variable -- if found and executable
// within that path then use that as source path
if TKeymanPaths.RunningFromSource(keyman_root) then
begin
- Exit(keyman_root + 'core\build\x86\debug\src\' + Filename);
+{$IFDEF DEBUG}
+ configuration := 'debug';
+{$ELSE}
+ configuration := 'release';
+{$ENDIF}
+ Exit(keyman_root + 'core\build\x86\'+configuration+'\src\' + Filename);
end;
Result := GetDebugPath('KeymanCoreLibraryPath', '');
diff --git a/core/doc/BUILDING.md b/core/docs/BUILDING.md
similarity index 100%
rename from core/doc/BUILDING.md
rename to core/docs/BUILDING.md
diff --git a/core/docs/api/background.md b/core/docs/api/background.md
new file mode 100644
index 00000000000..d4c638f0ce7
--- /dev/null
+++ b/core/docs/api/background.md
@@ -0,0 +1,417 @@
+---
+title: Background - Keyman Core API
+---
+
+## Namespace
+All calls, types and enums are prefixed with the namespace identifier `km_core_`
+
+## API idioms
+
+### Error Handling
+
+Error handling and success failure notification are communicated through a
+general mechanism similar to COM’s `HRESULT` scheme (unlike COM, any non-zero
+value is an error). Any functions that can fail will always return a status
+value and all results are returned via outparams passed to the function.
+
+### Passing variable length data out
+
+Almost all calls marshalling variable length aggregate data in or out of an API
+object take the form:
+
+```c
+km_core_status fn_name(object_ref, buffer_ptr, size_ptr)
+```
+where the `buffer_ptr` is nullable and all other arguments are required (will
+result in an [`KM_CORE_STATUS_INVALID_ARGUMENT`](#km_core_status_codes)
+status being returned if nulled). When `buffer_ptr` is `nullptr` or `0` the
+function will place the size of the required buffer in the variable pointed to
+by `size_ptr`.
+
+### Resource management
+
+Calls which result in the allocation of resources, regardless of resulting
+ownership, are of the form:
+```c
+km_core_status fn_name(object_ref, handle_out_ptr)
+```
+where `handle_out_ptr` is a valid pointer to a caller allocated variable to hold
+the resulting resource handle. This is often a reference to a created object.
+Unless stated all arguments are required (will result in an
+[`KM_CORE_STATUS_INVALID_ARGUMENT`](#km_core_status_codes) status being
+returned if nulled).
+
+All dispose calls are designed to accept `nullptr` or `0` as a valid value and
+will do nothing in that event.
+
+### Fixed size attribute access
+
+For accessors to fixed size attributes of an object these will take the form:
+```c
+attr_value fn_name(object_ref)
+```
+`object_ref` is required to be valid and will result in a nonsense value being returned if `nullptr` or `0`.
+
+### Versioning scheme
+
+This follows the libtool interface versioning scheme of `current.age.revision`:
+
+`current`
+
+The most recent interface number that the engine implements.
+
+`age`
+
+How many interface numbers back from current the library implements. E.g. 5.2.0
+would mean the library provides interface versions 3-5 and 5.0.0 would mean just
+interface version 5 and nothing older.
+
+`revision`
+
+The implementation version of the current interface. This represents
+improvements to the code that don't change the intended behaviour of the
+interface such as bug fixes and optimisations.
+
+For Linux and other OS which support this scheme the dynamic linker will
+automatically choose the most updated version if more than one implementation is
+available. For Windows or dynamic loaded shared objects on Linux you can use the
+[km_core_get_engine_attrs] call and [Library version
+macros](#lib-version-macros) to check the loaded DLL supplies the correct
+interface.
+
+-------------------------------------------------------------------------------
+
+# Common functions, types, and macros
+
+
+## Basic types
+
+Fundamental types for representing data passed across the API.
+
+### km_core_cp type {#km_core_cp}
+
+`uint16_t/char16_t`
+
+Represents a UTF16 codepoint, most strings are passed as UTF16.
+
+### km_core_usv type {#km_core_usv}
+
+`uint32_t/char32_t`
+
+An integral type capable of holding a single Unicode Scalar Value, a decoded UTF
+codepoint.
+
+### km_core_virtual_key type {#km_core_virtual_key}
+
+`uint16_t`
+
+An integral type capable of holding a platform specific virtual key code.
+
+### km_core_status type {#km_core_status}
+
+`uint32_t`
+
+An integral 32 bit wide type capable of holding any valid status code as defined
+by the `enum` [km_core_status_codes].
+
+### km_core_modifier_state type {#km_core_modifier_state}
+
+`uint16_t`
+
+An integral type bitmask representing the state of each modifier key.
+
+
+
+## Resource types
+
+Opaque types for representing resources provided or created by the keyboard
+processor implementation.
+
+### km_core_keyboard struct {#km_core_keyboard}
+
+Represents a keyboard loaded from disk, that can be executed by the keyboard
+processor to consume events, update state associated with an insertion point and
+produce action items. A keyboard object may be referenced by any number of state
+objects but must be disposed of after all state objects referencing it have
+first been disposed of.
+
+### km_core_state struct {#km_core_state}
+
+Represents all state associated with an insertion point using a keyboard. This
+tracks context, and current action items resulting from a processed keyboard
+event. There can be many state objects using the same keyboard. A state object
+may not live longer than the keyboard it manages state for.
+
+### km_core_options struct {#km_core_options}
+
+Represents a set of option items for environmental state and keyboard state.
+
+
+
+
+
+
+-------------------------------------------------------------------------------
+
+# km_core_status_codes enum {#km_core_status_codes}
+
+## Description
+
+An error code mechanism similar to COM’s `HRESULT` scheme (unlike COM, any
+non-zero value is an error).
+
+## Specification
+
+```c
+enum km_core_status_codes {
+ KM_CORE_STATUS_OK = 0,
+ KM_CORE_STATUS_NO_MEM = 1,
+ KM_CORE_STATUS_IO_ERROR = 2,
+ KM_CORE_STATUS_INVALID_ARGUMENT = 3,
+ KM_CORE_STATUS_KEY_ERROR = 4,
+ KM_CORE_STATUS_INSUFFICENT_BUFFER = 5,
+ KM_CORE_STATUS_INVALID_UTF = 6,
+ KM_CORE_STATUS_INVALID_KEYBOARD = 7,
+ KM_CORE_STATUS_NOT_IMPLEMENTED = 8,
+ KM_CORE_STATUS_OS_ERROR = 0x80000000
+};
+
+```
+
+## Values
+
+`KM_CORE_STATUS_OK`
+
+: Success code. Call completed as documented.
+
+`KM_CORE_STATUS_NO_MEM`
+
+: The call failed to allocate memory during its execution, causing it to fail.
+
+`KM_CORE_STATUS_IO_ERROR`
+
+: The call performed an I/O operation which failed, causing it to fail.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+
+: The call detected one of its parameters was invalid or unsafe.
+
+`KM_CORE_STATUS_KEY_ERROR`
+
+: The provided key or index into a collection object was not present.
+
+`KM_CORE_STATUS_INSUFFICENT_BUFFER`
+
+: The provided buffer did not contain enough space to fully encode or copy the
+result of this call.
+
+`KM_CORE_STATUS_INVALID_UTF`
+
+: A malformed or partial UTF sequence prevented complete decoding of a unicode
+string.
+
+`KM_CORE_STATUS_INVALID_KEYBOARD`
+
+: An attempt to decode a keyboard file failed.
+
+`KM_CORE_STATUS_OS_ERROR`
+
+: This allows encapsulating a platform error code: the remaining 31 low bits are
+the error code returned by the OS for cases where the failure mode is platform
+specific. For HRESULT codes this only permits failure codes to be passed and not
+success codes.
+
+-------------------------------------------------------------------------------
+
+# km_core_attr struct {#km_core_attr}
+
+## Description
+
+A structure describing information about Keyman Core implementing this API.
+
+## Specification
+
+```c
+
+typedef struct {
+ size_t max_context;
+ uint16_t current;
+ uint16_t revision;
+ uint16_t age;
+ uint16_t technology;
+ char const *vendor;
+} km_core_attr;
+
+```
+## Members
+
+`max_context`
+: Maximum context size supported by processor.
+
+`current`
+: Current API number supported.
+
+`revision`
+: Implementation number of current API.
+
+`age`
+: current - age == Oldest API number supported.
+
+`technology`
+: A bit field of [km_core_tech_value] values,
+specifiying which Keyboard technologies the engine supports.
+
+`vendor`
+: A UTF-8 encoded string identifying the implementer of the processor.
+
+-------------------------------------------------------------------------------
+
+# km_core_tech_value enum {#km_core_tech_value}
+
+## Description
+
+Values for a bit field indicating which keyboarding technologies a keyboard
+processor supports.
+
+## Specification
+
+```c
+
+enum km_core_tech_value {
+ KM_CORE_TECH_UNSPECIFIED = 0,
+ KM_CORE_TECH_MOCK = 1 << 0,
+ KM_CORE_TECH_KMX = 1 << 1,
+ KM_CORE_TECH_LDML = 1 << 2
+};
+
+```
+## Values
+
+`KM_CORE_TECH_UNSPECIFIED`
+: The keyboard processor implementation does not disclose which technologies it
+implements.
+
+`KM_CORE_TECH_MOCK`
+: The keyboard processor implements a simple en-US keyboard for the purposes of
+testing the API.
+
+`KM_CORE_TECH_UNSPECIFIED`
+: The keyboard processor implements a Keyman KMX compatible engine.
+
+`KM_CORE_TECH_UNSPECIFIED`
+: The keyboard processor implements a LDML capable processing engine.
+
+-------------------------------------------------------------------------------
+
+# km_core_get_engine_attrs() {#km_core_get_engine_attrs}
+
+## Description
+
+Get access processors attributes describing version and technology implemented.
+
+## Specification
+
+```c
+KMN_API
+km_core_attr const *
+km_core_get_engine_attrs(km_core_state const *state);
+
+```
+
+## Parameters
+
+`state`
+: An opaque pointer to a [km_core_state].
+
+## Returns
+A pointer to a [km_core_attr] structure. Do not modify the contents of this
+structure.
+
+-------------------------------------------------------------------------------
+
+# km_core_bool enum {#km_core_bool}
+
+## Description
+
+Defines a boolean state.
+
+## Specification
+```c
+typedef enum { KM_CORE_FALSE = 0, KM_CORE_TRUE = 1 } km_core_bool;
+
+```
+-------------------------------------------------------------------------------
+
+
+[km_core_cp]: background#km_core_cp "km_core_cp type"
+[km_core_usv]: background#km_core_usv "km_core_usv type"
+[km_core_virtual_key]: background#km_core_virtual_key "km_core_virtual_key type"
+[km_core_status]: background#km_core_status "km_core_status type"
+[km_core_modifier_state]: background#km_core_modifier_state "km_core_modifier_state type"
+[km_core_keyboard]: background#km_core_keyboard "km_core_keyboard struct"
+[km_core_state]: background#km_core_state "km_core_state struct"
+[km_core_options]: background#km_core_options "km_core_options struct"
+[km_core_status_codes]: background#km_core_status_codes "km_core_status_codes enum"
+[km_core_attr]: background#km_core_attr "km_core_attr struct"
+[km_core_tech_value]: background#km_core_tech_value "km_core_tech_value enum"
+[km_core_get_engine_attrs]: background#km_core_get_engine_attrs "km_core_get_engine_attrs function"
+[km_core_bool]: background#km_core_bool "km_core_bool enum"
+[km_core_caps_state]: state#km_core_caps_state "km_core_caps_state enum"
+[km_core_actions]: state#km_core_actions "km_core_actions struct"
+[km_core_state_get_actions]: state#km_core_state_get_actions "km_core_state_get_actions function"
+[km_core_context_status]: state#km_core_context_status "km_core_context_status enum"
+[km_core_state_context_set_if_needed]: state#km_core_state_context_set_if_needed "km_core_state_context_set_if_needed function"
+[km_core_state_context_clear]: state#km_core_state_context_clear "km_core_state_context_clear function"
+[km_core_option_scope]: options#km_core_option_scope "km_core_option_scope enum"
+[km_core_option_item]: options#km_core_option_item "km_core_option_item struct"
+[km_core_options_list_size]: options#km_core_options_list_size "km_core_options_list_size function"
+[km_core_state_options_update]: options#km_core_state_options_update "km_core_state_options_update function"
+[km_core_state_options_to_json]: options#km_core_state_options_to_json "km_core_state_options_to_json function"
+[km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct"
+[km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct"
+[km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct"
+[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function"
+[km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function"
+[km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function"
+[km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function"
+[km_core_keyboard_key_list_dispose]: keyboards#km_core_keyboard_key_list_dispose "km_core_keyboard_key_list_dispose function"
+[km_core_keyboard_imx_list_dispose]: keyboards#km_core_keyboard_imx_list_dispose "km_core_keyboard_imx_list_dispose function"
+[km_core_state_imx_register_callback]: keyboards#km_core_state_imx_register_callback "km_core_state_imx_register_callback function"
+[km_core_state_imx_deregister_callback]: keyboards#km_core_state_imx_deregister_callback "km_core_state_imx_deregister_callback function"
+[km_core_state_create]: keyboards#km_core_state_create "km_core_state_create function"
+[km_core_state_clone]: keyboards#km_core_state_clone "km_core_state_clone function"
+[km_core_state_dispose]: keyboards#km_core_state_dispose "km_core_state_dispose function"
+[km_core_debug_context_type]: keyboards#km_core_debug_context_type "km_core_debug_context_type enum"
+[km_core_state_context_debug]: keyboards#km_core_state_context_debug "km_core_state_context_debug function"
+[km_core_cp_dispose]: keyboards#km_core_cp_dispose "km_core_cp_dispose function"
+[km_core_state_to_json]: keyboards#km_core_state_to_json "km_core_state_to_json function"
+[km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum"
+[km_core_process_event]: processor#km_core_process_event "km_core_process_event function"
+[km_core_event]: processor#km_core_event "km_core_event function"
+[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum"
\ No newline at end of file
diff --git a/core/docs/api/building.md b/core/docs/api/building.md
new file mode 100644
index 00000000000..65ca558670a
--- /dev/null
+++ b/core/docs/api/building.md
@@ -0,0 +1,63 @@
+---
+title: How to build Keyman Core
+---
+
+## Prerequisites
+
+### To build
+
+* Python 3
+* Meson build system.
+* C++17 or later compiler.
+
+### Optional
+
+* [kmc](https://keyman.com/developer/download) (for testing)
+
+## Installing Python3
+
+### Linux
+
+You will be able to install a python3 package in any reputable recent version of
+linux using its package manager if it's not already installed.
+
+### macOS
+
+You can get the official installer from the official Python site:
+[https://www.python.org/downloads/mac-osx](https://www.python.org/downloads/mac-osx/)
+
+### Windows
+
+You can get the official installer from the official Python site:
+[https://www.python.org/downloads/windows](https://www.python.org/downloads/windows/)
+
+## Installing Meson
+
+Ensure you have Python3 correctly installed and can run the command `pip3`.
+
+ $> python3 -m pip install meson
+
+## Building
+
+In your source directory do the following:
+
+ $> cd core
+ $> ./build.sh configure build test
+
+## Note on kmc
+
+kmc is node.js-based the command-line compiler from Keyman Developer, available
+from [keyman.com](https://keyman.com/developer/) or on npm at
+[@keymanapp/kmc](https://npmjs.com/package/@keymanapp/kmc).
+
+### Windows
+
+The search path can be edited through System settings / Advanced system settings
+/ Environment Variables / User environment variables.
+
+If you have Keyman Developer installed, kmc should already be on your path.
+Otherwise, add the path where you extracted the kmcomp archive.
+
+### Linux & MacOS
+
+Install kmc from the NPM package.
diff --git a/core/docs/api/changes.md b/core/docs/api/changes.md
new file mode 100644
index 00000000000..e5736494abc
--- /dev/null
+++ b/core/docs/api/changes.md
@@ -0,0 +1,64 @@
+---
+title: Changes - Keyman Core API
+---
+
+## Changes between 16.0 and 17.0
+
+* The namespace identifier has changed from `km_kbp_` to `km_core_`.
+* Most context APIs are now private, and [km_core_state_context_set_if_needed] is the
+ primary context function. Private APIs are available in
+ `keyman_core_api_context.h`.
+* The action queue APIs are now private and deprecated. Instead, use
+ [km_core_state_get_actions]. Private APIs are available in
+ `keyman_core_api_actions.h`.
+* Debug APIs are available in `keyman_core_api_debug.h`.
+
+-------------------------------------------------------------------------------
+
+
+[km_core_cp]: background#km_core_cp "km_core_cp type"
+[km_core_usv]: background#km_core_usv "km_core_usv type"
+[km_core_virtual_key]: background#km_core_virtual_key "km_core_virtual_key type"
+[km_core_status]: background#km_core_status "km_core_status type"
+[km_core_modifier_state]: background#km_core_modifier_state "km_core_modifier_state type"
+[km_core_keyboard]: background#km_core_keyboard "km_core_keyboard struct"
+[km_core_state]: background#km_core_state "km_core_state struct"
+[km_core_options]: background#km_core_options "km_core_options struct"
+[km_core_status_codes]: background#km_core_status_codes "km_core_status_codes enum"
+[km_core_attr]: background#km_core_attr "km_core_attr struct"
+[km_core_tech_value]: background#km_core_tech_value "km_core_tech_value enum"
+[km_core_get_engine_attrs]: background#km_core_get_engine_attrs "km_core_get_engine_attrs function"
+[km_core_bool]: background#km_core_bool "km_core_bool enum"
+[km_core_caps_state]: state#km_core_caps_state "km_core_caps_state enum"
+[km_core_actions]: state#km_core_actions "km_core_actions struct"
+[km_core_state_get_actions]: state#km_core_state_get_actions "km_core_state_get_actions function"
+[km_core_context_status]: state#km_core_context_status "km_core_context_status enum"
+[km_core_state_context_set_if_needed]: state#km_core_state_context_set_if_needed "km_core_state_context_set_if_needed function"
+[km_core_state_context_clear]: state#km_core_state_context_clear "km_core_state_context_clear function"
+[km_core_option_scope]: options#km_core_option_scope "km_core_option_scope enum"
+[km_core_option_item]: options#km_core_option_item "km_core_option_item struct"
+[km_core_options_list_size]: options#km_core_options_list_size "km_core_options_list_size function"
+[km_core_state_options_update]: options#km_core_state_options_update "km_core_state_options_update function"
+[km_core_state_options_to_json]: options#km_core_state_options_to_json "km_core_state_options_to_json function"
+[km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct"
+[km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct"
+[km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct"
+[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function"
+[km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function"
+[km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function"
+[km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function"
+[km_core_keyboard_key_list_dispose]: keyboards#km_core_keyboard_key_list_dispose "km_core_keyboard_key_list_dispose function"
+[km_core_keyboard_imx_list_dispose]: keyboards#km_core_keyboard_imx_list_dispose "km_core_keyboard_imx_list_dispose function"
+[km_core_state_imx_register_callback]: keyboards#km_core_state_imx_register_callback "km_core_state_imx_register_callback function"
+[km_core_state_imx_deregister_callback]: keyboards#km_core_state_imx_deregister_callback "km_core_state_imx_deregister_callback function"
+[km_core_state_create]: keyboards#km_core_state_create "km_core_state_create function"
+[km_core_state_clone]: keyboards#km_core_state_clone "km_core_state_clone function"
+[km_core_state_dispose]: keyboards#km_core_state_dispose "km_core_state_dispose function"
+[km_core_debug_context_type]: keyboards#km_core_debug_context_type "km_core_debug_context_type enum"
+[km_core_state_context_debug]: keyboards#km_core_state_context_debug "km_core_state_context_debug function"
+[km_core_cp_dispose]: keyboards#km_core_cp_dispose "km_core_cp_dispose function"
+[km_core_state_to_json]: keyboards#km_core_state_to_json "km_core_state_to_json function"
+[km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum"
+[km_core_process_event]: processor#km_core_process_event "km_core_process_event function"
+[km_core_event]: processor#km_core_event "km_core_event function"
+[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum"
\ No newline at end of file
diff --git a/core/docs/api/index.md b/core/docs/api/index.md
new file mode 100644
index 00000000000..94bb517654c
--- /dev/null
+++ b/core/docs/api/index.md
@@ -0,0 +1,125 @@
+---
+title: Keyman Core API
+---
+
+## Overview
+Keyman Core is the component of Keyman Engine that implements keyboarding rules.
+It is platform independent and allows support for different keyboard formats to
+be implemented within Keyman Engine. Eventually, Keyman Core will be used in
+Keyman on all platforms. As of writing, Keyman for Linux, Keyman for Windows,
+Keyman for macOS, and Keyman Developer use Keyman Core.
+
+This is an internal API intended for use only within Keyman Engine.
+
+## Reference
+
+* [Background](background)
+* [Changes from earlier versions](changes)
+* [Keyboards](keyboards)
+* [Options](options)
+* [Processor](processor)
+* [State and Actions](state)
+* [JSON introspection Schema](json-schema)
+* [Building Keyman Core](building)
+
+## Requirements
+1. Cross platform.
+2. Cross language.
+3. Facilitate stateless operation of the Engine.
+4. Keyboard format agnostic -- support both KMN and future LDML based keyboards.
+5. Support querying Engine attributes.
+6. Support querying Keyboard attributes.
+7. Idempotent
+
+
+## Design decisions in support of requirements
+- Use C or C99 types and calling convention for the interface, it has the
+ broadest language FFI support. [1,2]
+- Have client (Platform layer) code load keyboards, manage & pass state. [3,4,7]
+- Provide query calls to return static attributes data for keyboards and
+ engine [5,6]
+- Provide get/set calls for client accessible keyboard state information [3,4]
+
+
+## Glossary
+- __Platform layer:__
+The code that consumes the Keyman Core API, and provides the
+operating system-specific handling of keystroke events and integration with
+applications.
+- __Client Application:__
+The application that has the focus and receives text events from the Platform
+layer.
+- __Context:__ Text preceding the insertion point
+- __Marker:__ Positional state that can be placed in the Context.
+- __Keyboard:__ A set of rules for execution by an Engine
+- __Option:__ A variable in a dynamic or static key value store.
+- __Processor:__
+The component that implements this API and can parse and execute a particular
+keyboard.
+- __State:__ An object that holds internal state of the Processor for a given
+insertion point
+- __Action:__
+A directive output by the processor detailing how the Platform layer should
+transform the Client Application's text buffer. There may be several items
+produced by a single keyboard event.
+- __Keyboard Event:__
+A virtual key event and modifier map received from the Platform layer to be
+processed with the state object for this Client application.
+- __Virtual Key:__
+A code based on the US English layout, with values matching the Windows
+virtual key codes. See `keyman_core_api_vkeys.h` for definitions.
+- __Modifier Key:__
+The set of Control, Shift, Alt, Caps Lock keys. On some platforms these may
+have other names (e.g. Alt is called Option on macOS); other platform-specific
+modifiers such as Windows key are excluded from this set. Some modifiers are
+transient, such as Control, and others have long-lasting state, such as
+Caps Lock.
+
+- __See more in__ [Keyman Glossary](https://github.com/keymanapp/keyman/wiki/Keyman-glossary)
+
+[km_core_cp]: background#km_core_cp "km_core_cp type"
+[km_core_usv]: background#km_core_usv "km_core_usv type"
+[km_core_virtual_key]: background#km_core_virtual_key "km_core_virtual_key type"
+[km_core_status]: background#km_core_status "km_core_status type"
+[km_core_modifier_state]: background#km_core_modifier_state "km_core_modifier_state type"
+[km_core_keyboard]: background#km_core_keyboard "km_core_keyboard struct"
+[km_core_state]: background#km_core_state "km_core_state struct"
+[km_core_options]: background#km_core_options "km_core_options struct"
+[km_core_status_codes]: background#km_core_status_codes "km_core_status_codes enum"
+[km_core_attr]: background#km_core_attr "km_core_attr struct"
+[km_core_tech_value]: background#km_core_tech_value "km_core_tech_value enum"
+[km_core_get_engine_attrs]: background#km_core_get_engine_attrs "km_core_get_engine_attrs function"
+[km_core_bool]: background#km_core_bool "km_core_bool enum"
+[km_core_caps_state]: state#km_core_caps_state "km_core_caps_state enum"
+[km_core_actions]: state#km_core_actions "km_core_actions struct"
+[km_core_state_get_actions]: state#km_core_state_get_actions "km_core_state_get_actions function"
+[km_core_context_status]: state#km_core_context_status "km_core_context_status enum"
+[km_core_state_context_set_if_needed]: state#km_core_state_context_set_if_needed "km_core_state_context_set_if_needed function"
+[km_core_state_context_clear]: state#km_core_state_context_clear "km_core_state_context_clear function"
+[km_core_option_scope]: options#km_core_option_scope "km_core_option_scope enum"
+[km_core_option_item]: options#km_core_option_item "km_core_option_item struct"
+[km_core_options_list_size]: options#km_core_options_list_size "km_core_options_list_size function"
+[km_core_state_options_update]: options#km_core_state_options_update "km_core_state_options_update function"
+[km_core_state_options_to_json]: options#km_core_state_options_to_json "km_core_state_options_to_json function"
+[km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct"
+[km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct"
+[km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct"
+[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function"
+[km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function"
+[km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function"
+[km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function"
+[km_core_keyboard_key_list_dispose]: keyboards#km_core_keyboard_key_list_dispose "km_core_keyboard_key_list_dispose function"
+[km_core_keyboard_imx_list_dispose]: keyboards#km_core_keyboard_imx_list_dispose "km_core_keyboard_imx_list_dispose function"
+[km_core_state_imx_register_callback]: keyboards#km_core_state_imx_register_callback "km_core_state_imx_register_callback function"
+[km_core_state_imx_deregister_callback]: keyboards#km_core_state_imx_deregister_callback "km_core_state_imx_deregister_callback function"
+[km_core_state_create]: keyboards#km_core_state_create "km_core_state_create function"
+[km_core_state_clone]: keyboards#km_core_state_clone "km_core_state_clone function"
+[km_core_state_dispose]: keyboards#km_core_state_dispose "km_core_state_dispose function"
+[km_core_debug_context_type]: keyboards#km_core_debug_context_type "km_core_debug_context_type enum"
+[km_core_state_context_debug]: keyboards#km_core_state_context_debug "km_core_state_context_debug function"
+[km_core_cp_dispose]: keyboards#km_core_cp_dispose "km_core_cp_dispose function"
+[km_core_state_to_json]: keyboards#km_core_state_to_json "km_core_state_to_json function"
+[km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum"
+[km_core_process_event]: processor#km_core_process_event "km_core_process_event function"
+[km_core_event]: processor#km_core_event "km_core_event function"
+[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum"
\ No newline at end of file
diff --git a/core/docs/api/json-schema.md b/core/docs/api/json-schema.md
new file mode 100644
index 00000000000..756d5c4969d
--- /dev/null
+++ b/core/docs/api/json-schema.md
@@ -0,0 +1,77 @@
+---
+title: JSON Introspection Schema
+---
+
+The [`km_core_state_to_json()`](state#km_core_state_to_json) call
+generates a JSON document describing the internal state of the keyboard
+processor, this is the schema describing that document.
+
+**WARNING**: The structure and format of the JSON document is independently
+versioned is not considered part of C API. It is intended solely for use in
+diagnostics or by development and debugging tools which may need to be aware of
+keyboard processor engine implementation details.
+
+```json
+{"$schema": "http://json-schema.org/draft-06/schema#",
+ "$id": "@replace_me_with_introspection_schema_uri@",
+ "$comment": "© 2018 SIL International. MIT Licensed",
+ "title": "Keyboard Processor State object introspection",
+ "description": "Internal data from a Keyboard Processor State object, sufficient for debugging and diagnostics",
+ "type": "object",
+
+ "definitions": {
+ "options": {
+ "type": "object",
+ "properties": {
+ "scope": { "enum": ["unknown", "environment", "keyboard"] },
+ "options": {
+ "type": "object",
+ "default": {}
+ }
+ },
+ "required": ["scope", "options"]
+ },
+ "rule": {
+ "type": "object"
+ },
+ "keyboard" : {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "version": { "type": "string" },
+ "folder": { "type": "string"},
+ "options": { "$ref": "#definitions/options" },
+ "rules": {
+ "type":"array",
+ "items": { "$ref": "#/definitions/rule" }
+ }
+ },
+ "required": ["id", "version", "folder", "options", "rules"]
+ },
+ "context": {
+ "type":"array",
+ "items": {"$ref": "#/definitions/context_item"},
+ "default": []
+ },
+ "context_item": {
+ "oneOf": [
+ {"type": "string", "minLength": 1, "maxLength": 4},
+ {"type": "number"}
+ ]
+ }
+ },
+ "properties": {
+ "$schema": { "const": "keyman/core/docs/introspection.schema" },
+ "keyboard": { "$ref": "#/definitions/keyboard" },
+ "options": {
+ "type": "object",
+ "properties": {
+ "enviroment": { "$ref": "#/definitions/options" },
+ "dynamic": { "$ref": "#/definitions/options" }
+ },
+ "required": [ "enviroment","dynamic"]
+ },
+ "context": { "$ref": "#/definitions/context" }
+ }
+}
+```
\ No newline at end of file
diff --git a/core/docs/api/keyboards.md b/core/docs/api/keyboards.md
new file mode 100644
index 00000000000..3439e20722b
--- /dev/null
+++ b/core/docs/api/keyboards.md
@@ -0,0 +1,665 @@
+---
+title: Keyboards - Keyman Core API
+---
+
+A keyboard is a set of rules and transforms in a Processor specific format for
+transforming key events into action items. The keyboard is parsed and loaded by
+the processsor and made available in an immutable fashion for use with any number
+of state objects.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_attrs struct {#km_core_keyboard_attrs}
+
+## Description
+
+Provides read-only information about a keyboard.
+
+## Specification
+```c
+typedef struct {
+ km_core_cp const * version_string;
+ km_core_cp const * id;
+ km_core_path_name folder_path;
+ km_core_option_item const * default_options;
+} km_core_keyboard_attrs;
+
+```
+## Members
+
+`version_string`
+: Processor specific version string.
+
+`id`
+: Keyman keyboard ID string.
+
+`folder_path`
+: Path to the unpacked folder containing the keyboard and associated resources.
+
+`default_options`
+: Set of default values for any options included in the keyboard.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_key struct {#km_core_keyboard_key}
+
+## Description
+
+Describes a single key and modifier combination that a keyboard handles, for
+use by the Platform layer. This is used when the Platform layer must know in
+advance which keys are used by a given keyboard.
+
+## Specification
+
+```c
+typedef struct {
+ km_core_virtual_key key;
+ uint32_t modifier_flag;
+} km_core_keyboard_key;
+
+#define KM_CORE_KEYBOARD_KEY_LIST_END { 0, 0 }
+
+```
+
+## Members
+
+`key`
+: A virtual key.
+
+`modifier_flag`
+: A [km_core_modifier_state] bitmask.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_imx struct {#km_core_keyboard_imx}
+
+## Description
+
+Describes a single Input Method eXtension library and entry point.
+
+## Specification
+
+```c
+typedef struct {
+ km_core_cp const * library_name;
+ km_core_cp const * function_name;
+ uint32_t imx_id;
+} km_core_keyboard_imx;
+
+#define KM_CORE_KEYBOARD_IMX_END { 0, 0, 0 }
+
+```
+## Members
+
+`library_name`
+: The fully-qualified path and filename of the dynamically loaded library file.
+
+`function_name`
+: The entry point for the IMX.
+
+`imx_id`
+: unique identifier used to call this function
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_load_from_blob() {#km_core_keyboard_load_from_blob}
+
+## Description
+
+Parse and load a keyboard from the supplied blob and return a pointer to the
+loaded keyboard in the out parameter.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_keyboard_load_from_blob(const km_core_path_name kb_name,
+ const void* blob,
+ const size_t blob_size,
+ km_core_keyboard** keyboard);
+
+```
+
+## Parameters
+
+`kb_name`
+: a string with the name of the keyboard.
+
+`blob`
+: a byte array containing the content of a KMX/KMX+ file.
+
+`blob_size`
+: a size_t variable with the size of the blob in bytes.
+
+`keyboard`
+: A pointer to result variable: A pointer to the opaque keyboard
+ object returned by the Processor. This memory must be freed with a
+ call to [km_core_keyboard_dispose].
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event an internal memory allocation fails.
+
+`KM_CORE_STATUS_IO_ERROR`
+: In the event the keyboard file is unparseable for any reason
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: In the event the file doesn't exist or is inaccesible or `keyboard` is null.
+
+`KM_CORE_STATUS_OS_ERROR`
+: Bit 31 (high bit) set, bits 0-30 are an OS-specific error code.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_dispose() {#km_core_keyboard_dispose}
+
+## Description
+
+Free the allocated memory belonging to an opaque keyboard object previously
+returned by [km_core_keyboard_load_from_blob].
+
+## Specification
+
+```c
+KMN_API
+void
+km_core_keyboard_dispose(km_core_keyboard *keyboard);
+
+```
+## Parameters
+`keyboard`
+: A pointer to the opaque keyboard object to be disposed of.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_get_attrs() {#km_core_keyboard_get_attrs}
+
+## Description
+
+Returns the const internal attributes of the keyboard. This structure is valid
+for the lifetime of the opaque keyboard object. Do not modify the returned data.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_keyboard_get_attrs(km_core_keyboard const *keyboard,
+ km_core_keyboard_attrs const **out);
+
+```
+## Parameters
+
+`keyboard`
+: A pointer to the opaque keyboard object to be queried.
+
+`out`
+: A pointer to the result: A pointer to a [km_core_keyboard_attrs] structure.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: If non-optional parameters are null.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_get_key_list() {#km_core_keyboard_get_key_list}
+
+## Description
+
+Returns the unordered full set of modifier+virtual keys that are handled by the
+keyboard. The matching dispose call needs to be called to free the memory.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_keyboard_get_key_list(km_core_keyboard const *keyboard,
+ km_core_keyboard_key **out);
+
+```
+## Parameters
+
+`keyboard`
+: A pointer to the opaque keyboard object to be queried.
+
+`out`
+: A pointer to an array of [km_core_keyboard_key] structures,
+ terminated by `KM_CORE_KEYBOARD_KEY_LIST_END`.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: If non-optional parameters are null.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_key_list_dispose() {#km_core_keyboard_key_list_dispose}
+
+## Description
+
+Free the allocated memory belonging to a keyboard key list previously
+returned by [km_core_keyboard_get_key_list].
+
+## Specification
+
+```c
+KMN_API
+void km_core_keyboard_key_list_dispose(km_core_keyboard_key *key_list);
+
+```
+## Parameters
+
+`key_list`
+: A pointer to the keyboard key list to be disposed of.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_get_imx_list
+
+## Description
+
+Returns the list of IMX libraries and function names that are referenced by
+the keyboard. The matching dispose call needs to be called to free the memory.
+
+## Specification
+
+```c
+KMN_API
+km_core_status km_core_keyboard_get_imx_list(km_core_keyboard const *keyboard, km_core_keyboard_imx **imx_list);
+
+```
+## Parameters
+
+`keyboard`
+: A pointer to the keyboard
+
+`imx_list`
+: A pointer to a variable that will contain a pointer to the IMX list.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: If non-optional parameters are null.
+
+-------------------------------------------------------------------------------
+
+# km_core_keyboard_imx_list_dispose() {#km_core_keyboard_imx_list_dispose}
+
+## Description
+
+Disposes of the IMX list.
+
+## Specification
+
+```c
+KMN_API
+void km_core_keyboard_imx_list_dispose(km_core_keyboard_imx *imx_list);
+
+```
+## Parameters
+
+`imx_list`
+: A pointer to the IMX list.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_imx_register_callback() {#km_core_state_imx_register_callback}
+
+## Description
+
+Register the IMX callback endpoint for the client.
+
+## Specification
+
+```c
+KMN_API
+void km_core_state_imx_register_callback(km_core_state *state, km_core_keyboard_imx_platform imx_callback, void *callback_object);
+
+```
+## Parameters
+
+`state`
+: A pointer to the opaque state object
+
+`imx_callback`
+: pointer to a function that implements the IMX callback
+
+`callback_object`
+: TODO
+
+-------------------------------------------------------------------------------
+
+# km_core_state_imx_deregister_callback() {#km_core_state_imx_deregister_callback}
+
+## Description
+
+De-register IMX callback endpoint for the client.
+
+## Specification
+
+```c
+KMN_API
+void km_core_state_imx_deregister_callback(km_core_state *state);
+
+```
+## Parameters
+
+`state`
+: A pointer to the opaque state object
+
+-------------------------------------------------------------------------------
+
+# km_core_state_create() {#km_core_state_create}
+
+## Description
+
+Create a keyboard processor state object, maintaining state for the keyboard in
+the environment passed.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_state_create(km_core_keyboard *keyboard,
+ km_core_option_item const *env,
+ km_core_state **out);
+
+```
+## Parameters
+
+`keyboard`
+: A pointer to the opaque keyboard object this object will hold state for.
+
+`env`
+: The array of [km_core_option_item] key/value pairs used to initialise the
+ environment, terminated by `KM_CORE_OPTIONS_END`.
+
+`out`
+: A pointer to result variable: A pointer to the opaque state object
+ returned by the Processor, initalised to maintain state for `keyboard`.
+ This must be disposed of by a call to [km_core_state_dispose].
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event memory is unavailable to allocate a state object.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: In the event the `keyboard` or `out` pointer are null.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_clone() {#km_core_state_clone}
+
+## Description
+
+Clone an existing opaque state object.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_state_clone(km_core_state const *state,
+ km_core_state **out);
+
+```
+## Parameters
+
+`state`
+: A pointer to the opaque statea object to be cloned.
+
+`out`
+: A pointer to result variable: A pointer to the opaque state object
+ returned by the Processor, cloned from the existing object `state`. This
+ must be disposed of by a call to [km_core_state_dispose].
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event memory is unavailable to allocate a state object.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: In the event the `state` or `out` pointer are null.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_dispose() {#km_core_state_dispose}
+
+## Description
+
+Free the allocated resources belonging to a [km_core_state] object previously
+returned by [km_core_state_create] or [km_core_state_clone]. After this all
+pointers previously returned by any [km_core_state] family of calls will become
+invalid.
+
+## Specification
+
+```c
+KMN_API
+void
+km_core_state_dispose(km_core_state *state);
+
+```
+## Parameters
+
+`state`
+: A pointer to the opaque state object to be disposed.
+
+-------------------------------------------------------------------------------
+
+# km_core_debug_context_type enum {#km_core_debug_context_type}
+
+As of version 17, the cached context is an internal property of the
+state, not exposed to the consumer of the API -- apart from the
+Keyman Developer Keyboard Debugger. However, for other debug
+purposes, it is helpful to be able to examine the cached context, so
+a debug-formatted version of the context is made available with
+[km_core_state_context_debug]. This is not intended to be parsed for
+reading the context for other purposes, and the format may change.
+
+The three context types are: cached, intermediate, and app.
+
+## Specification
+
+```c
+typedef enum {
+ KM_CORE_DEBUG_CONTEXT_CACHED = 0,
+ KM_CORE_DEBUG_CONTEXT_INTERMEDIATE = 1,
+ KM_CORE_DEBUG_CONTEXT_APP = 2
+} km_core_debug_context_type;
+
+```
+## Values
+
+`KM_CORE_DEBUG_CONTEXT_CACHED`
+: the internal context used by Core, which may be normalized
+ and may contain markers. This is set via
+ [km_core_state_context_set_if_needed], and will be modified
+ during keystroke event processing.
+
+`KM_CORE_DEBUG_CONTEXT_INTERMEDIATE`
+: internal context used by IMX, only valid during
+ keystroke event processing.
+
+`KM_CORE_DEBUG_CONTEXT_APP`
+: an exact copy of the current context passed in to
+ [km_core_state_context_set_if_needed], which is used to verify
+ the precise text manipulations required when emitted changes.
+ This input context is in "NFU" -- normalization form unknown,
+ and may be mixed normalization so may require fixups when
+ it is manipulated by keyboard processors that support
+ normalization, such as the LDML keyboard processor.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_context_debug() {#km_core_state_context_debug}
+
+## Description
+
+Returns a debug formatted string of the context from the state.
+
+## Specification
+
+```c
+KMN_API
+km_core_cp *
+km_core_state_context_debug(km_core_state *state, km_core_debug_context_type context_type);
+
+```
+## Parameters
+
+`state`
+: A pointer to the opaque state object to be queried.
+
+`context_type`
+: The type of context to retrieve from the state.
+
+## Returns
+
+A pointer to a [km_core_cp] UTF-16 string. Must be disposed of by a call
+to [km_core_cp_dispose].
+
+-------------------------------------------------------------------------------
+
+# km_core_cp_dispose() {#km_core_cp_dispose}
+
+## Description
+
+Free the allocated memory belonging to a [km_core_cp] array previously
+returned by [km_core_state_context_debug]. May be `nullptr`.
+
+## Specification
+
+```c
+KMN_API
+void
+km_core_cp_dispose(km_core_cp *cp);
+
+```
+## Parameters
+
+`cp`
+: A pointer to the start of the [km_core_cp] array to be disposed of.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_to_json() {#km_core_state_to_json}
+
+## Description
+
+Export the internal state of a [km_core_state] object to a JSON format document
+and place it in the supplied buffer, reporting how much space was used. If null
+is passed as the buffer the number of bytes required is returned. If there is
+insufficent space to hold the document, the contents of the buffer is undefined.
+The encoding of the returned data is UTF-8.
+
+__WARNING__: The structure and format of the JSON document while independently
+versioned is not part of this API and is intended solely for use in diagnostics
+or by development and debugging tools which are aware of processor
+implementation details.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_state_to_json(km_core_state const *state,
+ char *buf,
+ size_t *space);
+
+```
+## Parameters
+
+`state`
+: An pointer to an opaque state object.
+
+`buf`
+: A pointer to the buffer to place the C string containing the JSON
+ document into. May be null.
+
+`space`
+: A pointer to a size_t variable. This variable must contain the
+ number of bytes available in the buffer pointed to by `buf`, unless `buf` is
+ null. On return it will hold how many bytes were used.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event an internal memory allocation fails.
+
+-------------------------------------------------------------------------------
+
+
+[km_core_cp]: background#km_core_cp "km_core_cp type"
+[km_core_usv]: background#km_core_usv "km_core_usv type"
+[km_core_virtual_key]: background#km_core_virtual_key "km_core_virtual_key type"
+[km_core_status]: background#km_core_status "km_core_status type"
+[km_core_modifier_state]: background#km_core_modifier_state "km_core_modifier_state type"
+[km_core_keyboard]: background#km_core_keyboard "km_core_keyboard struct"
+[km_core_state]: background#km_core_state "km_core_state struct"
+[km_core_options]: background#km_core_options "km_core_options struct"
+[km_core_status_codes]: background#km_core_status_codes "km_core_status_codes enum"
+[km_core_attr]: background#km_core_attr "km_core_attr struct"
+[km_core_tech_value]: background#km_core_tech_value "km_core_tech_value enum"
+[km_core_get_engine_attrs]: background#km_core_get_engine_attrs "km_core_get_engine_attrs function"
+[km_core_bool]: background#km_core_bool "km_core_bool enum"
+[km_core_caps_state]: state#km_core_caps_state "km_core_caps_state enum"
+[km_core_actions]: state#km_core_actions "km_core_actions struct"
+[km_core_state_get_actions]: state#km_core_state_get_actions "km_core_state_get_actions function"
+[km_core_context_status]: state#km_core_context_status "km_core_context_status enum"
+[km_core_state_context_set_if_needed]: state#km_core_state_context_set_if_needed "km_core_state_context_set_if_needed function"
+[km_core_state_context_clear]: state#km_core_state_context_clear "km_core_state_context_clear function"
+[km_core_option_scope]: options#km_core_option_scope "km_core_option_scope enum"
+[km_core_option_item]: options#km_core_option_item "km_core_option_item struct"
+[km_core_options_list_size]: options#km_core_options_list_size "km_core_options_list_size function"
+[km_core_state_options_update]: options#km_core_state_options_update "km_core_state_options_update function"
+[km_core_state_options_to_json]: options#km_core_state_options_to_json "km_core_state_options_to_json function"
+[km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct"
+[km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct"
+[km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct"
+[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function"
+[km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function"
+[km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function"
+[km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function"
+[km_core_keyboard_key_list_dispose]: keyboards#km_core_keyboard_key_list_dispose "km_core_keyboard_key_list_dispose function"
+[km_core_keyboard_imx_list_dispose]: keyboards#km_core_keyboard_imx_list_dispose "km_core_keyboard_imx_list_dispose function"
+[km_core_state_imx_register_callback]: keyboards#km_core_state_imx_register_callback "km_core_state_imx_register_callback function"
+[km_core_state_imx_deregister_callback]: keyboards#km_core_state_imx_deregister_callback "km_core_state_imx_deregister_callback function"
+[km_core_state_create]: keyboards#km_core_state_create "km_core_state_create function"
+[km_core_state_clone]: keyboards#km_core_state_clone "km_core_state_clone function"
+[km_core_state_dispose]: keyboards#km_core_state_dispose "km_core_state_dispose function"
+[km_core_debug_context_type]: keyboards#km_core_debug_context_type "km_core_debug_context_type enum"
+[km_core_state_context_debug]: keyboards#km_core_state_context_debug "km_core_state_context_debug function"
+[km_core_cp_dispose]: keyboards#km_core_cp_dispose "km_core_cp_dispose function"
+[km_core_state_to_json]: keyboards#km_core_state_to_json "km_core_state_to_json function"
+[km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum"
+[km_core_process_event]: processor#km_core_process_event "km_core_process_event function"
+[km_core_event]: processor#km_core_event "km_core_event function"
+[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum"
\ No newline at end of file
diff --git a/core/docs/api/options.md b/core/docs/api/options.md
new file mode 100644
index 00000000000..41d5fe28c9a
--- /dev/null
+++ b/core/docs/api/options.md
@@ -0,0 +1,284 @@
+---
+title: Options - Keyman Core API
+---
+
+A state’s default options are set from the keyboard at creation time and the
+environment. The Platform layer is then is expected to apply any persisted
+options it is maintaining. Options are passed into and out of API functions as
+simple C arrays of [km_core_option_item] terminated with a `KM_CORE_OPTIONS_END`
+sentinel value. A state's options are exposed and manipulatable via the
+[km_core_options] API. All option values are of type C string.
+
+During processing when the Platform layer finds a PERSIST action type it should
+store the updated option in the appropriate place, based on its scope.
+For RESET the processor will apply the pristine value from the original scope,
+the Platform layer should update that only if it manages a previously persisted
+value.
+
+-------------------------------------------------------------------------------
+
+# km_core_option_scope enum {#km_core_option_scope}
+
+## Description
+
+## Specification
+
+```c
+enum km_core_option_scope {
+ KM_CORE_OPT_UNKNOWN = 0,
+ KM_CORE_OPT_KEYBOARD = 1,
+ KM_CORE_OPT_ENVIRONMENT = 2,
+ KM_CORE_OPT_MAX_SCOPES
+};
+
+```
+## Values
+
+`KM_CORE_OPT_UNKNOWN`
+: An unknown option type. Reserved.
+
+`KM_CORE_OPT_KEYBOARD`
+: An option that is defined for the currently active keyboard;
+ not all processors support this type of option. These options
+ are specific to the active keyboard.
+
+`KM_CORE_OPT_ENVIRONMENT`
+: Properties of the current environment, often but not necessarily
+ always read-only.
+
+-------------------------------------------------------------------------------
+
+# km_core_option_item struct {#km_core_option_item}
+
+## Description
+
+Defines a single option to be passed into the Keyman Core from the
+Platform layer.
+
+## Specification
+```c
+struct km_core_option_item {
+ km_core_cp const * key;
+ km_core_cp const * value;
+ uint8_t scope;
+};
+
+#define KM_CORE_OPTIONS_END { 0, 0, 0 }
+```
+## Members
+
+`key`
+: Null-terminated string key for the option
+
+`value`
+: Null-terminated string value for the option
+
+`scope`
+: Scope which an option belongs to, from [km_core_option_scope].
+
+-------------------------------------------------------------------------------
+
+# km_core_options_list_size() {#km_core_options_list_size}
+
+## Description
+Return the length of a terminated [km_core_option_item] array (options
+list).
+
+## Specification
+```c
+KMN_API
+size_t
+km_core_options_list_size(km_core_option_item const *opts);
+
+```
+## Parameters
+
+`opts`
+: A pointer to a `KM_CORE_OPTIONS_END` terminated array of
+ [km_core_option_item] values.
+
+## Returns
+
+The number of items in the list, not including terminating item,
+or 0 if `opts` is null.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_option_lookup
+
+## Description
+
+Lookup an option based on its key, in an options list.
+
+## Specification
+```c
+KMN_API
+km_core_status
+km_core_state_option_lookup(km_core_state const *state,
+ uint8_t scope,
+ km_core_cp const *key,
+ km_core_cp const **value);
+
+```
+## Parameters
+
+`state`
+: An opaque pointer to a state object.
+
+`scope`
+: Which key-value store to interrogate.
+
+`key`
+: A UTF-16 string that matches the key in the target [km_core_option_item].
+
+`value`
+: A pointer to the result variable: A pointer to a UTF-16 string value owned
+ by the state or keyboard object at the time of the call. This pointer is
+ only valid *until* the next call to any function on this API and should be
+ used immediately.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: If non-optional parameters are null, or if the scope is invalid.
+
+`KM_CORE_STATUS_KEY_ERROR`
+: The key cannot be found.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_options_update() {#km_core_state_options_update}
+
+## Description
+
+Adds or updates one or more options from a list of [km_core_option_item]s.
+
+## Specification
+``` */
+KMN_API
+km_core_status
+km_core_state_options_update(km_core_state *state,
+ km_core_option_item const *new_opts);
+
+```
+## Parameters
+`state`
+: An opaque pointer to a state object.
+
+`new_opts`
+: An array of [km_core_option_item] objects to update or add. Must be
+ terminated with `KM_CORE_OPTIONS_END`.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: If non-optional parameters are null.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event an internal memory allocation fails.
+
+`KM_CORE_STATUS_KEY_ERROR`
+: The key cannot be found.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_options_to_json() {#km_core_state_options_to_json}
+
+## Description
+
+Export the contents of a [km_core_options] array to a JSON formatted document and
+place it in the supplied buffer, reporting how much space was used. If null is
+passed as the buffer the number of bytes required is returned in `space`. If
+there is insufficent space to hold the document the contents of the buffer is
+undefined. The returned buffer uses UTF-8 encoding.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_state_options_to_json(km_core_state const *state,
+ char *buf,
+ size_t *space);
+
+```
+## Parameters
+
+`state`
+: An opaque pointer to a state object.
+
+`buf`
+: A pointer to the buffer to place the C string containing the JSON
+ document into, can be null.
+
+`space`
+: A pointer to a size_t variable. This variable must contain the
+ number of bytes available in the buffer pointed to by `buf`, unless `buf` is
+ null. On return it will hold how many bytes were used.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: If non-optional parameters are null.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event an internal memory allocation fails.
+
+-------------------------------------------------------------------------------
+
+
+[km_core_cp]: background#km_core_cp "km_core_cp type"
+[km_core_usv]: background#km_core_usv "km_core_usv type"
+[km_core_virtual_key]: background#km_core_virtual_key "km_core_virtual_key type"
+[km_core_status]: background#km_core_status "km_core_status type"
+[km_core_modifier_state]: background#km_core_modifier_state "km_core_modifier_state type"
+[km_core_keyboard]: background#km_core_keyboard "km_core_keyboard struct"
+[km_core_state]: background#km_core_state "km_core_state struct"
+[km_core_options]: background#km_core_options "km_core_options struct"
+[km_core_status_codes]: background#km_core_status_codes "km_core_status_codes enum"
+[km_core_attr]: background#km_core_attr "km_core_attr struct"
+[km_core_tech_value]: background#km_core_tech_value "km_core_tech_value enum"
+[km_core_get_engine_attrs]: background#km_core_get_engine_attrs "km_core_get_engine_attrs function"
+[km_core_bool]: background#km_core_bool "km_core_bool enum"
+[km_core_caps_state]: state#km_core_caps_state "km_core_caps_state enum"
+[km_core_actions]: state#km_core_actions "km_core_actions struct"
+[km_core_state_get_actions]: state#km_core_state_get_actions "km_core_state_get_actions function"
+[km_core_context_status]: state#km_core_context_status "km_core_context_status enum"
+[km_core_state_context_set_if_needed]: state#km_core_state_context_set_if_needed "km_core_state_context_set_if_needed function"
+[km_core_state_context_clear]: state#km_core_state_context_clear "km_core_state_context_clear function"
+[km_core_option_scope]: options#km_core_option_scope "km_core_option_scope enum"
+[km_core_option_item]: options#km_core_option_item "km_core_option_item struct"
+[km_core_options_list_size]: options#km_core_options_list_size "km_core_options_list_size function"
+[km_core_state_options_update]: options#km_core_state_options_update "km_core_state_options_update function"
+[km_core_state_options_to_json]: options#km_core_state_options_to_json "km_core_state_options_to_json function"
+[km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct"
+[km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct"
+[km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct"
+[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function"
+[km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function"
+[km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function"
+[km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function"
+[km_core_keyboard_key_list_dispose]: keyboards#km_core_keyboard_key_list_dispose "km_core_keyboard_key_list_dispose function"
+[km_core_keyboard_imx_list_dispose]: keyboards#km_core_keyboard_imx_list_dispose "km_core_keyboard_imx_list_dispose function"
+[km_core_state_imx_register_callback]: keyboards#km_core_state_imx_register_callback "km_core_state_imx_register_callback function"
+[km_core_state_imx_deregister_callback]: keyboards#km_core_state_imx_deregister_callback "km_core_state_imx_deregister_callback function"
+[km_core_state_create]: keyboards#km_core_state_create "km_core_state_create function"
+[km_core_state_clone]: keyboards#km_core_state_clone "km_core_state_clone function"
+[km_core_state_dispose]: keyboards#km_core_state_dispose "km_core_state_dispose function"
+[km_core_debug_context_type]: keyboards#km_core_debug_context_type "km_core_debug_context_type enum"
+[km_core_state_context_debug]: keyboards#km_core_state_context_debug "km_core_state_context_debug function"
+[km_core_cp_dispose]: keyboards#km_core_cp_dispose "km_core_cp_dispose function"
+[km_core_state_to_json]: keyboards#km_core_state_to_json "km_core_state_to_json function"
+[km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum"
+[km_core_process_event]: processor#km_core_process_event "km_core_process_event function"
+[km_core_event]: processor#km_core_event "km_core_event function"
+[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum"
\ No newline at end of file
diff --git a/core/docs/api/processor.md b/core/docs/api/processor.md
new file mode 100644
index 00000000000..c72d743bb9c
--- /dev/null
+++ b/core/docs/api/processor.md
@@ -0,0 +1,198 @@
+---
+title: Processor - Keyman Core API
+---
+
+# km_core_event_flags enum {#km_core_event_flags}
+
+## Description
+
+Bit flags to be used with the `event_flags` parameter of [km_core_process_event]
+
+## Specification
+
+```c
+enum km_core_event_flags {
+ KM_CORE_EVENT_FLAG_DEFAULT = 0,
+ KM_CORE_EVENT_FLAG_TOUCH = 1,
+};
+
+```
+## Values
+
+`KM_CORE_EVENT_FLAG_DEFAULT`
+: default value: hardware
+
+`KM_CORE_EVENT_FLAG_TOUCH`
+: set if the event is touch, otherwise hardware
+
+-------------------------------------------------------------------------------
+
+# km_core_process_event() {#km_core_process_event}
+
+## Description
+
+Run the keyboard on an opaque state object with the provided virtual key and modifer
+key state. Updates the state object as appropriate and fills out its internal set
+of actions, which can be retrieved with [km_core_state_get_actions].
+
+The state's actions will be cleared at the start of this call; options and context in
+the state may also be modified.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_process_event(km_core_state *state,
+ km_core_virtual_key vk,
+ uint16_t modifier_state,
+ uint8_t is_key_down,
+ uint16_t event_flags);
+
+```
+## Parameters
+
+`state`
+: A pointer to the opaque state object.
+
+`vk`
+: A virtual key to be processed.
+
+`modifier_state`
+: The combinations of modifier keys set at the time key `vk` was pressed, bitmask
+ from the [km_core_modifier_state] enum.
+
+`event_flags`
+: Event level flags, see [km_core_event_flags]
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event memory is unavailable to allocate internal buffers.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: In the event the `state` pointer is null or an invalid virtual key or modifier
+ state is passed.
+
+-------------------------------------------------------------------------------
+
+# km_core_event() {#km_core_event}
+
+## Description
+
+Tell the keyboard processor that an external event has occurred, such as a keyboard
+being activated through the language switching UI.
+
+The keyboard processor may generate actions which should be processed by the
+consumer of the API.
+
+The actions will be cleared at the start of this call; options and context in
+the state may also be modified.
+
+## Specification
+
+```c
+KMN_API
+km_core_status
+km_core_event(
+ km_core_state *state,
+ uint32_t event,
+ void* data
+);
+
+```
+## Parameters
+
+`state`
+: A pointer to the opaque state object.
+
+`event`
+: The event to be processed, from [km_core_event_code] enumeration
+
+`data`
+: Additional event-specific data. Currently unused, must be nullptr.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_NO_MEM`
+: In the event memory is unavailable to allocate internal buffers.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: In the event the `state` pointer is null or an invalid event or data is passed.
+
+-------------------------------------------------------------------------------
+
+# km_core_event_code enum {#km_core_event_code}
+
+## Description
+
+Possible events to be passed into Keyman Core from the Platform layer.
+
+## Specification
+
+```c
+enum km_core_event_code {
+ KM_CORE_EVENT_KEYBOARD_ACTIVATED = 1,
+ //future: KM_CORE_EVENT_KEYBOARD_DEACTIVATED = 2,
+};
+
+```
+## Values
+
+`KM_CORE_EVENT_KEYBOARD_ACTIVATED`
+: A keyboard has been activated by the user. The processor may use this
+ event, for example, to switch caps lock state or provide other UX.
+
+
+[km_core_cp]: background#km_core_cp "km_core_cp type"
+[km_core_usv]: background#km_core_usv "km_core_usv type"
+[km_core_virtual_key]: background#km_core_virtual_key "km_core_virtual_key type"
+[km_core_status]: background#km_core_status "km_core_status type"
+[km_core_modifier_state]: background#km_core_modifier_state "km_core_modifier_state type"
+[km_core_keyboard]: background#km_core_keyboard "km_core_keyboard struct"
+[km_core_state]: background#km_core_state "km_core_state struct"
+[km_core_options]: background#km_core_options "km_core_options struct"
+[km_core_status_codes]: background#km_core_status_codes "km_core_status_codes enum"
+[km_core_attr]: background#km_core_attr "km_core_attr struct"
+[km_core_tech_value]: background#km_core_tech_value "km_core_tech_value enum"
+[km_core_get_engine_attrs]: background#km_core_get_engine_attrs "km_core_get_engine_attrs function"
+[km_core_bool]: background#km_core_bool "km_core_bool enum"
+[km_core_caps_state]: state#km_core_caps_state "km_core_caps_state enum"
+[km_core_actions]: state#km_core_actions "km_core_actions struct"
+[km_core_state_get_actions]: state#km_core_state_get_actions "km_core_state_get_actions function"
+[km_core_context_status]: state#km_core_context_status "km_core_context_status enum"
+[km_core_state_context_set_if_needed]: state#km_core_state_context_set_if_needed "km_core_state_context_set_if_needed function"
+[km_core_state_context_clear]: state#km_core_state_context_clear "km_core_state_context_clear function"
+[km_core_option_scope]: options#km_core_option_scope "km_core_option_scope enum"
+[km_core_option_item]: options#km_core_option_item "km_core_option_item struct"
+[km_core_options_list_size]: options#km_core_options_list_size "km_core_options_list_size function"
+[km_core_state_options_update]: options#km_core_state_options_update "km_core_state_options_update function"
+[km_core_state_options_to_json]: options#km_core_state_options_to_json "km_core_state_options_to_json function"
+[km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct"
+[km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct"
+[km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct"
+[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function"
+[km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function"
+[km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function"
+[km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function"
+[km_core_keyboard_key_list_dispose]: keyboards#km_core_keyboard_key_list_dispose "km_core_keyboard_key_list_dispose function"
+[km_core_keyboard_imx_list_dispose]: keyboards#km_core_keyboard_imx_list_dispose "km_core_keyboard_imx_list_dispose function"
+[km_core_state_imx_register_callback]: keyboards#km_core_state_imx_register_callback "km_core_state_imx_register_callback function"
+[km_core_state_imx_deregister_callback]: keyboards#km_core_state_imx_deregister_callback "km_core_state_imx_deregister_callback function"
+[km_core_state_create]: keyboards#km_core_state_create "km_core_state_create function"
+[km_core_state_clone]: keyboards#km_core_state_clone "km_core_state_clone function"
+[km_core_state_dispose]: keyboards#km_core_state_dispose "km_core_state_dispose function"
+[km_core_debug_context_type]: keyboards#km_core_debug_context_type "km_core_debug_context_type enum"
+[km_core_state_context_debug]: keyboards#km_core_state_context_debug "km_core_state_context_debug function"
+[km_core_cp_dispose]: keyboards#km_core_cp_dispose "km_core_cp_dispose function"
+[km_core_state_to_json]: keyboards#km_core_state_to_json "km_core_state_to_json function"
+[km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum"
+[km_core_process_event]: processor#km_core_process_event "km_core_process_event function"
+[km_core_event]: processor#km_core_event "km_core_event function"
+[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum"
\ No newline at end of file
diff --git a/core/docs/api/state.md b/core/docs/api/state.md
new file mode 100644
index 00000000000..0292d9f8a59
--- /dev/null
+++ b/core/docs/api/state.md
@@ -0,0 +1,285 @@
+---
+title: State and Actions - Keyman Core API
+---
+
+A State object maintains all per keyboard related state including context
+and dynamic options ("option stores" in kmn format).
+
+When a keystroke is processed by Keyman Core, Core provides back a set of actions
+for the Platform layer to emit to the Client application. These actions are
+owned by the state object.
+
+-------------------------------------------------------------------------------
+
+# km_core_caps_state enum {#km_core_caps_state}
+
+## Description
+
+Describes the
+
+## Specification
+```c
+typedef enum { KM_CORE_CAPS_UNCHANGED = -1, KM_CORE_CAPS_OFF = 0, KM_CORE_CAPS_ON = 1 } km_core_caps_state;
+
+```
+## Values
+
+`KM_CORE_CAPS_UNCHANGED`
+: Caps lock state has not changed in this event.
+
+`KM_CORE_CAPS_OFF`
+: As a result of processing this event, the Platform layer should switch off
+ Caps Lock on the hardware keyboard.
+
+`KM_CORE_CAPS_ON`
+: As a result of processing this event, the Platform layer should switch on
+ Caps Lock on the hardware keyboard.
+
+-------------------------------------------------------------------------------
+
+# km_core_actions struct {#km_core_actions}
+
+## Description
+
+This structure provides the results of processing a key event to the Platform layer and
+should be processed by the Platform layer to issue commands to the os text
+services framework to transform the text store in the Client Application, among
+other actions.
+
+This API replaces the Action items APIs, which are now deprecated and will be
+removed in the future.
+
+## Specification
+```c
+typedef struct {
+ unsigned int code_points_to_delete;
+ const km_core_usv* output;
+ km_core_option_item * persist_options;
+ km_core_bool do_alert;
+ km_core_bool emit_keystroke;
+ km_core_caps_state new_caps_lock_state;
+ const km_core_usv* deleted_context;
+} km_core_actions;
+
+```
+## Members
+
+`code_points_to_delete`
+: Number of codepoints (not codeunits!) to delete from app context.
+
+`output`
+: Null-term string of characters to insert into document.
+
+`persist_options`
+: List of options to persist, terminated with `KM_CORE_OPTIONS_END`.
+
+`do_alert`
+: Issue a beep, 0 = no, 1 = yes.
+
+`emit_keystroke`
+: Emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes.
+
+`new_caps_lock_state`
+: -1=unchanged, 0=off, 1=on
+
+`deleted_context`
+: Reference copy of actual UTF32 codepoints deleted from end of context
+ (closest to caret) exactly code_points_to_delete in length (plus null
+ terminator). Used to determine encoding conversion differences when
+ deleting; only set when using [km_core_state_get_actions], otherwise nullptr.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_get_actions() {#km_core_state_get_actions}
+
+## Description
+
+Returns a pointer to an actions object which details all the actions
+that the Platform layer must take after a keystroke. The `code_points_to_delete`
+action must be performed before the `output` action, but the other
+actions may be performed in any order.
+
+## Specification
+```c
+KMN_API
+km_core_actions const *
+km_core_state_get_actions(
+ km_core_state const *state
+);
+
+```
+## Parameters
+
+`state`
+: An opaque pointer to a state object.
+
+## Returns
+
+A pointer to a [km_core_actions] object. This data becomes invalid
+when the state object is destroyed, or after a call to
+[km_core_process_event]. Do not modify the contents of this data.
+
+-------------------------------------------------------------------------------
+
+# km_core_context_status enum {#km_core_context_status}
+
+## Description
+
+Return values for [km_core_state_context_set_if_needed].
+
+## Specification
+
+```c
+typedef enum {
+ KM_CORE_CONTEXT_STATUS_UNCHANGED = 0,
+ KM_CORE_CONTEXT_STATUS_UPDATED = 1,
+ KM_CORE_CONTEXT_STATUS_CLEARED = 2,
+ KM_CORE_CONTEXT_STATUS_ERROR = 3,
+ KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT = 4,
+} km_core_context_status;
+
+```
+
+## Values
+
+`KM_CORE_CONTEXT_STATUS_UNCHANGED`
+: Cached context change was not needed.
+
+`KM_CORE_CONTEXT_STATUS_UPDATED`
+: Cached context was set to application context.
+
+`KM_CORE_CONTEXT_STATUS_CLEARED`
+: Application context was invalid, perhaps had unpaired surrogates,
+ and so cached context was cleared instead.
+
+`KM_CORE_CONTEXT_STATUS_ERROR`
+: Internal error.
+
+`KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT`
+: One or more parameters was null.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_context_set_if_needed() {#km_core_state_context_set_if_needed}
+
+## Description
+
+Sets the internal cached context for the state object, to the passed-in
+application context string, if it differs from the codepoints in the
+cached context. For the purposes of comparison, (1) cached markers are
+ignored, (2) if the cached context is shorter than the application
+context, it is considered identical, but (3) if the cached context is
+longer, then it is considered different.
+
+If a difference is found, then the cached context will be set to the
+application context, and thus any cached markers will be cleared.
+
+[km_core_state_context_set_if_needed] and [km_core_state_context_clear]
+will replace most uses of the existing Core context APIs.
+
+## Specification
+```c
+KMN_API
+km_core_context_status
+km_core_state_context_set_if_needed(
+ km_core_state *state,
+ km_core_cp const *application_context
+);
+
+```
+## Parameters
+
+`state`
+: An opaque pointer to a state object.
+
+`application_context`
+: A pointer to an null-terminated array of utf16 encoded data representing
+ the current context from the application.
+
+## Returns
+
+A value from the [km_core_context_status] enum.
+
+-------------------------------------------------------------------------------
+
+# km_core_state_context_clear() {#km_core_state_context_clear}
+
+## Description
+
+Clears the internal cached context for the state. This is the same as
+`km_core_context_clear(km_core_state_context(&state))`.
+
+[km_core_state_context_set_if_needed] and [km_core_state_context_clear]
+will replace most uses of the existing Core context APIs.
+
+## Specification
+```c
+KMN_API
+km_core_status
+km_core_state_context_clear(
+ km_core_state *state
+);
+
+```
+## Parameters
+
+`state`:
+An opaque pointer to a state object.
+
+## Returns
+
+`KM_CORE_STATUS_OK`
+: On success.
+
+`KM_CORE_STATUS_INVALID_ARGUMENT`
+: If any parameters are null.
+
+-------------------------------------------------------------------------------
+
+
+[km_core_cp]: background#km_core_cp "km_core_cp type"
+[km_core_usv]: background#km_core_usv "km_core_usv type"
+[km_core_virtual_key]: background#km_core_virtual_key "km_core_virtual_key type"
+[km_core_status]: background#km_core_status "km_core_status type"
+[km_core_modifier_state]: background#km_core_modifier_state "km_core_modifier_state type"
+[km_core_keyboard]: background#km_core_keyboard "km_core_keyboard struct"
+[km_core_state]: background#km_core_state "km_core_state struct"
+[km_core_options]: background#km_core_options "km_core_options struct"
+[km_core_status_codes]: background#km_core_status_codes "km_core_status_codes enum"
+[km_core_attr]: background#km_core_attr "km_core_attr struct"
+[km_core_tech_value]: background#km_core_tech_value "km_core_tech_value enum"
+[km_core_get_engine_attrs]: background#km_core_get_engine_attrs "km_core_get_engine_attrs function"
+[km_core_bool]: background#km_core_bool "km_core_bool enum"
+[km_core_caps_state]: state#km_core_caps_state "km_core_caps_state enum"
+[km_core_actions]: state#km_core_actions "km_core_actions struct"
+[km_core_state_get_actions]: state#km_core_state_get_actions "km_core_state_get_actions function"
+[km_core_context_status]: state#km_core_context_status "km_core_context_status enum"
+[km_core_state_context_set_if_needed]: state#km_core_state_context_set_if_needed "km_core_state_context_set_if_needed function"
+[km_core_state_context_clear]: state#km_core_state_context_clear "km_core_state_context_clear function"
+[km_core_option_scope]: options#km_core_option_scope "km_core_option_scope enum"
+[km_core_option_item]: options#km_core_option_item "km_core_option_item struct"
+[km_core_options_list_size]: options#km_core_options_list_size "km_core_options_list_size function"
+[km_core_state_options_update]: options#km_core_state_options_update "km_core_state_options_update function"
+[km_core_state_options_to_json]: options#km_core_state_options_to_json "km_core_state_options_to_json function"
+[km_core_keyboard_attrs]: keyboards#km_core_keyboard_attrs "km_core_keyboard_attrs struct"
+[km_core_keyboard_key]: keyboards#km_core_keyboard_key "km_core_keyboard_key struct"
+[km_core_keyboard_imx]: keyboards#km_core_keyboard_imx "km_core_keyboard_imx struct"
+[km_core_keyboard_load_from_blob]: keyboards#km_core_keyboard_load_from_blob "km_core_keyboard_load_from_blob function"
+[km_core_keyboard_dispose]: keyboards#km_core_keyboard_dispose "km_core_keyboard_dispose function"
+[km_core_keyboard_get_attrs]: keyboards#km_core_keyboard_get_attrs "km_core_keyboard_get_attrs function"
+[km_core_keyboard_get_key_list]: keyboards#km_core_keyboard_get_key_list "km_core_keyboard_get_key_list function"
+[km_core_keyboard_key_list_dispose]: keyboards#km_core_keyboard_key_list_dispose "km_core_keyboard_key_list_dispose function"
+[km_core_keyboard_imx_list_dispose]: keyboards#km_core_keyboard_imx_list_dispose "km_core_keyboard_imx_list_dispose function"
+[km_core_state_imx_register_callback]: keyboards#km_core_state_imx_register_callback "km_core_state_imx_register_callback function"
+[km_core_state_imx_deregister_callback]: keyboards#km_core_state_imx_deregister_callback "km_core_state_imx_deregister_callback function"
+[km_core_state_create]: keyboards#km_core_state_create "km_core_state_create function"
+[km_core_state_clone]: keyboards#km_core_state_clone "km_core_state_clone function"
+[km_core_state_dispose]: keyboards#km_core_state_dispose "km_core_state_dispose function"
+[km_core_debug_context_type]: keyboards#km_core_debug_context_type "km_core_debug_context_type enum"
+[km_core_state_context_debug]: keyboards#km_core_state_context_debug "km_core_state_context_debug function"
+[km_core_cp_dispose]: keyboards#km_core_cp_dispose "km_core_cp_dispose function"
+[km_core_state_to_json]: keyboards#km_core_state_to_json "km_core_state_to_json function"
+[km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum"
+[km_core_process_event]: processor#km_core_process_event "km_core_process_event function"
+[km_core_event]: processor#km_core_event "km_core_event function"
+[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum"
\ No newline at end of file
diff --git a/core/doc/hotdoc.json b/core/docs/internal/hotdoc.json
similarity index 100%
rename from core/doc/hotdoc.json
rename to core/docs/internal/hotdoc.json
diff --git a/core/doc/markdown_files/index.md b/core/docs/internal/markdown_files/index.md
similarity index 100%
rename from core/doc/markdown_files/index.md
rename to core/docs/internal/markdown_files/index.md
diff --git a/core/doc/meson.build b/core/docs/internal/meson.build
similarity index 100%
rename from core/doc/meson.build
rename to core/docs/internal/meson.build
diff --git a/core/doc/sitemap.txt b/core/docs/internal/sitemap.txt
similarity index 100%
rename from core/doc/sitemap.txt
rename to core/docs/internal/sitemap.txt
diff --git a/core/doc/introspection.schema b/core/docs/introspection.schema
similarity index 96%
rename from core/doc/introspection.schema
rename to core/docs/introspection.schema
index 3b7684e0e9d..ffccc59f466 100644
--- a/core/doc/introspection.schema
+++ b/core/docs/introspection.schema
@@ -47,7 +47,7 @@
}
},
"properties": {
- "$schema": { "const": "keyman/core/doc/introspection.schema" },
+ "$schema": { "const": "keyman/core/docs/introspection.schema" },
"keyboard": { "$ref": "#/definitions/keyboard" },
"options": {
"type": "object",
diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h
index 328965e8761..7863689296d 100644
--- a/core/include/keyman/keyman_core_api.h
+++ b/core/include/keyman/keyman_core_api.h
@@ -1007,7 +1007,11 @@ Provides read-only information about a keyboard.
typedef struct {
km_core_cu const * version_string;
km_core_cu const * id;
+
+ // TODO-web-core: Deprecate this field (#12497)
+ // KMN_DEPRECATED
km_core_path_name folder_path;
+
km_core_option_item const * default_options;
} km_core_keyboard_attrs;
@@ -1022,7 +1026,7 @@ typedef struct {
: Keyman keyboard ID string.
`folder_path`
-: Path to the unpacked folder containing the keyboard and associated resources.
+: Path to the unpacked folder containing the keyboard and associated resources (deprecated).
`default_options`
: Set of default values for any options included in the keyboard.
@@ -1092,29 +1096,35 @@ typedef struct {
-------------------------------------------------------------------------------
-# km_core_keyboard_load()
+# km_core_keyboard_load_from_blob()
## Description
-Parse and load keyboard from the supplied path and a pointer to the loaded keyboard
+Parse and load keyboard from the supplied blob and a pointer to the loaded keyboard
into the out paramter.
## Specification
```c */
KMN_API
-km_core_status
-km_core_keyboard_load(km_core_path_name kb_path,
- km_core_keyboard **keyboard);
+km_core_status km_core_keyboard_load_from_blob(const km_core_path_name kb_name,
+ const void* blob,
+ const size_t blob_size,
+ km_core_keyboard** keyboard);
/*
```
## Parameters
-`kb_path`
-: On Windows, a UTF-16 string; on other platforms, a C string:
- contains a valid path to the keyboard file.
+`kb_name`
+: a string with the name of the keyboard.
+
+`blob`
+: a byte array containing the content of a KMX/KMX+ file.
+
+`blob_size`
+: a size_t variable with the size of the blob in bytes.
`keyboard`
: A pointer to result variable: A pointer to the opaque keyboard
@@ -1133,7 +1143,7 @@ km_core_keyboard_load(km_core_path_name kb_path,
: In the event the keyboard file is unparseable for any reason
`KM_CORE_STATUS_INVALID_ARGUMENT`
-: In the event the file doesn't exist or is inaccesible or `keyboard` is null.
+: In the event `keyboard` is null.
`KM_CORE_STATUS_OS_ERROR`
: Bit 31 (high bit) set, bits 0-30 are an OS-specific error code.
@@ -1145,7 +1155,7 @@ km_core_keyboard_load(km_core_path_name kb_path,
## Description
Free the allocated memory belonging to an opaque keyboard object previously
-returned by [km_core_keyboard_load].
+returned by [km_core_keyboard_load_from_blob].
## Specification
diff --git a/core/include/keyman/keyman_core_api_bits.h b/core/include/keyman/keyman_core_api_bits.h
index e00f4698a8c..bd1f519bffe 100644
--- a/core/include/keyman/keyman_core_api_bits.h
+++ b/core/include/keyman/keyman_core_api_bits.h
@@ -23,7 +23,6 @@
#define _kmn_unused(x) UNUSED_ ## x __attribute__((__unused__))
#else
#define _kmn_unused(x) UNUSED_ ## x
-
#endif
#if defined _WIN32 || defined __CYGWIN__
@@ -36,7 +35,7 @@
#undef _kmn_static_flag
#else // How MSVC sepcifies function level attributes adn deprecation
#define _kmn_and
- #define _kmn_tag_fn(a) __declspec(a)
+ #define _kmn_tag_fn(a) __declspec(a)
#define _kmn_deprecated_flag deprecated
#endif
#define _kmn_export_flag dllexport
@@ -48,6 +47,8 @@
#define _KM_CORE_EXT_SEPARATOR ('.')
#endif
+#define KMN_DEPRECATED _kmn_tag_fn(_kmn_deprecated_flag)
+
#if defined KM_CORE_LIBRARY_STATIC
#define KMN_API _kmn_tag_fn(_kmn_static_flag)
#define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_static_flag)
diff --git a/core/meson.build b/core/meson.build
index 4a9ea257ea8..8c6cbfdc61d 100644
--- a/core/meson.build
+++ b/core/meson.build
@@ -40,7 +40,7 @@ if cpp_compiler.get_id() == 'emscripten'
wasm_exported_runtime_methods = '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\',\'stringToNewUTF8\']'
endif
-subdir('doc')
+subdir('docs/internal')
subdir('include')
subdir('src')
subdir('tests')
diff --git a/core/src/action.cpp b/core/src/action.cpp
index 5f591444091..1ff7917575c 100644
--- a/core/src/action.cpp
+++ b/core/src/action.cpp
@@ -61,7 +61,9 @@ bool km::core::action_item_list_to_actions_object(
if(output.empty()) {
actions->code_points_to_delete++;
} else {
+#ifndef NDEBUG
auto last_context_item = output.back();
+#endif
output.pop_back();
assert(last_context_item.type == KM_CORE_CT_CHAR);
assert(last_context_item.character == action_items->backspace.expected_value);
@@ -71,7 +73,9 @@ bool km::core::action_item_list_to_actions_object(
if(output.empty()) {
// deleting a marker has no effect on the application
} else {
+#ifndef NDEBUG
auto last_context_item = output.back();
+#endif
output.pop_back();
assert(last_context_item.type == KM_CORE_CT_MARKER);
assert(last_context_item.marker == action_items->backspace.expected_value);
diff --git a/core/src/keyboard.cpp b/core/src/keyboard.cpp
index 2c3a0c11b2c..0f104f9422a 100644
--- a/core/src/keyboard.cpp
+++ b/core/src/keyboard.cpp
@@ -17,18 +17,16 @@ void keyboard_attributes::render()
// Make attributes point to the stored values above.
id = _keyboard_id.c_str();
version_string = _version_string.c_str();
- folder_path = _folder_path.c_str();
default_options = _default_opts.data();
}
keyboard_attributes::keyboard_attributes(std::u16string const & kbid,
std::u16string const & version,
- path_type const & path,
options_store const &opts)
: _keyboard_id(kbid),
_version_string(version),
- _folder_path(path),
+ _folder_path(""),
_default_opts(opts)
{
// Ensure that the default_options array will be properly terminated.
@@ -40,7 +38,7 @@ keyboard_attributes::keyboard_attributes(std::u16string const & kbid,
keyboard_attributes::keyboard_attributes(keyboard_attributes &&rhs)
: _keyboard_id(std::move(rhs._keyboard_id)),
_version_string(std::move(rhs._version_string)),
- _folder_path(std::move(rhs._folder_path)),
+ _folder_path(""),
_default_opts(std::move(rhs._default_opts))
{
rhs.id = rhs.version_string = nullptr;
@@ -58,7 +56,6 @@ json & km::core::operator << (json & j, km::core::keyboard_attributes const & kb
{
j << json::object
<< "id" << kb.id
- << "folder" << kb._folder_path
<< "version" << kb.version_string
<< "rules" << json::array << json::close;
diff --git a/core/src/keyboard.hpp b/core/src/keyboard.hpp
index 142bf40e860..2ca7118d870 100644
--- a/core/src/keyboard.hpp
+++ b/core/src/keyboard.hpp
@@ -26,6 +26,7 @@ namespace core
{
std::u16string _keyboard_id;
std::u16string _version_string;
+ // unused and deprecated
core::path _folder_path;
std::vector _default_opts;
@@ -33,7 +34,6 @@ namespace core
public:
using options_store = decltype(_default_opts);
- using path_type = decltype(_folder_path);
keyboard_attributes()
: km_core_keyboard_attrs {nullptr, nullptr, nullptr, nullptr} {}
@@ -42,7 +42,6 @@ namespace core
keyboard_attributes(std::u16string const & id,
std::u16string const & version,
- path_type const & path,
options_store const &opts);
keyboard_attributes & operator = (keyboard_attributes const &) = delete;
@@ -52,8 +51,6 @@ namespace core
options_store const & default_opts_store() const noexcept { return _default_opts; }
options_store & default_opts_store() noexcept { return _default_opts; }
-
- path_type const & path() const noexcept { return _folder_path; }
};
json & operator << (json &, km::core::keyboard_attributes const &);
diff --git a/core/src/km_core_keyboard_api.cpp b/core/src/km_core_keyboard_api.cpp
index 964f73ca072..70d37fa63b0 100644
--- a/core/src/km_core_keyboard_api.cpp
+++ b/core/src/km_core_keyboard_api.cpp
@@ -14,55 +14,54 @@
#include "keyman_core.h"
#include "keyboard.hpp"
-#include "processor.hpp"
#include "kmx/kmx_processor.hpp"
#include "ldml/ldml_processor.hpp"
#include "mock/mock_processor.hpp"
+#include "processor.hpp"
+#include "utfcodec.hpp"
using namespace km::core;
namespace
{
- abstract_processor * processor_factory(path const & kb_path) {
- // Some legacy packages may include upper-case file extensions
- // TODO-LDML: move file io out of core and into engine
- if (kb_path.suffix() == ".kmx" || kb_path.suffix() == ".KMX") {
- std::vector buf;
- if(ldml_processor::is_kmxplus_file(kb_path, buf)) {
- abstract_processor * result = new ldml_processor(kb_path, buf);
- return result;
- }
- return new kmx_processor(kb_path);
+ abstract_processor* processor_factory(path const & kb_name, const std::vector & buf) {
+ if (ldml_processor::is_handled(buf)) {
+ return new ldml_processor(kb_name, buf);
}
- else if (kb_path.suffix() == ".mock") {
- return new mock_processor(kb_path);
+ if (kmx_processor::is_handled(buf)) {
+ return new kmx_processor(kb_name, buf);
}
- else {
- return new null_processor();
+ if (mock_processor::is_handled(buf)) {
+ return new mock_processor(kb_name);
}
+ return new null_processor();
}
+} // namespace
-}
km_core_status
-km_core_keyboard_load(km_core_path_name kb_path, km_core_keyboard **keyboard)
-{
+km_core_keyboard_load_from_blob(
+ const km_core_path_name kb_name,
+ const void* blob,
+ const size_t blob_size,
+ km_core_keyboard** keyboard
+) {
assert(keyboard);
- if (!keyboard)
+ if (!keyboard || !blob) {
return KM_CORE_STATUS_INVALID_ARGUMENT;
+ }
- try
- {
- abstract_processor *kp = processor_factory(kb_path);
- km_core_status status = kp->validate();
+ std::vector buf((uint8_t*)blob, (uint8_t*)blob + blob_size);
+ *keyboard = nullptr;
+ try {
+ abstract_processor* kp = processor_factory(kb_name, buf);
+ km_core_status status = kp->validate();
if (status != KM_CORE_STATUS_OK) {
delete kp;
return status;
}
- *keyboard = static_cast(kp);
- }
- catch (std::bad_alloc &)
- {
+ *keyboard = static_cast(kp);
+ } catch (std::bad_alloc&) {
return KM_CORE_STATUS_NO_MEM;
}
return KM_CORE_STATUS_OK;
diff --git a/core/src/km_core_state_api.cpp b/core/src/km_core_state_api.cpp
index 8165a85779e..6c74cd159ba 100644
--- a/core/src/km_core_state_api.cpp
+++ b/core/src/km_core_state_api.cpp
@@ -225,7 +225,7 @@ km_core_status km_core_state_to_json(km_core_state const *state,
{
// Pretty print the document.
jo << json::object
- << "$schema" << "keyman/core/doc/introspection.schema"
+ << "$schema" << "keyman/core/docs/introspection.schema"
<< "keyboard" << state->processor().keyboard()
// << "options" << state->options() TODO: Fix
<< "context" << state->context()
diff --git a/core/src/kmx/kmx_file.cpp b/core/src/kmx/kmx_file.cpp
index 8258c93b8a0..2eea0185813 100644
--- a/core/src/kmx/kmx_file.cpp
+++ b/core/src/kmx/kmx_file.cpp
@@ -13,9 +13,9 @@ using namespace kmx;
#include
#endif
-KMX_BOOL KMX_ProcessEvent::Load(km_core_path_name KeyboardName)
-{
- if(!LoadKeyboard(KeyboardName, &m_keyboard.Keyboard)) return FALSE; // I5136
+KMX_BOOL KMX_ProcessEvent::Load(PKMX_BYTE buf, size_t sz) {
+ if(!LoadKeyboardFromBlob(buf, sz, &m_keyboard.Keyboard))
+ return FALSE; // I5136
return TRUE;
}
@@ -51,50 +51,22 @@ const int km::core::kmx::CODE__SIZE[] = {
// Ensure that all CODE_### sizes are defined
static_assert(sizeof(CODE__SIZE) / sizeof(CODE__SIZE[0]) == (CODE_LASTCODE + 1), "Size of array CODE__SIZE not correct");
-
-
-KMX_BOOL KMX_ProcessEvent::LoadKeyboard(km_core_path_name fileName, LPKEYBOARD *lpKeyboard)
-{
- PKMX_BYTE buf;
- FILE *fp;
+KMX_BOOL KMX_ProcessEvent::LoadKeyboardFromBlob(
+ PKMX_BYTE original_buf,
+ size_t sz,
+ LPKEYBOARD* lpKeyboard
+) {
LPKEYBOARD kbp;
+ PKMX_BYTE buf;
PKMX_BYTE filebase;
- DebugLog("Loading file '%s'", fileName);
- if(!fileName || !lpKeyboard)
- {
- DebugLog("Bad Filename");
- return FALSE;
- }
-
-#if defined(_WIN32) || defined(_WIN64)
- fp = _wfsopen(fileName, L"rb", _SH_DENYWR);
-#else
- fp = fopen(fileName, "rb");
-#endif
- if(fp == NULL)
- {
- DebugLog("Could not open file");
- return FALSE;
- }
-
- if (fseek(fp, 0, SEEK_END) != 0) {
- fclose(fp);
- DebugLog("Could not fseek file");
- return FALSE;
- }
- auto sz = ftell(fp);
- if (sz < 0) {
- fclose(fp);
+ if (!lpKeyboard || !original_buf) {
+ DebugLog("Invalid parameter");
return FALSE;
}
- if (fseek(fp, 0, SEEK_SET) != 0) {
- fclose(fp);
- DebugLog("Could not fseek(set) file");
- return FALSE;
- }
+ *lpKeyboard = NULL;
#ifdef KMX_64BIT
// allocate enough memory for expanded data structure + original data.
@@ -108,57 +80,55 @@ KMX_BOOL KMX_ProcessEvent::LoadKeyboard(km_core_path_name fileName, LPKEYBOARD *
buf = new KMX_BYTE[sz];
#endif
- if(!buf)
- {
- fclose(fp);
+ if (!buf) {
DebugLog("Not allocmem");
return FALSE;
}
#ifdef KMX_64BIT
- filebase = buf + sz*2;
+ filebase = buf + sz * 2;
#else
filebase = buf;
#endif
+ memcpy(filebase, original_buf, sz);
- if (fread(filebase, 1, sz, fp) < (size_t) sz) {
- fclose(fp);
- DebugLog("Could not read file");
+ if (*PKMX_DWORD(filebase) != KMX_DWORD(FILEID_COMPILED)) {
+ DebugLog("Invalid keyboard - signature is invalid");
+ delete[] buf;
return FALSE;
}
- fclose(fp);
-
- if(*PKMX_DWORD(filebase) != KMX_DWORD(FILEID_COMPILED))
- {
- delete [] buf;
- DebugLog("Invalid file - signature is invalid");
+ if (!VerifyKeyboard(filebase, sz)) {
+ DebugLog("Verify keyboard failed");
+ delete[] buf;
return FALSE;
}
- if(!VerifyKeyboard(filebase, sz)) return FALSE;
-
#ifdef KMX_64BIT
kbp = CopyKeyboard(buf, filebase);
#else
kbp = FixupKeyboard(buf, filebase);
#endif
- if(!kbp) return FALSE;
+ if (!kbp) {
+ DebugLog("Can't copy/fixup keyboard");
+ delete[] buf;
+ return FALSE;
+ }
- if(kbp->dwIdentifier != FILEID_COMPILED) {
- delete [] buf;
+ if (kbp->dwIdentifier != FILEID_COMPILED) {
DebugLog("errNotFileID");
+ delete[] buf;
return FALSE;
}
*lpKeyboard = kbp;
-
return TRUE;
}
PKMX_WCHAR KMX_ProcessEvent::StringOffset(PKMX_BYTE base, KMX_DWORD offset)
{
- if(offset == 0) return NULL;
+ if(offset == 0)
+ return NULL;
return (PKMX_WCHAR)(base + offset);
}
diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp
index 6e1e8dc2b4a..70d20102bc7 100644
--- a/core/src/kmx/kmx_processevent.cpp
+++ b/core/src/kmx/kmx_processevent.cpp
@@ -12,7 +12,7 @@ using namespace kmx;
/* Globals */
KMX_BOOL km::core::kmx::g_debug_ToConsole = FALSE;
-KMX_BOOL km::core::kmx::g_debug_KeymanLog = TRUE;
+KMX_BOOL km::core::kmx::g_debug_KeymanLog = FALSE; // workaround for #12661
KMX_BOOL km::core::kmx::g_silent = FALSE;
/*
diff --git a/core/src/kmx/kmx_processevent.h b/core/src/kmx/kmx_processevent.h
index 50e84c996d8..090488dccb4 100644
--- a/core/src/kmx/kmx_processevent.h
+++ b/core/src/kmx/kmx_processevent.h
@@ -54,7 +54,7 @@ class KMX_ProcessEvent {
KMX_DWORD m_modifiers = 0;
/* File loading */
- KMX_BOOL LoadKeyboard(km_core_path_name fileName, LPKEYBOARD *lpKeyboard);
+ KMX_BOOL LoadKeyboardFromBlob(PKMX_BYTE buf, size_t sz, LPKEYBOARD* lpKeyboard);
KMX_BOOL VerifyKeyboard(PKMX_BYTE filebase, size_t sz);
KMX_BOOL VerifyChecksum(PKMX_BYTE buf, size_t sz);
#ifdef KMX_64BIT
@@ -96,7 +96,7 @@ class KMX_ProcessEvent {
KMX_ProcessEvent();
~KMX_ProcessEvent();
- KMX_BOOL Load(km_core_path_name keyboardName);
+ KMX_BOOL Load(PKMX_BYTE buf, size_t sz);
KMX_BOOL ProcessEvent(km_core_state *state, KMX_UINT vkey, KMX_DWORD modifiers, KMX_BOOL isKeyDown); // returns FALSE on error or key not matched
KMX_Actions *GetActions();
diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp
index 95a9a094564..7ce8fad6bab 100644
--- a/core/src/kmx/kmx_processor.cpp
+++ b/core/src/kmx/kmx_processor.cpp
@@ -38,9 +38,8 @@ km_core_status kmx_processor::validate() const {
return _valid ? KM_CORE_STATUS_OK : KM_CORE_STATUS_INVALID_KEYBOARD;
}
-kmx_processor::kmx_processor(core::path p) {
- p.replace_extension(".kmx");
- _valid = bool(_kmx.Load(p.c_str()));
+kmx_processor::kmx_processor(std::u16string const& kb_name, const std::vector& data) {
+ _valid = bool(_kmx.Load((PKMX_BYTE)data.data(), data.size()));
if (!_valid)
return;
@@ -57,8 +56,7 @@ kmx_processor::kmx_processor(core::path p) {
auto v = _kmx.GetKeyboard()->Keyboard->version;
auto vs = std::to_string(v >> 16) + "." + std::to_string(v & 0xffff);
- _attributes = keyboard_attributes(static_cast(p.stem()),
- std::u16string(vs.begin(), vs.end()), p.parent(), defaults);
+ _attributes = keyboard_attributes(kb_name, std::u16string(vs.begin(), vs.end()), defaults);
}
char16_t const *
@@ -414,3 +412,24 @@ km_core_keyboard_imx * kmx_processor::get_imx_list() const {
return imx_list;
}
+/**
+ * Returns true the data is a KMX file, i.e. starts with 'KXTS'.
+ *
+ * @param data the keyboard blob
+ * @return true if the processor can handle the keyboard, otherwise false.
+ */
+bool kmx_processor::is_handled(const std::vector& data) {
+ if (data.empty()) {
+ return false;
+ }
+
+ if (data.size() < sizeof(COMP_KEYBOARD)) { // a KMX file is at least 64 bytes (KMX header)
+ return false;
+ }
+
+ if (data.size() >= KMX_MAX_ALLOWED_FILE_SIZE) {
+ return false;
+ }
+
+ return ((kmx::PCOMP_KEYBOARD)data.data())->dwIdentifier == KMX_DWORD(FILEID_COMPILED); // 'KXTS'
+}
diff --git a/core/src/kmx/kmx_processor.hpp b/core/src/kmx/kmx_processor.hpp
index 08b2c1d242f..6e3c4dd963a 100644
--- a/core/src/kmx/kmx_processor.hpp
+++ b/core/src/kmx/kmx_processor.hpp
@@ -29,7 +29,7 @@ namespace core
);
public:
- kmx_processor(path);
+ kmx_processor(std::u16string const& kb_name, const std::vector& data);
km_core_status
process_event(
@@ -84,6 +84,8 @@ namespace core
supports_normalization() const override {
return false;
}
+
+ static bool is_handled(const std::vector& buf);
};
} // namespace core
diff --git a/core/src/ldml/ldml_markers.cpp b/core/src/ldml/ldml_markers.cpp
index 5d4fb4ff462..50c2282828d 100644
--- a/core/src/ldml/ldml_markers.cpp
+++ b/core/src/ldml/ldml_markers.cpp
@@ -56,15 +56,19 @@ void add_back_markers(std::u32string &str, const std::u32string &src, marker_map
str.clear();
// iterator over the marker map
auto marki = map2.rbegin();
+#ifndef NDEBUG
// number of markers left to processnfd
size_t max_markers = count_markers(map);
size_t processed_markers = 0;
+#endif
// add any end-of-text markers
while(marki != map2.rend() && marki->ch == MARKER_BEFORE_EOT) {
if (!marki->end) {
prepend_marker(str, marki->marker, encoding);
+#ifndef NDEBUG
processed_markers++;
+#endif
}
marki->processed = true; // mark as done
marki++;
@@ -88,7 +92,9 @@ void add_back_markers(std::u32string &str, const std::u32string &src, marker_map
break;
} else {
prepend_marker(str, i->marker, encoding);
+#ifndef NDEBUG
processed_markers++;
+#endif
}
}
}
diff --git a/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp
index fcadda629cb..548c0d76b00 100644
--- a/core/src/ldml/ldml_processor.cpp
+++ b/core/src/ldml/ldml_processor.cpp
@@ -5,7 +5,6 @@
Authors: Marc Durdin (MD)
*/
-#include
#include
#include "ldml/ldml_processor.hpp"
#include "ldml/ldml_transforms.hpp"
@@ -15,6 +14,7 @@
#include "kmx/kmx_plus.h"
#include "kmx/kmx_xstring.h"
#include "kmx/kmx_processevent.h"
+#include "kmx/kmx_processor.hpp"
#include "ldml/keyman_core_ldml.h"
#include "kmx/kmx_file_validator.hpp"
#include "debuglog.h"
@@ -36,19 +36,15 @@ namespace {
namespace km {
namespace core {
-
-ldml_processor::ldml_processor(path const & kb_path, const std::vector &data)
-: abstract_processor(
- keyboard_attributes(kb_path.stem(), KM_CORE_LMDL_PROCESSOR_VERSION, kb_path.parent(), {})
- ), _valid(false), transforms(), bksp_transforms(), keys(), normalization_disabled(false)
-{
-
+ldml_processor::ldml_processor(std::u16string const& kb_name, const std::vector& data)
+ : abstract_processor(keyboard_attributes(kb_name, KM_CORE_LMDL_PROCESSOR_VERSION, {})),
+ _valid(false), transforms(), bksp_transforms(), keys(), normalization_disabled(false) {
if(data.size() <= sizeof(kmx::COMP_KEYBOARD_EX)) {
DebugLog("data.size %zu too small", data.size());
return;
}
-// // Locate the structs here, but still retain ptrs to the raw structs.
+ // Locate the structs here, but still retain ptrs to the raw structs.
kmx::KMX_FileValidator* comp_keyboard = (kmx::KMX_FileValidator*)data.data();
// Perform the standard validation
@@ -116,40 +112,24 @@ ldml_processor::ldml_processor(path const & kb_path, const std::vector
_valid = true;
}
-bool ldml_processor::is_kmxplus_file(path const & kb_path, std::vector& data) {
-// TODO-LDML: we should refactor all the core components to delegate file loading
-// to the Engine, which requires an API change, but this makes delivery
-// of keyboard files more flexible under more WASM.
-
- std::ifstream file(static_cast(kb_path), std::ios::binary | std::ios::ate);
- if(!file.good()) {
- return false;
- }
- const std::streamsize size = file.tellg();
- if(size >= KMX_MAX_ALLOWED_FILE_SIZE) {
+/**
+ * Returns true if the data is a KMX+ file.
+ *
+ * @param data the keyboard blob
+ * @return true if the processor can handle the keyboard, otherwise false.
+ */
+bool ldml_processor::is_handled(const std::vector& data) {
+ // Check if it's a blob from a KMX file
+ if (!kmx_processor::is_handled(data)) {
return false;
}
- file.seekg(0, std::ios::beg);
-
- data.resize((size_t)size);
- if(!file.read((char *) data.data(), size)) {
- return false;
- }
-
- file.close();
-
const kmx::PCOMP_KEYBOARD comp_keyboard = (kmx::PCOMP_KEYBOARD)data.data();
-
- if(comp_keyboard->dwIdentifier != KMX_DWORD(FILEID_COMPILED)) {
- return false;
- }
-
- if(comp_keyboard->dwFileVersion < VERSION_160 || (comp_keyboard->dwFlags & KF_KMXPLUS) == 0) {
+ if (comp_keyboard->dwFileVersion < VERSION_160 || (comp_keyboard->dwFlags & KF_KMXPLUS) == 0) {
return false;
}
- // A KMXPlus file is in the buffer (although more validation is required and will
+ // The buffer contains KMXPlus data (although more validation is required and will
// be done in the constructor)
return true;
}
@@ -430,8 +410,10 @@ ldml_event_state::remove_text(std::u32string &str, size_t length) {
/** track how many context items have been removed, via push_backspace() */
size_t contextRemoved = 0;
for (auto c = state->context().rbegin(); length > 0 && c != state->context().rend(); c++, contextRemoved++) {
+#ifndef NDEBUG
/** last char of context */
km_core_usv lastCtx = str.back();
+#endif
uint8_t type = c->type;
assert(type == KM_CORE_BT_CHAR || type == KM_CORE_BT_MARKER);
if (type == KM_CORE_BT_CHAR) {
diff --git a/core/src/ldml/ldml_processor.hpp b/core/src/ldml/ldml_processor.hpp
index fa5fada27ed..82f59292c32 100644
--- a/core/src/ldml/ldml_processor.hpp
+++ b/core/src/ldml/ldml_processor.hpp
@@ -30,13 +30,12 @@ class ldml_event_state;
class ldml_processor : public abstract_processor {
public:
ldml_processor(
- path const & kb_path,
+ std::u16string const& kb_name,
const std::vector & data
);
static bool is_kmxplus_file(
- path const & kb_path,
- std::vector& data
+ const std::vector & data
);
km_core_status
@@ -86,9 +85,11 @@ class ldml_processor : public abstract_processor {
return !normalization_disabled;
}
+ static bool is_handled(const std::vector & buf);
+
private:
/** process a key-up */
- void process_key_up(ldml_event_state &ldml_state) const;
+ void process_key_up(ldml_event_state& ldml_state) const;
/** process a key-down (if it wasn't handled exceptionally) */
void process_key_down(ldml_event_state &ldml_state) const;
diff --git a/core/src/meson.build b/core/src/meson.build
index 41c198543cb..449dd3644fd 100644
--- a/core/src/meson.build
+++ b/core/src/meson.build
@@ -48,23 +48,27 @@ endif
generated_headers = []
if cpp_compiler.get_id() == 'emscripten'
-
-util_normalize_table_generator = executable('util_normalize_table_generator',
- ['util_normalize_table_generator.cpp'],
- cpp_args: defns + warns,
- include_directories: [inc],
- link_args: links,
- dependencies: [icu_uc, icu_i18n],
- )
-
-util_normalize_table_h = custom_target('util_normalize_table.h',
- output: 'util_normalize_table.h',
- command: [util_normalize_table_generator],
- capture:true)
-
-generated_headers += util_normalize_table_h
-
-
+ links += [
+ '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\',\'stringToNewUTF8\',\'wasmExports\']',
+ # Forcing inclusion of debug symbols
+ '-g', '-Wlimited-postlink-optimizations',
+ '-lembind'
+ ]
+
+ util_normalize_table_generator = executable('util_normalize_table_generator',
+ ['util_normalize_table_generator.cpp'],
+ cpp_args: defns + warns,
+ include_directories: [inc],
+ link_args: links,
+ dependencies: [icu_uc, icu_i18n],
+ )
+
+ util_normalize_table_h = custom_target('util_normalize_table.h',
+ output: 'util_normalize_table.h',
+ command: [util_normalize_table_generator],
+ capture:true)
+
+ generated_headers += util_normalize_table_h
endif
diff --git a/core/src/mock/mock_processor.cpp b/core/src/mock/mock_processor.cpp
index d78ab5ef0e0..570553264d6 100644
--- a/core/src/mock/mock_processor.cpp
+++ b/core/src/mock/mock_processor.cpp
@@ -3,7 +3,7 @@
Description: This is a test implementation of the keyboard processor API to
enable testing API clients against a basic keyboard and give
them something to link against and load.
- TODO: Add a mecahnism to trigger output of PERSIST_OPT &
+ TODO: Add a mechanism to trigger output of PERSIST_OPT &
RESET_OPT actions items, options support and context matching.
Create Date: 17 Oct 2018
Authors: Tim Eves (TSE)
@@ -74,7 +74,7 @@ namespace km {
{
mock_processor::mock_processor(core::path const & path)
: abstract_processor(
- keyboard_attributes(path.stem(), u"3.145", path.parent(), {
+ keyboard_attributes(path.stem(), u"3.145", {
option{KM_CORE_OPT_KEYBOARD, u"__test_point", u"not tiggered"},
})),
_options({
@@ -228,5 +228,23 @@ namespace km {
km_core_status mock_processor::validate() const { return KM_CORE_STATUS_OK; }
km_core_status null_processor::validate() const { return KM_CORE_STATUS_INVALID_ARGUMENT; }
- } // namespace core
+
+ /**
+ * Returns true if the data starts with 'MOCK'.
+ *
+ * @param data the keyboard blob
+ * @return true if the processor can handle the keyboard, otherwise false.
+ */
+ bool mock_processor::is_handled(const std::vector& data) {
+ if (data.empty()) {
+ return false;
+ }
+
+ if (data.size() < 4) { // a MOCK file is at least 4 bytes (MOCK)
+ return false;
+ }
+
+ return ((char*)data.data())[0] == 'M' && ((char*)data.data())[1] == 'O' && ((char*)data.data())[2] == 'C' && ((char*)data.data())[3] == 'K';
+ }
+ } // namespace core
} // namespace km
diff --git a/core/src/mock/mock_processor.hpp b/core/src/mock/mock_processor.hpp
index 97a7b82ab44..06fc29ad580 100644
--- a/core/src/mock/mock_processor.hpp
+++ b/core/src/mock/mock_processor.hpp
@@ -69,13 +69,21 @@ namespace core
supports_normalization() const override {
return true;
}
+
+ /**
+ * Returns true if the data starts with 'MOCK'
+ *
+ * @param buf the keyboard blob
+ * @return true if the processor can handle the keyboard, otherwise false.
+ */
+ static bool is_handled(const std::vector& data);
};
class null_processor : public mock_processor {
public:
null_processor(): mock_processor(path())
{
- _attributes = keyboard_attributes(u"null", u"0.0", path(), {});
+ _attributes = keyboard_attributes(u"null", u"0.0", {});
}
km_core_status validate() const override;
diff --git a/core/src/util_normalize_table_generator.cpp b/core/src/util_normalize_table_generator.cpp
index 994f16d8b37..28edf68188a 100644
--- a/core/src/util_normalize_table_generator.cpp
+++ b/core/src/util_normalize_table_generator.cpp
@@ -17,7 +17,6 @@
#include "core_icu.h"
-#include
#include
#include
#include
diff --git a/core/subprojects/.gitignore b/core/subprojects/.gitignore
index 99479aa952c..0161ac6349f 100644
--- a/core/subprojects/.gitignore
+++ b/core/subprojects/.gitignore
@@ -2,3 +2,4 @@
/*.zip
/*.tgz
/packagecache
+/googletest*/
diff --git a/core/subprojects/gtest.wrap b/core/subprojects/gtest.wrap
new file mode 100644
index 00000000000..a77eb315bfa
--- /dev/null
+++ b/core/subprojects/gtest.wrap
@@ -0,0 +1,16 @@
+[wrap-file]
+directory = googletest-1.14.0
+source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz
+source_filename = gtest-1.14.0.tar.gz
+source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7
+patch_filename = gtest_1.14.0-2_patch.zip
+patch_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-2/gtest_1.14.0-2_patch.zip
+patch_hash = 4ec7f767364386a99f7b2d61678287a73ad6ba0f9998be43b51794c464a63732
+source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-2/gtest-1.14.0.tar.gz
+wrapdb_version = 1.14.0-2
+
+[provide]
+gtest = gtest_dep
+gtest_main = gtest_main_dep
+gmock = gmock_dep
+gmock_main = gmock_main_dep
diff --git a/core/tests/kmx_test_source/kmx_test_source.cpp b/core/tests/kmx_test_source/kmx_test_source.cpp
index 0464bce8832..9ba7ed36e83 100644
--- a/core/tests/kmx_test_source/kmx_test_source.cpp
+++ b/core/tests/kmx_test_source/kmx_test_source.cpp
@@ -18,6 +18,9 @@
#include "kmx_test_source.hpp"
+#include
+#include
+
namespace km {
namespace tests {
@@ -51,7 +54,7 @@ KmxTestSource::parse_source_string(std::string const &s) {
if (*p == '\\') {
p++;
km_core_usv v;
- assert(p != s.end());
+ test_assert(p != s.end());
if (*p == 'u' || *p == 'U') {
// Unicode value
p++;
@@ -59,7 +62,7 @@ KmxTestSource::parse_source_string(std::string const &s) {
std::string s1 = s.substr(p - s.begin(), 8);
v = std::stoul(s1, &n, 16);
// Allow deadkey_number (U+0001) characters and onward
- assert(v >= 0x0001 && v <= 0x10FFFF);
+ test_assert(v >= 0x0001 && v <= 0x10FFFF);
p += n - 1;
if (v < 0x10000) {
t += km_core_cu(v);
@@ -70,7 +73,7 @@ KmxTestSource::parse_source_string(std::string const &s) {
} else if (*p == 'd') {
// Deadkey
// TODO, not yet supported
- assert(false);
+ test_assert(false);
}
} else {
t += *p;
@@ -212,7 +215,7 @@ KmxTestSource::get_keyboard_options(kmx_options options) {
key_event
KmxTestSource::char_to_event(char ch) {
- assert(ch >= 32);
+ test_assert(ch >= 32);
return {
km::core::kmx::s_char_to_vkey[(int)ch - 32].vk,
(uint16_t)(km::core::kmx::s_char_to_vkey[(int)ch - 32].shifted ? KM_CORE_MODIFIER_SHIFT : 0)};
@@ -258,8 +261,8 @@ KmxTestSource::vkey_to_event(std::string const &vk_event) {
}
// The string should be empty at this point
- assert(!std::getline(f, s, ' '));
- assert(vk != 0);
+ test_assert(!std::getline(f, s, ' '));
+ test_assert(vk != 0);
return {vk, modifier_state};
}
@@ -276,7 +279,7 @@ KmxTestSource::next_key(std::string &keys) {
return char_to_event(ch);
}
auto n = keys.find(']');
- assert(n != std::string::npos);
+ test_assert(n != std::string::npos);
auto vkey = keys.substr(1, n - 1);
keys.erase(0, n + 1);
return vkey_to_event(vkey);
diff --git a/core/tests/meson.build b/core/tests/meson.build
index 90e0ab2a54b..ce0f68a2577 100644
--- a/core/tests/meson.build
+++ b/core/tests/meson.build
@@ -7,7 +7,7 @@
#
# Note: this version of cmpfiles ignores line endings, which is better for platform independence
-cmpfiles = ['-c', 'import sys; a = open(sys.argv[1], \'r\').read(); b = open(sys.argv[2], \'r\').read(); exit(not (a==b))']
+cmpfiles = ['-c', 'import sys; a = open(sys.argv[1], \'r\').read(); b = open(sys.argv[2], \'r\').read(); sys.exit(not (a==b))']
stnds = join_paths(meson.current_source_dir(), 'standards')
libsrc = include_directories(
diff --git a/core/tests/unit/emscripten_filesystem.cpp b/core/tests/unit/emscripten_filesystem.cpp
index c5e180f6bea..d51fa98f12d 100644
--- a/core/tests/unit/emscripten_filesystem.cpp
+++ b/core/tests/unit/emscripten_filesystem.cpp
@@ -4,7 +4,7 @@
#include
#include
-#include
+#include
const std::string get_wasm_file_path(const std::string& filename) {
// Verify that we are passing a fully-qualified path
@@ -13,13 +13,13 @@ const std::string get_wasm_file_path(const std::string& filename) {
std::cout << "get_wasm_file_path ENTER (" << filename << ")" << std::endl;
#endif
- assert(
+ test_assert(
(filename.length() > 0 && filename.at(0) == '/') ||
(filename.length() > 1 && filename.at(1) == ':')
);
#if _DEBUG_FOPEN
- std::cout << "get_wasm_file_path assert passed " << std::endl;
+ std::cout << "get_wasm_file_path test_assert passed " << std::endl;
#endif
EM_ASM_({
diff --git a/core/tests/unit/km_core_keyboard_api.tests.cpp b/core/tests/unit/km_core_keyboard_api.tests.cpp
new file mode 100644
index 00000000000..2898f9e7cd7
--- /dev/null
+++ b/core/tests/unit/km_core_keyboard_api.tests.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2024 SIL International
+// This software is licensed under the MIT license (http://opensource.org/licenses/MIT)
+
+#include
+#include
+
+#include
+#include "emscripten_filesystem.h"
+#include "load_kmx_file.hpp"
+
+km::core::path test_dir;
+
+class KmCoreKeyboardApiTests : public testing::Test {
+protected:
+ km_core_keyboard* keyboard = nullptr;
+ void TearDown() override {
+ if (this->keyboard) {
+ km_core_keyboard_dispose(this->keyboard);
+ this->keyboard = nullptr;
+ }
+ }
+};
+
+TEST_F(KmCoreKeyboardApiTests, LoadFromBlob) {
+ // Setup
+ km::core::path kmxfile = km::core::path(test_dir / "kmx/k_020___deadkeys_and_backspace.kmx");
+
+ std::vector data = km::tests::load_kmx_file(kmxfile.native());
+ ASSERT_GT(data.size(), (size_t)0);
+
+ // Execute
+ auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.data(), data.size(), &this->keyboard);
+
+ // Verify
+ EXPECT_EQ(status, KM_CORE_STATUS_OK);
+ EXPECT_TRUE(this->keyboard != nullptr);
+}
+
+TEST_F(KmCoreKeyboardApiTests, LoadFromBlobMock) {
+ // Setup
+ km::core::path kmxfile = "mock_keyboard.mock";
+ std::string blob_string = "MOCK";
+
+ std::vector data = std::vector(blob_string.begin(), blob_string.end());
+ ASSERT_GT(data.size(), (size_t)0);
+
+ // Execute
+ auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.data(), data.size(), &this->keyboard);
+
+ // Verify
+ EXPECT_EQ(status, KM_CORE_STATUS_OK);
+ EXPECT_TRUE(this->keyboard != nullptr);
+}
+
+TEST_F(KmCoreKeyboardApiTests, LoadFromBlobNull) {
+ // Setup
+ km::core::path kmxfile = "";
+
+ std::unique_ptr data(new uint8_t[0]);
+
+ // Execute
+ auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.get(), 0, &this->keyboard);
+
+ // Verify
+ EXPECT_EQ(status, KM_CORE_STATUS_INVALID_ARGUMENT);
+ EXPECT_TRUE(this->keyboard == nullptr);
+}
+
+TEST_F(KmCoreKeyboardApiTests, LoadFromBlobInvalidKeyboard) {
+ // Setup
+ km::core::path kmxfile = "invalid_keyboard.kmx";
+ std::string blob_string = "KXTS";
+
+ std::vector data = std::vector(blob_string.begin(), blob_string.end());
+ for (auto i = data.size(); i < 64; i++) {
+ data.push_back(0);
+ }
+ ASSERT_GT(data.size(), (size_t)0);
+
+ // Execute
+ auto status = km_core_keyboard_load_from_blob(kmxfile.stem().c_str(), data.data(), data.size(), &this->keyboard);
+
+ // Verify
+ EXPECT_EQ(status, KM_CORE_STATUS_INVALID_KEYBOARD);
+ EXPECT_TRUE(this->keyboard == nullptr);
+}
+
+// provide our own `main` so that we can get the path of the exe so that
+// we have a well-defined location to find our test keyboards
+int main(int argc, char **argv) {
+#ifdef __EMSCRIPTEN__
+ test_dir = get_wasm_file_path(km::core::path(argv[0]).parent());
+#else
+ test_dir = km::core::path(argv[0]).parent();
+#endif
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/core/tests/unit/kmnkbd/action_api.cpp b/core/tests/unit/kmnkbd/action_api.tests.cpp
similarity index 61%
rename from core/tests/unit/kmnkbd/action_api.cpp
rename to core/tests/unit/kmnkbd/action_api.tests.cpp
index 6899539d874..b4bbb1cd844 100644
--- a/core/tests/unit/kmnkbd/action_api.cpp
+++ b/core/tests/unit/kmnkbd/action_api.tests.cpp
@@ -35,19 +35,19 @@ void test_two_backspaces() {
};
km_core_actions actions;
- assert(km::core::action_item_list_to_actions_object(action_items, &actions));
+ test_assert(km::core::action_item_list_to_actions_object(action_items, &actions));
- assert(actions.code_points_to_delete == 1);
- assert(std::u32string(actions.output) == U"");
- assert(actions.persist_options != nullptr);
- assert(actions.persist_options[0].key == nullptr);
- assert(actions.persist_options[0].value == nullptr);
- assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
+ test_assert(actions.code_points_to_delete == 1);
+ test_assert(std::u32string(actions.output) == U"");
+ test_assert(actions.persist_options != nullptr);
+ test_assert(actions.persist_options[0].key == nullptr);
+ test_assert(actions.persist_options[0].value == nullptr);
+ test_assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
- assert(actions.do_alert == false);
- assert(actions.emit_keystroke == false);
- assert(actions.new_caps_lock_state == -1);
- assert(actions.deleted_context == nullptr);
+ test_assert(actions.do_alert == false);
+ test_assert(actions.emit_keystroke == false);
+ test_assert(actions.new_caps_lock_state == -1);
+ test_assert(actions.deleted_context == nullptr);
km::core::actions_dispose(actions);
}
@@ -68,18 +68,18 @@ void test_marker_text_interleaved() {
};
km_core_actions actions;
- assert(km::core::action_item_list_to_actions_object(action_items, &actions));
-
- assert(actions.code_points_to_delete == 0);
- assert(std::u32string(actions.output) == U"ABD");
- assert(actions.persist_options != nullptr);
- assert(actions.persist_options[0].key == nullptr);
- assert(actions.persist_options[0].value == nullptr);
- assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
- assert(actions.do_alert == false);
- assert(actions.emit_keystroke == false);
- assert(actions.new_caps_lock_state == -1);
- assert(actions.deleted_context == nullptr);
+ test_assert(km::core::action_item_list_to_actions_object(action_items, &actions));
+
+ test_assert(actions.code_points_to_delete == 0);
+ test_assert(std::u32string(actions.output) == U"ABD");
+ test_assert(actions.persist_options != nullptr);
+ test_assert(actions.persist_options[0].key == nullptr);
+ test_assert(actions.persist_options[0].value == nullptr);
+ test_assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
+ test_assert(actions.do_alert == false);
+ test_assert(actions.emit_keystroke == false);
+ test_assert(actions.new_caps_lock_state == -1);
+ test_assert(actions.deleted_context == nullptr);
km::core::actions_dispose(actions);
}
@@ -93,18 +93,18 @@ void test_alert() {
};
km_core_actions actions;
- assert(km::core::action_item_list_to_actions_object(action_items, &actions));
-
- assert(actions.code_points_to_delete == 0);
- assert(std::u32string(actions.output) == U"");
- assert(actions.persist_options != nullptr);
- assert(actions.persist_options[0].key == nullptr);
- assert(actions.persist_options[0].value == nullptr);
- assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
- assert(actions.do_alert == KM_CORE_TRUE);
- assert(actions.emit_keystroke == KM_CORE_FALSE);
- assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
- assert(actions.deleted_context == nullptr);
+ test_assert(km::core::action_item_list_to_actions_object(action_items, &actions));
+
+ test_assert(actions.code_points_to_delete == 0);
+ test_assert(std::u32string(actions.output) == U"");
+ test_assert(actions.persist_options != nullptr);
+ test_assert(actions.persist_options[0].key == nullptr);
+ test_assert(actions.persist_options[0].value == nullptr);
+ test_assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
+ test_assert(actions.do_alert == KM_CORE_TRUE);
+ test_assert(actions.emit_keystroke == KM_CORE_FALSE);
+ test_assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
+ test_assert(actions.deleted_context == nullptr);
km::core::actions_dispose(actions);
}
@@ -118,18 +118,18 @@ void test_emit_keystroke() {
};
km_core_actions actions;
- assert(km::core::action_item_list_to_actions_object(action_items, &actions));
-
- assert(actions.code_points_to_delete == 0);
- assert(std::u32string(actions.output) == U"");
- assert(actions.persist_options != nullptr);
- assert(actions.persist_options[0].key == nullptr);
- assert(actions.persist_options[0].value == nullptr);
- assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
- assert(actions.do_alert == KM_CORE_FALSE);
- assert(actions.emit_keystroke == KM_CORE_TRUE);
- assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
- assert(actions.deleted_context == nullptr);
+ test_assert(km::core::action_item_list_to_actions_object(action_items, &actions));
+
+ test_assert(actions.code_points_to_delete == 0);
+ test_assert(std::u32string(actions.output) == U"");
+ test_assert(actions.persist_options != nullptr);
+ test_assert(actions.persist_options[0].key == nullptr);
+ test_assert(actions.persist_options[0].value == nullptr);
+ test_assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
+ test_assert(actions.do_alert == KM_CORE_FALSE);
+ test_assert(actions.emit_keystroke == KM_CORE_TRUE);
+ test_assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
+ test_assert(actions.deleted_context == nullptr);
km::core::actions_dispose(actions);
}
@@ -144,18 +144,18 @@ void test_invalidate_context() {
};
km_core_actions actions;
- assert(km::core::action_item_list_to_actions_object(action_items, &actions));
-
- assert(actions.code_points_to_delete == 0);
- assert(std::u32string(actions.output) == U"");
- assert(actions.persist_options != nullptr);
- assert(actions.persist_options[0].key == nullptr);
- assert(actions.persist_options[0].value == nullptr);
- assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
- assert(actions.do_alert == KM_CORE_FALSE);
- assert(actions.emit_keystroke == KM_CORE_FALSE);
- assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
- assert(actions.deleted_context == nullptr);
+ test_assert(km::core::action_item_list_to_actions_object(action_items, &actions));
+
+ test_assert(actions.code_points_to_delete == 0);
+ test_assert(std::u32string(actions.output) == U"");
+ test_assert(actions.persist_options != nullptr);
+ test_assert(actions.persist_options[0].key == nullptr);
+ test_assert(actions.persist_options[0].value == nullptr);
+ test_assert(actions.persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
+ test_assert(actions.do_alert == KM_CORE_FALSE);
+ test_assert(actions.emit_keystroke == KM_CORE_FALSE);
+ test_assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
+ test_assert(actions.deleted_context == nullptr);
km::core::actions_dispose(actions);
}
@@ -175,28 +175,28 @@ void test_persist_opt() {
};
km_core_actions actions;
- assert(km::core::action_item_list_to_actions_object(action_items, &actions));
+ test_assert(km::core::action_item_list_to_actions_object(action_items, &actions));
- assert(actions.code_points_to_delete == 0);
- assert(std::u32string(actions.output) == U"");
- assert(actions.persist_options != nullptr);
- assert(std::u16string(actions.persist_options[0].key) == u"key");
- assert(std::u16string(actions.persist_options[0].value) == u"value");
- assert(actions.persist_options[0].scope == KM_CORE_OPT_KEYBOARD);
+ test_assert(actions.code_points_to_delete == 0);
+ test_assert(std::u32string(actions.output) == U"");
+ test_assert(actions.persist_options != nullptr);
+ test_assert(std::u16string(actions.persist_options[0].key) == u"key");
+ test_assert(std::u16string(actions.persist_options[0].value) == u"value");
+ test_assert(actions.persist_options[0].scope == KM_CORE_OPT_KEYBOARD);
// verify that data is copied
- assert(actions.persist_options[0].key != option.key);
- assert(actions.persist_options[0].value != option.value);
+ test_assert(actions.persist_options[0].key != option.key);
+ test_assert(actions.persist_options[0].value != option.value);
// verify that we have a KM_CORE_OPTIONS_END term
- assert(actions.persist_options[1].key == nullptr);
- assert(actions.persist_options[1].value == nullptr);
- assert(actions.persist_options[1].scope == KM_CORE_OPT_UNKNOWN);
-
- assert(actions.do_alert == KM_CORE_FALSE);
- assert(actions.emit_keystroke == KM_CORE_FALSE);
- assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
- assert(actions.deleted_context == nullptr);
+ test_assert(actions.persist_options[1].key == nullptr);
+ test_assert(actions.persist_options[1].value == nullptr);
+ test_assert(actions.persist_options[1].scope == KM_CORE_OPT_UNKNOWN);
+
+ test_assert(actions.do_alert == KM_CORE_FALSE);
+ test_assert(actions.emit_keystroke == KM_CORE_FALSE);
+ test_assert(actions.new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
+ test_assert(actions.deleted_context == nullptr);
km::core::actions_dispose(actions);
}
diff --git a/core/tests/unit/kmnkbd/action_set_api.cpp b/core/tests/unit/kmnkbd/action_set_api.tests.cpp
similarity index 92%
rename from core/tests/unit/kmnkbd/action_set_api.cpp
rename to core/tests/unit/kmnkbd/action_set_api.tests.cpp
index 544d64a7571..72a2d9be531 100644
--- a/core/tests/unit/kmnkbd/action_set_api.cpp
+++ b/core/tests/unit/kmnkbd/action_set_api.tests.cpp
@@ -14,6 +14,7 @@
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
const km_core_action_item alert_action_item();
const km_core_action_item bksp_action_item(uint8_t type, uintptr_t value);
@@ -57,7 +58,9 @@ void setup(const char *keyboard, const km_core_cu* context) {
teardown();
km::core::path path = km::core::path::join(arg_path, keyboard);
- try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
+
+ auto blob = km::tests::load_kmx_file(path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
try_status(context_items_from_utf16(context, &citems));
try_status(km_core_context_set(km_core_state_context(test_state), citems));
@@ -71,20 +74,20 @@ void run_test(km_core_action_item const * action_items, const km_core_actions &a
int n = 0;
for(auto act = set_actions.begin(); act != set_actions.end(); act++, n++) {
- assert(act->type == action_items[n].type);
+ test_assert(act->type == action_items[n].type);
// TODO: all other fields
switch(act->type) {
case KM_CORE_IT_ALERT:
break;
case KM_CORE_IT_BACK:
- assert(act->backspace.expected_type == action_items[n].backspace.expected_type);
- assert(act->backspace.expected_value == action_items[n].backspace.expected_value);
+ test_assert(act->backspace.expected_type == action_items[n].backspace.expected_type);
+ test_assert(act->backspace.expected_value == action_items[n].backspace.expected_value);
break;
case KM_CORE_IT_CAPSLOCK:
- assert(act->capsLock == action_items[n].capsLock);
+ test_assert(act->capsLock == action_items[n].capsLock);
break;
case KM_CORE_IT_CHAR:
- assert(act->character == action_items[n].character);
+ test_assert(act->character == action_items[n].character);
break;
case KM_CORE_IT_EMIT_KEYSTROKE:
break;
@@ -93,16 +96,16 @@ void run_test(km_core_action_item const * action_items, const km_core_actions &a
case KM_CORE_IT_INVALIDATE_CONTEXT:
break;
case KM_CORE_IT_MARKER:
- assert(act->marker == action_items[n].marker);
+ test_assert(act->marker == action_items[n].marker);
break;
case KM_CORE_IT_PERSIST_OPT:
- assert(act->option->scope == action_items[n].option->scope);
- assert(std::u16string(act->option->key) == action_items[n].option->key);
- assert(std::u16string(act->option->value) == action_items[n].option->value);
+ test_assert(act->option->scope == action_items[n].option->scope);
+ test_assert(std::u16string(act->option->key) == action_items[n].option->key);
+ test_assert(std::u16string(act->option->value) == action_items[n].option->value);
break;
default:
// Invalid action type
- assert(false);
+ test_assert(false);
}
}
}
diff --git a/core/tests/unit/kmnkbd/test_actions_get_api.cpp b/core/tests/unit/kmnkbd/actions_get_api.tests.cpp
similarity index 96%
rename from core/tests/unit/kmnkbd/test_actions_get_api.cpp
rename to core/tests/unit/kmnkbd/actions_get_api.tests.cpp
index ead21762957..f9b7c75dac5 100644
--- a/core/tests/unit/kmnkbd/test_actions_get_api.cpp
+++ b/core/tests/unit/kmnkbd/actions_get_api.tests.cpp
@@ -15,6 +15,7 @@
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
km_core_option_item test_env_opts[] =
{
@@ -48,7 +49,8 @@ void setup(const km_core_cu *app_context, const km_core_cu *cached_context, int
teardown();
km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "k_001_tiny.kmx");
- try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
try_status(set_context_from_string(km_core_state_context(test_state), cached_context));
@@ -137,17 +139,17 @@ void test(
}
std::cout << std::endl;
- assert(expected_delete == actual_actions->code_points_to_delete);
- assert(expected_output == actual_actions->output);
- assert(expected_deleted_context == actual_actions->deleted_context);
+ test_assert(expected_delete == actual_actions->code_points_to_delete);
+ test_assert(expected_output == actual_actions->output);
+ test_assert(expected_deleted_context == actual_actions->deleted_context);
- // assert(expected_deleted_context == actual_actions->deleted_context);
+ // test_assert(expected_deleted_context == actual_actions->deleted_context);
auto actual_final_app_context = get_context_as_string(km_core_state_app_context(test_state));
auto actual_final_app_context_string = std::u16string(actual_final_app_context);
auto expected_final_app_context_string = std::u16string(expected_final_app_context);
std::cout << " final app context: actual: |" << actual_final_app_context_string << "| expected: |" << expected_final_app_context_string << "|" << std::endl;
- assert(actual_final_app_context_string == expected_final_app_context_string);
+ test_assert(actual_final_app_context_string == expected_final_app_context_string);
delete [] actual_final_app_context;
teardown();
diff --git a/core/tests/unit/kmnkbd/test_actions_normalize.cpp b/core/tests/unit/kmnkbd/actions_normalize.tests.cpp
similarity index 97%
rename from core/tests/unit/kmnkbd/test_actions_normalize.cpp
rename to core/tests/unit/kmnkbd/actions_normalize.tests.cpp
index f9044c5f322..f71ef1cd3f1 100644
--- a/core/tests/unit/kmnkbd/test_actions_normalize.cpp
+++ b/core/tests/unit/kmnkbd/actions_normalize.tests.cpp
@@ -15,6 +15,7 @@
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
void compare_context(km_core_context *app_context, const km_core_cu* expected_final_app_context);
@@ -46,7 +47,8 @@ void setup(const km_core_cu *app_context, const km_core_cu *cached_context_strin
teardown();
km::core::path path = km::core::path::join(arg_path, "..", "ldml", "keyboards", "k_001_tiny.kmx");
- try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
if(cached_context_string) {
@@ -119,7 +121,7 @@ void test_actions_normalize(
setup(initial_app_context, final_cached_context_string, final_cached_context_items, actions_code_points_to_delete, actions_output);
- assert(km::core::actions_normalize(km_core_state_context(test_state), km_core_state_app_context(test_state), test_actions));
+ test_assert(km::core::actions_normalize(km_core_state_context(test_state), km_core_state_app_context(test_state), test_actions));
std::cout << "test_actions_normalize: (" << name << "): delete: " << test_actions.code_points_to_delete << " output: |" << std::u32string(test_actions.output) << "|" << std::endl;
std::u32string o(test_actions.output);
@@ -128,8 +130,8 @@ void test_actions_normalize(
}
std::cout << std::endl;
- assert(expected_delete == test_actions.code_points_to_delete);
- assert(expected_output == test_actions.output);
+ test_assert(expected_delete == test_actions.code_points_to_delete);
+ test_assert(expected_output == test_actions.output);
auto debug = km_core_state_context_debug(test_state, KM_CORE_DEBUG_CONTEXT_APP);
std::cout << " final app context: " << debug << std::endl;
@@ -193,7 +195,7 @@ void test_actions_update_app_context_nfu(
setup(initial_app_context, final_cached_context_string, final_cached_context_items, actions_code_points_to_delete, actions_output);
- assert(km::core::actions_update_app_context_nfu(km_core_state_context(test_state), km_core_state_app_context(test_state)));
+ test_assert(km::core::actions_update_app_context_nfu(km_core_state_context(test_state), km_core_state_app_context(test_state)));
std::cout << "test_actions_update_app_context_nfu: (" << name << "): delete: " << expected_delete << " output: |" << std::u32string(test_actions.output) << "|" << std::endl;
std::u32string o(test_actions.output);
@@ -202,8 +204,8 @@ void test_actions_update_app_context_nfu(
}
std::cout << std::endl;
- assert(expected_delete == test_actions.code_points_to_delete);
- assert(expected_output == test_actions.output);
+ test_assert(expected_delete == test_actions.code_points_to_delete);
+ test_assert(expected_output == test_actions.output);
auto debug = km_core_state_context_debug(test_state, KM_CORE_DEBUG_CONTEXT_APP);
std::cout << " final app context: " << debug << std::endl;
@@ -625,7 +627,7 @@ void compare_context(km_core_context *app_context, const km_core_cu* expected_fi
try_status(context_items_from_utf16(expected_final_app_context, &expected_final_app_context_items));
for(int i = 0; actual_final_app_context_items[i].type != KM_CORE_CT_END || expected_final_app_context_items[i].type != KM_CORE_CT_END; i++) {
- assert(
+ test_assert(
actual_final_app_context_items[i].type == expected_final_app_context_items[i].type &&
// union so testing character is sufficient to do both char + marker types
actual_final_app_context_items[i].character == expected_final_app_context_items[i].character
diff --git a/core/tests/unit/kmnkbd/context_api.cpp b/core/tests/unit/kmnkbd/context_api.tests.cpp
similarity index 100%
rename from core/tests/unit/kmnkbd/context_api.cpp
rename to core/tests/unit/kmnkbd/context_api.tests.cpp
diff --git a/core/tests/unit/kmnkbd/debug_api.cpp b/core/tests/unit/kmnkbd/debug_api.tests.cpp
similarity index 93%
rename from core/tests/unit/kmnkbd/debug_api.cpp
rename to core/tests/unit/kmnkbd/debug_api.tests.cpp
index 6542f3c378f..d40664436b0 100644
--- a/core/tests/unit/kmnkbd/debug_api.cpp
+++ b/core/tests/unit/kmnkbd/debug_api.tests.cpp
@@ -22,6 +22,7 @@
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
using namespace km::core::kmx;
@@ -54,16 +55,16 @@ void setup(const char *keyboard) {
teardown();
km::core::path path = km::core::path::join(arg_path, keyboard);
-
- try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
try_status(context_items_from_utf16(u"Hello 😁", &citems));
// Pre-test sanity: ensure debugging is disabled
- assert(km_core_state_debug_get(test_state) == 0);
+ test_assert(km_core_state_debug_get(test_state) == 0);
// Ensure the pre-run debug item state is not empty
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_END, {}, {}, {}}
}));
@@ -78,11 +79,11 @@ void test_debugging_disabled() {
setup("k_000___null_keyboard.kmx");
try_status(km_core_state_debug_set(test_state, 0));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_S, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_END}
}));
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}},
{KM_CORE_IT_END}
}));
@@ -96,14 +97,14 @@ void test_debugging_no_rule_match() {
DEBUG_GROUP gp = {u"Main"};
try_status(km_core_state_debug_set(test_state, 1));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_S, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_S, KM_CORE_MODIFIER_SHIFT, 'S'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, KM_CORE_DEBUG_FLAG_NOMATCH, {}, {u"", &gp, nullptr, {}, 1}},
km_core_state_debug_item{KM_CORE_DEBUG_END, 0, {}, {u"", nullptr, nullptr, {}, 1}}
}));
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}},
{KM_CORE_IT_END}
}));
@@ -117,14 +118,14 @@ void test_debugging_function_key() {
DEBUG_GROUP gp = {u"Main"};
try_status(km_core_state_debug_set(test_state, 1));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_F1, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_F1, 0, 0}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, KM_CORE_DEBUG_FLAG_NOMATCH, {}, {u"", &gp, nullptr, {}, 0}},
km_core_state_debug_item{KM_CORE_DEBUG_END, KM_CORE_DEBUG_FLAG_OUTPUTKEYSTROKE, {}, {u"", nullptr, nullptr, {}, 0}}
}));
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
{KM_CORE_IT_INVALIDATE_CONTEXT}, // It's a non character key that is not a modifier, so this is a hint that context may no longer be valid
{KM_CORE_IT_EMIT_KEYSTROKE},
{KM_CORE_IT_END}
@@ -143,33 +144,33 @@ void test_basic_rule_matches() {
// 'DE' + 'F' > U+0E04 U+0E05 U+0E06
try_status(km_core_process_event(test_state, KM_CORE_VKEY_D, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_D, KM_CORE_MODIFIER_SHIFT, 'D'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, KM_CORE_DEBUG_FLAG_NOMATCH, {}, {u"", &gp, nullptr, {}, 1}},
km_core_state_debug_item{KM_CORE_DEBUG_END, 0, {}, {u"", nullptr, nullptr, {}, 1}}, // action item will emit a default 'D'
}));
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('D')}},
{KM_CORE_IT_END}
}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_E, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_E, KM_CORE_MODIFIER_SHIFT, 'E'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, KM_CORE_DEBUG_FLAG_NOMATCH, {}, {u"", &gp, nullptr, {}, 1}},
km_core_state_debug_item{KM_CORE_DEBUG_END, 0, {}, {u"", nullptr, nullptr, {}, 1}}, // action item will emit a default 'E'
}));
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('E')}},
{KM_CORE_IT_END}
}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_F, KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_F, KM_CORE_MODIFIER_SHIFT, 'F'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
km_core_state_debug_item{KM_CORE_DEBUG_RULE_ENTER, 0, {}, {u"DE", &gp, &kp, {0xFFFF}}},
@@ -186,7 +187,7 @@ void test_basic_rule_matches() {
bksp_e.backspace.expected_type = KM_CORE_BT_CHAR;
bksp_e.backspace.expected_value = 'E';
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
bksp_e,
bksp_d,
{KM_CORE_IT_CHAR, {0,}, {km_core_usv(u'\u0E04')}},
@@ -213,7 +214,7 @@ void test_multiple_groups() {
// '12' -> 'abc'
try_status(km_core_process_event(test_state, KM_CORE_VKEY_1, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_1, 0, '1'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
@@ -235,7 +236,7 @@ void test_multiple_groups() {
bksp_a.backspace.expected_type = KM_CORE_BT_CHAR;
bksp_a.backspace.expected_value = 'a';
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('a')}},
bksp_a,
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('b')}},
@@ -243,7 +244,7 @@ void test_multiple_groups() {
}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_2, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_2, 0, '2'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
@@ -268,7 +269,7 @@ void test_multiple_groups() {
bksp_b.backspace.expected_type = KM_CORE_BT_CHAR;
bksp_b.backspace.expected_value = 'b';
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
bksp_b,
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('a')}},
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('b')}},
@@ -292,7 +293,7 @@ void test_store_offsets() {
// 'ab' -> 'ex'
try_status(km_core_process_event(test_state, KM_CORE_VKEY_A, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_A, 0, 'a'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
@@ -303,7 +304,7 @@ void test_store_offsets() {
km_core_state_debug_item{KM_CORE_DEBUG_END, 0, {}, {u"", nullptr, nullptr, {}, 4}}, // action item will emit a 'exay'
}));
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('e')}},
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('x')}},
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('a')}},
@@ -312,7 +313,7 @@ void test_store_offsets() {
}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_B, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_B, 0, 'b'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
@@ -341,7 +342,7 @@ void test_store_offsets() {
bksp[2].backspace.expected_value = 'x';
bksp[3].backspace.expected_value = 'e';
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
bksp[0],
bksp[1],
bksp[2],
@@ -366,7 +367,7 @@ void test_set_option() {
// '1' -> set_option
try_status(km_core_process_event(test_state, KM_CORE_VKEY_1, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_1, 0, '1'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
@@ -394,7 +395,7 @@ void test_save_option() {
// '2' -> save_option
try_status(km_core_process_event(test_state, KM_CORE_VKEY_2, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_2, 0, '2'}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
@@ -408,7 +409,7 @@ void test_save_option() {
km_core_action_item action = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action.option = &opt;
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
action,
{KM_CORE_IT_END}
}));
@@ -436,7 +437,7 @@ void test_backspace_markers() {
try_status(km_core_state_debug_set(test_state, 1));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_BKSP, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(debug_items(test_state, {
+ test_assert(debug_items(test_state, {
km_core_state_debug_item{KM_CORE_DEBUG_BEGIN, KM_CORE_DEBUG_FLAG_UNICODE, {KM_CORE_VKEY_BKSP, 0, 0}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}},
km_core_state_debug_item{KM_CORE_DEBUG_GROUP_EXIT, 2, {}, {u"", &gp, nullptr, {}, 2}},
@@ -447,7 +448,7 @@ void test_backspace_markers() {
bksp.backspace.expected_type = KM_CORE_BT_MARKER;
bksp.backspace.expected_value = 1;
- assert(action_items(test_state, {
+ test_assert(action_items(test_state, {
bksp,
bksp,
{KM_CORE_IT_INVALIDATE_CONTEXT},
diff --git a/core/tests/unit/kmnkbd/debug_items.hpp b/core/tests/unit/kmnkbd/debug_items.hpp
index ae5f0e8cbd5..9e281631f6b 100644
--- a/core/tests/unit/kmnkbd/debug_items.hpp
+++ b/core/tests/unit/kmnkbd/debug_items.hpp
@@ -121,20 +121,20 @@ bool operator==(
case KM_CORE_DEBUG_MATCH_EXIT:
case KM_CORE_DEBUG_NOMATCH_ENTER:
case KM_CORE_DEBUG_NOMATCH_EXIT:
- assert(lgp != nullptr);
- assert(rgp != nullptr);
- assert(lgp->dpName != nullptr);
- assert(rgp->dpName != nullptr);
+ test_assert(lgp != nullptr);
+ test_assert(rgp != nullptr);
+ test_assert(lgp->dpName != nullptr);
+ test_assert(rgp->dpName != nullptr);
result = u16cmp(lgp->dpName, rgp->dpName) == 0;
break;
case KM_CORE_DEBUG_RULE_ENTER:
case KM_CORE_DEBUG_RULE_EXIT:
- assert(lgp != nullptr);
- assert(rgp != nullptr);
- assert(lgp->dpName != nullptr);
- assert(rgp->dpName != nullptr);
- assert(lrule != nullptr);
- assert(rrule != nullptr);
+ test_assert(lgp != nullptr);
+ test_assert(rgp != nullptr);
+ test_assert(lgp->dpName != nullptr);
+ test_assert(rgp->dpName != nullptr);
+ test_assert(lrule != nullptr);
+ test_assert(rrule != nullptr);
result = u16cmp(lgp->dpName, rgp->dpName) == 0 &&
lrule->Line == rrule->Line &&
lrule->Key == rrule->Key &&
@@ -143,13 +143,13 @@ bool operator==(
are_store_offsets_equal(lhs.kmx_info.store_offsets, rhs.kmx_info.store_offsets);
break;
case KM_CORE_DEBUG_SET_OPTION:
- assert(loption_store != nullptr && roption_store != nullptr);
+ test_assert(loption_store != nullptr && roption_store != nullptr);
result =
u16cmp(loption_store->dpName, roption_store->dpName) == 0 &&
u16cmp(lhs.kmx_info.option.value, rhs.kmx_info.option.value) == 0;
break;
default:
- assert(false);
+ test_assert(false);
result = false;
}
}
diff --git a/core/tests/unit/kmnkbd/keyboard_api.cpp b/core/tests/unit/kmnkbd/keyboard_api.tests.cpp
similarity index 85%
rename from core/tests/unit/kmnkbd/keyboard_api.cpp
rename to core/tests/unit/kmnkbd/keyboard_api.tests.cpp
index bf232d69afb..07dfb3e4cfc 100644
--- a/core/tests/unit/kmnkbd/keyboard_api.cpp
+++ b/core/tests/unit/kmnkbd/keyboard_api.tests.cpp
@@ -8,8 +8,7 @@
#include "keyman_core.h"
#include "path.hpp"
-
-//#include "keyboard.hpp"
+#include "mock/mock_processor.hpp"
namespace
{
@@ -26,14 +25,11 @@ int main(int, char *[])
km_core_keyboard_key * kb_key_list = nullptr;
km_core_keyboard_imx * kb_imx_list = nullptr;
- try_status(km_core_keyboard_load(test_kb_path.c_str(), &test_kb));
+ test_kb = (km_core_keyboard *)new km::core::mock_processor(test_kb_path);
try_status(km_core_keyboard_get_attrs(test_kb, &kb_attrs));
try_status(km_core_keyboard_get_key_list(test_kb,&kb_key_list));
try_status(km_core_keyboard_get_imx_list(test_kb,&kb_imx_list));
- if (kb_attrs->folder_path != test_kb_path.parent())
- return __LINE__;
-
km_core_keyboard_dispose(test_kb);
km_core_keyboard_key_list_dispose(kb_key_list);
km_core_keyboard_imx_list_dispose(kb_imx_list);
diff --git a/core/tests/unit/kmnkbd/test_kmx_context.cpp b/core/tests/unit/kmnkbd/kmx_context.tests.cpp
similarity index 64%
rename from core/tests/unit/kmnkbd/test_kmx_context.cpp
rename to core/tests/unit/kmnkbd/kmx_context.tests.cpp
index e557daeb3cb..0679c34d1a5 100644
--- a/core/tests/unit/kmnkbd/test_kmx_context.cpp
+++ b/core/tests/unit/kmnkbd/kmx_context.tests.cpp
@@ -21,89 +21,89 @@ using namespace std;
void
test_CharIsDeadkey() {
KMX_Context context;
- assert(context.CharIsDeadkey() == FALSE);
+ test_assert(context.CharIsDeadkey() == FALSE);
context.Reset();
context.Add(u'a');
context.Add(u'a');
context.Add(u'a');
- assert(context.CharIsDeadkey() == FALSE);
+ test_assert(context.CharIsDeadkey() == FALSE);
context.Reset();
context.Add(u'\uffff');
context.Add(u'\u0008');
- assert(context.CharIsDeadkey() == FALSE);
+ test_assert(context.CharIsDeadkey() == FALSE);
context.Reset();
context.Add(u'\uffff');
- assert(context.CharIsDeadkey() == FALSE);
+ test_assert(context.CharIsDeadkey() == FALSE);
context.Reset();
context.Add(u'a');
context.Add(0xD801);
context.Add(0xDC12);
- assert(context.CharIsDeadkey() == FALSE);
+ test_assert(context.CharIsDeadkey() == FALSE);
context.Reset();
context.Add(u'a');
context.Add(u'\uffff');
context.Add(u'\u0008');
- assert(context.CharIsDeadkey() == FALSE);
+ test_assert(context.CharIsDeadkey() == FALSE);
context.Reset();
context.Add(u'a');
context.Add(u'\uffff');
context.Add(u'\u0014');
context.Add(u'\u0001');
- assert(context.CharIsDeadkey() == FALSE);
+ test_assert(context.CharIsDeadkey() == FALSE);
context.Reset();
context.Add(u'\uffff');
context.Add(u'\u0008');
context.Add(u'\u0001');
- assert(context.CharIsDeadkey() == TRUE);
+ test_assert(context.CharIsDeadkey() == TRUE);
context.Reset();
context.Add(u'a');
context.Add(u'\uffff');
context.Add(u'\u0008');
context.Add(u'\u0001');
- assert(context.CharIsDeadkey() == TRUE);
+ test_assert(context.CharIsDeadkey() == TRUE);
}
void test_CharIsSurrogatePair() {
KMX_Context context;
- assert(context.CharIsSurrogatePair() == FALSE);
+ test_assert(context.CharIsSurrogatePair() == FALSE);
context.Reset();
context.Add(u'a');
- assert(context.CharIsSurrogatePair() == FALSE);
+ test_assert(context.CharIsSurrogatePair() == FALSE);
context.Reset();
context.Add(u'a');
context.Add(u'a');
- assert(context.CharIsSurrogatePair() == FALSE);
+ test_assert(context.CharIsSurrogatePair() == FALSE);
context.Reset();
context.Add(u'\uffff');
context.Add(u'\u0008');
context.Add(u'\u0001');
- assert(context.CharIsSurrogatePair() == FALSE);
+ test_assert(context.CharIsSurrogatePair() == FALSE);
context.Reset();
context.Add(0xD801);
- assert(context.CharIsSurrogatePair() == FALSE);
+ test_assert(context.CharIsSurrogatePair() == FALSE);
// We don't support little endian surrogate pairs (cf #5111)
context.Reset();
context.Add(0xDC12);
context.Add(0xD801);
- assert(context.CharIsSurrogatePair() == FALSE);
+ test_assert(context.CharIsSurrogatePair() == FALSE);
context.Reset();
context.Add(0xD801);
context.Add(0xDC12);
- assert(context.CharIsSurrogatePair() == TRUE);
+ test_assert(context.CharIsSurrogatePair() == TRUE);
}
void
@@ -114,54 +114,54 @@ test_Set() {
context.Set(u"abc");
context.Get((KMX_WCHAR*)&buf, bufsize);
- assert_string_equal(buf, u"abc");
+ test_assert_string_equal(buf, u"abc");
context.Reset();
context.Set(u"\uFFFF\u0008\u0001abc");
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"\uFFFF\u0008\u0001abc");
+ test_assert_string_equal(buf, u"\uFFFF\u0008\u0001abc");
// test that we can set MAXCONTEXT-1 characters
context.Reset();
auto text = u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh";
- assert_equal(u16len(text), MAXCONTEXT - 1);
+ test_assert_equal(u16len(text), MAXCONTEXT - 1);
context.Set(text);
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh");
+ test_assert_string_equal(buf, u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh");
// test that setting > MAXCONTEXT-1 characters will only set the last MAXCONTEXT-1 characters
context.Reset();
text = u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl";
- assert_equal(u16len(text), MAXCONTEXT);
+ test_assert_equal(u16len(text), MAXCONTEXT);
context.Set(text);
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl");
- assert_equal(u16len(buf), MAXCONTEXT - 1);
+ test_assert_string_equal(buf, u"bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl");
+ test_assert_equal(u16len(buf), MAXCONTEXT - 1);
// test that setting > MAXCONTEXT-1 characters will set last characters and not split the deadkey
context.Reset();
text = u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi";
- assert_equal(u16len(text), MAXCONTEXT);
+ test_assert_equal(u16len(text), MAXCONTEXT);
context.Set(text);
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi");
- assert_equal(u16len(buf), MAXCONTEXT - 3);
+ test_assert_string_equal(buf, u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi");
+ test_assert_equal(u16len(buf), MAXCONTEXT - 3);
// test that setting the context replaces the previous context
context.Reset();
context.Set(u"\uFFFF\u0008\u0001abc");
context.Set(u"def");
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"def");
+ test_assert_string_equal(buf, u"def");
// test that setting the context completely replaces the existing context
context.Reset();
text = u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh";
- assert_equal(u16len(text), MAXCONTEXT-1);
+ test_assert_equal(u16len(text), MAXCONTEXT-1);
context.Set(text);
context.Set(u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh1");
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh1");
+ test_assert_string_equal(buf, u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh1");
}
void
@@ -172,19 +172,19 @@ test_Add() {
context.Add(u'a');
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"a");
+ test_assert_string_equal(buf, u"a");
context.Reset();
context.Add(0xD801);
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_equal(u16len(buf), 1);
- assert_equal(buf[0], 0xD801);
+ test_assert_equal(u16len(buf), 1);
+ test_assert_equal(buf[0], 0xD801);
context.Reset();
context.Add(0xD801);
context.Add(0xDC12);
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"\U00010412");
+ test_assert_string_equal(buf, u"\U00010412");
context.Reset();
context.Add(u'\uFFFF');
@@ -192,25 +192,25 @@ test_Add() {
context.Add(u'\u0001');
context.Add(u'a');
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"\uFFFF\u0008\u0001a");
+ test_assert_string_equal(buf, u"\uFFFF\u0008\u0001a");
context.Reset();
auto text = u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg";
- assert_equal(u16len(text), MAXCONTEXT - 2);
+ test_assert_equal(u16len(text), MAXCONTEXT - 2);
context.Set(text);
context.Add(u'1');
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg1");
- assert_equal(u16len(buf), MAXCONTEXT - 1);
+ test_assert_string_equal(buf, u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg1");
+ test_assert_equal(u16len(buf), MAXCONTEXT - 1);
context.Reset();
text = u"\uFFFF\u0008\u0001abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh";
- assert_equal(u16len(text), MAXCONTEXT - 1);
+ test_assert_equal(u16len(text), MAXCONTEXT - 1);
context.Set(text);
context.Add(u'1');
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh1");
- assert_equal(u16len(buf), MAXCONTEXT - 3);
+ test_assert_string_equal(buf, u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh1");
+ test_assert_equal(u16len(buf), MAXCONTEXT - 3);
}
void
@@ -222,25 +222,25 @@ test_Delete() {
context.Reset();
context.Delete();
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"");
+ test_assert_string_equal(buf, u"");
context.Reset();
context.Set(u"abc");
context.Delete();
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"ab");
+ test_assert_string_equal(buf, u"ab");
context.Reset();
context.Set(u"ab\U00010412");
context.Delete();
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"ab");
+ test_assert_string_equal(buf, u"ab");
context.Reset();
context.Set(u"ab\uFFFF\u0008\u0001");
context.Delete();
context.Get((KMX_WCHAR *)&buf, bufsize);
- assert_string_equal(buf, u"ab");
+ test_assert_string_equal(buf, u"ab");
}
void
@@ -251,81 +251,81 @@ test_Buf() {
context.Set(u"abc");
auto last = context.Buf(0);
auto p = context.Buf(1);
- assert_equal(p, last - 1);
+ test_assert_equal(p, last - 1);
p = context.Buf(2);
- assert_equal(p, last - 2);
+ test_assert_equal(p, last - 2);
p = context.Buf(3);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.Buf(4);
- assert(p == NULL);
+ test_assert(p == NULL);
p = context.Buf(10);
- assert(p == NULL);
+ test_assert(p == NULL);
p = context.Buf(-1);
- assert_equal(p, last);
+ test_assert_equal(p, last);
context.Reset();
context.Set(u"ab\U00010412");
last = context.Buf(0);
p = context.Buf(1);
- assert_equal(p, last - 2);
+ test_assert_equal(p, last - 2);
p = context.Buf(2);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.Buf(3);
- assert_equal(p, last - 4);
+ test_assert_equal(p, last - 4);
p = context.Buf(4);
- assert(p == NULL);
+ test_assert(p == NULL);
p = context.Buf(5);
- assert(p == NULL);
+ test_assert(p == NULL);
p = context.Buf(10);
- assert(p == NULL);
+ test_assert(p == NULL);
context.Reset();
context.Set(u"ab\uFFFF\u0008\u0001");
last = context.Buf(0);
p = context.Buf(1);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.Buf(2);
- assert_equal(p, last - 4);
+ test_assert_equal(p, last - 4);
p = context.Buf(3);
- assert_equal(p, last - 5);
+ test_assert_equal(p, last - 5);
p = context.Buf(4);
- assert(p == NULL);
+ test_assert(p == NULL);
p = context.Buf(5);
- assert(p == NULL);
+ test_assert(p == NULL);
p = context.Buf(6);
- assert(p == NULL);
+ test_assert(p == NULL);
context.Reset();
auto text = u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk";
- assert_equal(u16len(text), MAXCONTEXT - 1);
+ test_assert_equal(u16len(text), MAXCONTEXT - 1);
context.Set(text);
last = context.Buf(0);
p = context.Buf(1);
- assert_equal(p, last - 1);
+ test_assert_equal(p, last - 1);
p = context.Buf(MAXCONTEXT - 1);
- assert_equal(p, last - (MAXCONTEXT - 1));
+ test_assert_equal(p, last - (MAXCONTEXT - 1));
p = context.Buf(MAXCONTEXT);
- assert(p == NULL);
+ test_assert(p == NULL);
p = context.Buf(MAXCONTEXT + 1);
- assert(p == NULL);
+ test_assert(p == NULL);
}
void
@@ -336,97 +336,97 @@ test_BufMax() {
context.Set(u"abc");
auto last = context.Buf(0);
auto p = context.BufMax(0);
- assert_equal(p, last);
+ test_assert_equal(p, last);
p = context.BufMax(1);
- assert_equal(p, last - 1);
+ test_assert_equal(p, last - 1);
p = context.BufMax(2);
- assert_equal(p, last - 2);
+ test_assert_equal(p, last - 2);
p = context.BufMax(3);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.BufMax(4);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.BufMax(5);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.BufMax(10);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.BufMax(-1);
- assert_equal(p, last);
+ test_assert_equal(p, last);
context.Reset();
context.Set(u"ab\U00010412");
last = context.Buf(0);
p = context.BufMax(0);
- assert_equal(p, last);
+ test_assert_equal(p, last);
p = context.BufMax(1);
- assert_equal(p, last);
+ test_assert_equal(p, last);
p = context.BufMax(2);
- assert_equal(p, last - 2);
+ test_assert_equal(p, last - 2);
p = context.BufMax(3);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.BufMax(4);
- assert_equal(p, last - 4);
+ test_assert_equal(p, last - 4);
p = context.BufMax(5);
- assert_equal(p, last - 4);
+ test_assert_equal(p, last - 4);
p = context.BufMax(10);
- assert_equal(p, last - 4);
+ test_assert_equal(p, last - 4);
context.Reset();
context.Set(u"ab\uFFFF\u0008\u0001");
last = context.Buf(0);
p = context.BufMax(0);
- assert_equal(p, last);
+ test_assert_equal(p, last);
p = context.BufMax(1);
- assert_equal(p, last);
+ test_assert_equal(p, last);
p = context.BufMax(2);
- assert_equal(p, last);
+ test_assert_equal(p, last);
p = context.BufMax(3);
- assert_equal(p, last - 3);
+ test_assert_equal(p, last - 3);
p = context.BufMax(4);
- assert_equal(p, last - 4);
+ test_assert_equal(p, last - 4);
p = context.BufMax(5);
- assert_equal(p, last - 5);
+ test_assert_equal(p, last - 5);
p = context.BufMax(6);
- assert_equal(p, last - 5);
+ test_assert_equal(p, last - 5);
p = context.BufMax(10);
- assert_equal(p, last - 5);
+ test_assert_equal(p, last - 5);
context.Reset();
auto text = u"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk";
- assert_equal(u16len(text), MAXCONTEXT - 1);
+ test_assert_equal(u16len(text), MAXCONTEXT - 1);
context.Set(text);
last = context.Buf(0);
p = context.BufMax(1);
- assert_equal(p, last - 1);
+ test_assert_equal(p, last - 1);
p = context.BufMax(MAXCONTEXT - 1);
- assert_equal(p, last - (MAXCONTEXT - 1));
+ test_assert_equal(p, last - (MAXCONTEXT - 1));
p = context.BufMax(MAXCONTEXT);
- assert_equal(p, last - MAXCONTEXT + 1);
+ test_assert_equal(p, last - MAXCONTEXT + 1);
p = context.BufMax(MAXCONTEXT + 1);
- assert_equal(p, last - MAXCONTEXT + 1);
+ test_assert_equal(p, last - MAXCONTEXT + 1);
}
constexpr const auto help_str = "\
diff --git a/core/tests/unit/kmnkbd/test_kmx_xstring.cpp b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp
similarity index 83%
rename from core/tests/unit/kmnkbd/test_kmx_xstring.cpp
rename to core/tests/unit/kmnkbd/kmx_xstring.tests.cpp
index 157a744ae72..529e2308103 100644
--- a/core/tests/unit/kmnkbd/test_kmx_xstring.cpp
+++ b/core/tests/unit/kmnkbd/kmx_xstring.tests.cpp
@@ -48,32 +48,32 @@ void test_decxstr() {
p_start = (PKMX_WCHAR) C_CODE_ANY(u"\u0002");
p = p_start + 2; // \u0002 in the middle of the otherwise valid UC_SENTINEL CODE_ANY sequence
q = decxstr(p, p_start);
- assert(q == p - 1);
+ test_assert(q == p - 1);
p_start = (PKMX_WCHAR)u"abc" C_CODE_ANY(u"\u0001");
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p-1));
+ test_assert(q == (p-1));
p_start = (PKMX_WCHAR) C_CODE_ANY(u"\u0001");
p = p_start + 3; // nul, i.e. at the end of the valid UC_SENTINEL CODE_ANY sequence
q = decxstr(p, p_start);
- assert(q == p - 3);
+ test_assert(q == p - 3);
p_start = (PKMX_WCHAR)u"abc" C_CODE_ANY(u"\u0001") u"\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p-3));
+ test_assert(q == (p-3));
p_start = (PKMX_WCHAR)u"abc" C_CODE_ANY(u"\u0001") U_1F609_WINKING_FACE;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p-1));
+ test_assert(q == (p-1));
p_start = (PKMX_WCHAR)u"abc" C_CODE_ANY(u"\u0001") U_1F609_WINKING_FACE;
p = find_ptr_to_last_character(p_start)-1;
q = decxstr(p, p_start);
- assert(q == (p-3));
+ test_assert(q == (p-3));
// -------------------------------------------------------------------------------------------------------------------------------------------------------
// even more tests: check for use UC_SENTINEL with F000
@@ -82,47 +82,47 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p-1));
+ test_assert(q == (p-1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000\u0002";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000\u0001\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000\u0002\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000\u0001\u0001d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000\u0002\u0001\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL u"\uF000\u0002\u0001\u0001d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
// -------------------------------------------------------------------------------------------------------------------------------------------------------
// more tests: check if we might end up left of pstart
@@ -131,96 +131,96 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (NULL ));
+ test_assert(q == (NULL ));
p_start = (PKMX_WCHAR)u"\u0001\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"\u0001\u0001\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR) U_UC_SENTINEL U_CODE_ANY;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
// Note: this test puts the pointer into the middle of a valid `UC_SENTINEL CODE_ANY `
// so we should expect it to not be properly understood.
p_start = (PKMX_WCHAR) C_CODE_ANY(u"\u0001");
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR) C_CODE_ANY(u"\u0001") u"\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3));
+ test_assert(q == (p - 3));
p_start = (PKMX_WCHAR)u"\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (NULL));
+ test_assert(q == (NULL));
p_start = (PKMX_WCHAR)u"\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR)u"\u0014\u0014\u0014\u0014\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR) U_UC_SENTINEL u"\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
// 0x14 = CODE_IFOPT which has 3 parameters, so this is an invalid, so
// go back only one char
p_start = (PKMX_WCHAR) U_UC_SENTINEL u"\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR) U_UC_SENTINEL u"\u0014\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0014", u"\u0014", u"\u0014");
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
p_start = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0014", u"\u0014", u"\u0014") u"\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 5));
+ test_assert(q == (p - 5));
p_start = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0014", u"\u0014", u"\u0014") u"\u0014\u0014";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1));
+ test_assert(q == (p - 1));
// -------------------------------------------------------------------------------------------------------------------------------------------------------
// -- differences in pointer movement for new decxstr ----------------------------------------------------------------------------------------------------
@@ -232,25 +232,25 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL U_CODE_EXTENDED u"\u0001" u"\u0001" u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 4) );
+ test_assert(q == (p - 4) );
//runs OK with NEW version of decxstr (with CODE_SWITCH pointer moves 2 ( 3 altogether)
p_start = (PKMX_WCHAR)u"abc" C_CODE_SWITCH(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
// runs OK with NEW version of decxstr (with CODE_CLEARCONTEXT pointer moves 0 ( 1 altogether)
p_start = (PKMX_WCHAR)u"abc" C_CODE_CLEARCONTEXT() u"\u0001d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
// runs OK with OLD version and NEW version of decxstr
p_start = (PKMX_WCHAR)u"abc" C_CODE_CLEARCONTEXT() u"\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 2) );
+ test_assert(q == (p - 2) );
// ---------------------------------------------------------------------------------------
// ---- character
@@ -260,7 +260,7 @@ void test_decxstr() {
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
// ---------------------------------------------------------------------------------------
// ---- p <= pstart
@@ -269,12 +269,12 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"abc";
p = find_ptr_to_last_character(p_start)-5;
q = decxstr(p, p_start);
- assert(q == (NULL) );
+ test_assert(q == (NULL) );
p_start = (PKMX_WCHAR)u"a";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (NULL) );
+ test_assert(q == (NULL) );
// ---------------------------------------------------------------------------------------
// ---- p= UC_SENTINEL_EXTENDED
@@ -283,22 +283,22 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"abc" C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" U_CODE_EXTENDEDEND);
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" u"\u0008" U_CODE_EXTENDEDEND);
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" U_CODE_EXTENDEDEND) u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 8) );
+ test_assert(q == (p - 8) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" u"\u0008" U_CODE_EXTENDEDEND) u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 11) );
+ test_assert(q == (p - 11) );
// ---------------------------------------------------------------------------------------
// ---- Surrogate Pair
@@ -307,32 +307,32 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"abc" U_1F609_WINKING_FACE;
p = find_ptr_to_last_character(p_start)-1;
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" U_1F609_WINKING_FACE;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" U_1F609_WINKING_FACE u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 2) );
+ test_assert(q == (p - 2) );
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL U_1F609_WINKING_FACE;
p = find_ptr_to_last_character(p_start)-1;
q = decxstr(p, p_start);
- assert(q == (p-1));
+ test_assert(q == (p-1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL U_1F609_WINKING_FACE;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p-1));
+ test_assert(q == (p-1));
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL U_1F609_WINKING_FACE u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p-2));
+ test_assert(q == (p-2));
// ---------------------------------------------------------------------------------------
// ---- CODE_
// ---------------------------------------------------------------------------------------
@@ -351,16 +351,16 @@ void test_decxstr() {
p_start = (PKMX_WCHAR) str.c_str();
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1)); // this is in the middle of the sequence so it should always go back a single unit
+ test_assert(q == (p - 1)); // this is in the middle of the sequence so it should always go back a single unit
str.append(u"a");
p_start = (PKMX_WCHAR)str.c_str();
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
if (size < 0) {
- assert(q == (p - 1)); // this is in the middle of the sequence so it should always go back a single unit
+ test_assert(q == (p - 1)); // this is in the middle of the sequence so it should always go back a single unit
} else {
- assert(q == (p - size - 2)); /* UC_SENTINEL + code + (CODE__SIZE = number of params) */
+ test_assert(q == (p - size - 2)); /* UC_SENTINEL + code + (CODE__SIZE = number of params) */
}
}
@@ -371,87 +371,87 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"abc" C_CODE_ANY(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_INDEX(u"\u0001", u"\u0001d");
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 4) );
+ test_assert(q == (p - 4) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_CONTEXT() u"\u0001d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_NUL() u"\u0001d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_USE(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_RETURN() u"\u0001d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_BEEP() u"\u0001d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_DEADKEY(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_CALL(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_CONTEXTEX(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_NOTANY(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_SETOPT(u"\u0001", u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 4) );
+ test_assert(q == (p - 4) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_IFOPT(u"\u0001", u"\u0001", u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 5) );
+ test_assert(q == (p - 5) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_SAVEOPT(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_RESETOPT(u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 3) );
+ test_assert(q == (p - 3) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_IFSYSTEMSTORE(u"\u0001", u"\u0001", u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 5) );
+ test_assert(q == (p - 5) );
p_start = (PKMX_WCHAR)u"abc" C_CODE_SETSYSTEMSTORE(u"\u0001", u"\u0001") u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 4) );
+ test_assert(q == (p - 4) );
// ---------------------------------------------------------------------------------------
// ---- other
@@ -460,38 +460,38 @@ void test_decxstr() {
p_start = (PKMX_WCHAR)u"abc" C_CODE_INDEX(u"d", u"e") u"f\u0001";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"abc" U_UC_SENTINEL;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
// pointer in the middle of a surrogate pair, so beware!
p_start = (PKMX_WCHAR) U_UC_SENTINEL U_1F609_WINKING_FACE;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR)u"\u0014d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR) U_UC_SENTINEL;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == NULL );
+ test_assert(q == NULL );
p_start = (PKMX_WCHAR) U_UC_SENTINEL u"d";
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == (p - 1) );
+ test_assert(q == (p - 1) );
p_start = (PKMX_WCHAR) U_UC_SENTINEL U_UC_SENTINEL;
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
- assert(q == p - 1 );
+ test_assert(q == p - 1 );
}
@@ -508,27 +508,27 @@ void test_decxstr() {
// --- Test for empty string ------------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) u"\0";
q = incxstr(p);
- assert(q == p);
+ test_assert(q == p);
// --- Test for character ---------------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) u"\u1234";
q = incxstr(p);
- assert(q == p+1);
+ test_assert(q == p+1);
// --- Test for surrogate pair ----------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p+2);
+ test_assert(q == p+2);
// --- Test for one -----------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR)u"\u0012";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF only -------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL;
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL WITHOUT \0 ----------------------------------------------------------------------------------------------------------------------
@@ -537,102 +537,102 @@ void test_decxstr() {
// --- Test for FFFF +CODE_INDEX --------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_INDEX(u"\u0002", u"\u0001");
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_USE ----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_USE(u"\u0001");
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_DEADKEY ------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001");
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF CODE_EXTENDED --------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" U_CODE_EXTENDEDEND);
q = incxstr(p);
- assert(q == p + 9);
+ test_assert(q == p + 9);
// --- Test for FFFF +CODE_CLEARCONTEXT ------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CLEARCONTEXT() u"\u0001";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF +CODE_CALL ----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CALL(u"\u0001");
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_CONTEXTEX ---------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CONTEXTEX(u"\u0001");
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_IFOPT -------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0002", u"\u0002", u"\u0001");
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +CODE_IFSYSTEMSTORE ------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_IFSYSTEMSTORE(u"\u0002", u"\u0002", u"\u0001");
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +CODE_SETOPT ----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SETOPT(u"\u0002", u"\u0001");
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_SETSYSTEMRESTORE ---------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SETSYSTEMSTORE(u"\u0002", u"\u0001");
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_RESETOPT -----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_RESETOPT(u"\u0001");
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_SAVEOPT -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SAVEOPT(u"\u0001");
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --- Test for FFFF +default ----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL();
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF + CODE_ANY -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_ANY(u"\u0001");
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --- Test for FFFF + CODE_CONTEXT -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CONTEXT() u"\u0001";
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_RETURN -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_RETURN() u"\u0001";
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_BEEP -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_BEEP() u"\u0001";
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_SWITCH -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001");
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --- Test for FFFF + NOTANY -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NOTANY(u"\u0001");
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL WITH \0 AT DIFFERENT POSITIONS --------------------------------------------------------------------------------------------------
@@ -641,52 +641,52 @@ void test_decxstr() {
// --- Test for FFFF + control (earlier p+1) with \0 after first position --------------- unit test failed with old version of incxstr() -----
p = (PKMX_WCHAR) U_UC_SENTINEL u"\0\u0008\u0001";
q = incxstr(p);
- assert(q == p+1);
+ test_assert(q == p+1);
// --- Test for FFFF +control (earlier p+1) with \0 after second position --------- unit test failed with old version of incxstr() -----
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\0") u"\u0001";
q = incxstr(p);
- assert(q == p+2);
+ test_assert(q == p+2);
// --- Test for FFFF +control (earlier p+1) with \0 after third position ----- unit test failed with old version of incxstr() -----
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"\0";
q = incxstr(p);
- assert(q == p+3)
+ test_assert(q == p+3)
// --- Test for FFFF +control (earlier p+2) with \0 after fourth position ----- unit test failed with old version of incxstr() ----
p = (PKMX_WCHAR) C_CODE_INDEX(u"\u0001", u"\u0001") u"\0";
q = incxstr(p);
- assert(q == p+4);
+ test_assert(q == p+4);
// --- Test for FFFF +control (earlier p+3) with \0 after fifth position ----- unit test failed with old version of incxstr() ---------
p = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0001", u"\u0001", u"\u0001") u"\0";
q = incxstr(p);
- assert(q == p+5);
+ test_assert(q == p+5);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 6. position ----- unit test failed with old version of incxstr() -----
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\0" u"\u0005" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND);
q = incxstr(p);
- assert(q == p + 6);
+ test_assert(q == p + 6);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 7. position ----- unit test failed with old version of incxstr()
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\0" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND);
q = incxstr(p);
- assert(q == p + 7);
+ test_assert(q == p + 7);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 8. position ----- unit test failed with old version of incxstr() ----------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\0" u"\u0007" U_CODE_EXTENDEDEND);
q = incxstr(p);
- assert(q == p + 8);
+ test_assert(q == p + 8);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 9. position ----- unit test failed with old version of incxstr() ---
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" u"\0" U_CODE_EXTENDEDEND);
q = incxstr(p);
- assert(q == p + 9);
+ test_assert(q == p + 9);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 10. position ----- unit test failed with old version of incxstr() -----------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND) u"\0";
q = incxstr(p);
- assert(q == p + 10);
+ test_assert(q == p + 10);
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL, INCOMPLETE & UNUSUAL SEQUENCES--------------------------------------------------------------------------------------------------
@@ -695,22 +695,22 @@ void test_decxstr() {
// --- Test for FFFF + \0 --------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL u"\0";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +one character ------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL u"\u0062";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +one -----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL();
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF + one + character -------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL() u"\u0062";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
//-------------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------------
@@ -725,31 +725,31 @@ void test_decxstr() {
//---------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) u"\0\u1234\u2468";
q = incxstr(p);
- assert(q == p);
+ test_assert(q == p);
// --- Test for character
// ---------------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR)u"\u1234\u1234\u2468";
q = incxstr(p);
- assert(q == p + 1);;
+ test_assert(q == p + 1);;
// --- Test for surrogate pair
// ----------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_1F609_WINKING_FACE u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for one
// -----------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR)u"\u0012\u1234\u2468";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF only
// -------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL WITHOUT \0
@@ -760,115 +760,115 @@ void test_decxstr() {
// --------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_INDEX(u"\u0002", u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_USE
// ----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_USE(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_DEADKEY
// ------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF CODE_EXTENDED
// --------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" U_CODE_EXTENDEDEND) u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 9);
+ test_assert(q == p + 9);
// --- Test for FFFF +CODE_CLEARCONTEXT
// ------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CLEARCONTEXT() u"\u0001\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF +CODE_CALL
// ----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CALL(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_CONTEXTEX
// ---------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CONTEXTEX(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_IFOPT
// -------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0002", u"\u0002", u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +CODE_IFSYSTEMSTORE
// ------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_IFSYSTEMSTORE(u"\u0002", u"\u0002", u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +CODE_SETOPT
// ----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SETOPT(u"\u0002", u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_SETSYSTEMRESTORE
// ---------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SETSYSTEMSTORE(u"\u0002", u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_RESETOPT
// -----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_RESETOPT(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_SAVEOPT
// -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SAVEOPT(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +default
// ----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL() u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF + CODE_ANY -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_ANY(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --- Test for FFFF + CODE_CONTEXT -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CONTEXT() u"\u0001\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_RETURN -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_RETURN() u"\u0001\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_BEEP -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_BEEP() u"\u0001\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_SWITCH -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --- Test for FFFF + NOTANY -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NOTANY(u"\u0001") u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL WITH \0 AT DIFFERENT POSITIONS
@@ -879,61 +879,61 @@ void test_decxstr() {
// incxstr() -----
p = (PKMX_WCHAR) U_UC_SENTINEL u"\0\u0008\u0001\u1234\u2468";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +control (earlier p+1) with \0 after second position --------- unit test failed with old version of
// incxstr() -----
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\0") u"\u0001\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF +control (earlier p+1) with \0 after third position ----- unit test failed with old version of incxstr()
// -----
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"\0\u1234\u2468";
q = incxstr(p);
- assert(q == p + 3)
+ test_assert(q == p + 3)
// --- Test for FFFF +control (earlier p+2) with \0 after fourth position ----- unit test failed with old version of
// incxstr() ----
p = (PKMX_WCHAR) C_CODE_INDEX(u"\u0001", u"\u0001") u"\0\u1234\u2468";
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +control (earlier p+3) with \0 after fifth position ----- unit test failed with old version of incxstr()
// ---------
p = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0001", u"\u0001", u"\u0001") u"\0\u1234\u2468";
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 6. position ----- unit test failed with old
// version of incxstr() -----
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\0" u"\u0005" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND) u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 6);
+ test_assert(q == p + 6);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 7. position ----- unit test failed with old
// version of incxstr()
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\0" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND) u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 7);
+ test_assert(q == p + 7);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 8. position ----- unit test failed with old
// version of incxstr() ----------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\0" u"\u0007" U_CODE_EXTENDEDEND) u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 8);
+ test_assert(q == p + 8);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 9. position ----- unit test failed with old
// version of incxstr() ---
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" u"\0" U_CODE_EXTENDEDEND) u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 9);
+ test_assert(q == p + 9);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 10. position ----- unit test failed with old
// version of incxstr() -----------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND) u"\0\u1234\u2468";
q = incxstr(p);
- assert(q == p + 10);
+ test_assert(q == p + 10);
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL, INCOMPLETE & UNUSUAL SEQUENCES--------------------------------------------------------------------------------------------------
@@ -943,25 +943,25 @@ void test_decxstr() {
// --------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL u"\0\u1234\u2468";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +one character
// ------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL u"\u0062\u1234\u2468";
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +one
// -----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL() u"\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF + one + character
// -------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL() u"\u0062\u1234\u2468";
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
//-------------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------------
@@ -976,31 +976,31 @@ void test_decxstr() {
//------------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR)u"\0" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p);
+ test_assert(q == p);
// --- Test for character
// ---------------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR)u"\u1234" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for surrogate pair
// ----------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_1F609_WINKING_FACE U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for one
// -----------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR)u"\u0012" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF only
// -------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL WITHOUT \0
@@ -1011,115 +1011,115 @@ void test_decxstr() {
// --------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_INDEX(u"\u0002", u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_USE
// ----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_USE(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_DEADKEY
// ------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF CODE_EXTENDED
// --------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" U_CODE_EXTENDEDEND) U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 9);
+ test_assert(q == p + 9);
// --- Test for FFFF +CODE_CLEARCONTEXT
// ------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CLEARCONTEXT() u"\u0001" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF +CODE_CALL
// ----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CALL(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_CONTEXTEX
// ---------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CONTEXTEX(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_IFOPT
// -------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0002", u"\u0002", u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +CODE_IFSYSTEMSTORE
// ------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_IFSYSTEMSTORE(u"\u0002", u"\u0002", u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +CODE_SETOPT
// ----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SETOPT(u"\u0002", u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_SETSYSTEMRESTORE
// ---------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SETSYSTEMSTORE(u"\u0002", u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +CODE_RESETOPT
// -----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_RESETOPT(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +CODE_SAVEOPT
// -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SAVEOPT(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3);
+ test_assert(q == p + 3);
// --- Test for FFFF +default
// ----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL() U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF + CODE_ANY -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_ANY(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --- Test for FFFF + CODE_CONTEXT -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_CONTEXT() u"\u0001" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_RETURN -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_RETURN() u"\u0001" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_BEEP -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_BEEP() u"\u0001" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2 );
+ test_assert(q == p + 2 );
// --- Test for FFFF + CODE_SWITCH -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --- Test for FFFF + NOTANY -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NOTANY(u"\u0001") U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3 );
+ test_assert(q == p + 3 );
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL WITH \0 AT DIFFERENT POSITIONS
@@ -1130,61 +1130,61 @@ void test_decxstr() {
// with old version of incxstr() -----
p = (PKMX_WCHAR) U_UC_SENTINEL u"\0\u0008\u0001" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +control (earlier p+1) with \0 after second position --------- unit test failed with
// old version of incxstr() -----
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\0") u"\u0001" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF +control (earlier p+1) with \0 after third position ----- unit test failed with old
// version of incxstr() -----
p = (PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"\0" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 3)
+ test_assert(q == p + 3)
// --- Test for FFFF +control (earlier p+2) with \0 after fourth position ----- unit test failed with
// old version of incxstr() ----
p = (PKMX_WCHAR) C_CODE_INDEX(u"\u0001", u"\u0001") u"\0" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 4);
+ test_assert(q == p + 4);
// --- Test for FFFF +control (earlier p+3) with \0 after fifth position ----- unit test failed with old
// version of incxstr() ---------
p = (PKMX_WCHAR) C_CODE_IFOPT(u"\u0001", u"\u0001", u"\u0001") u"\0" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 5);
+ test_assert(q == p + 5);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 6. position ----- unit test
// failed with old version of incxstr() -----
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\0" u"\u0005" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND) U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 6);
+ test_assert(q == p + 6);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 7. position ----- unit test
// failed with old version of incxstr()
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\0" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND) U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 7);
+ test_assert(q == p + 7);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 8. position ----- unit test
// failed with old version of incxstr() ----------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\0" u"\u0007" U_CODE_EXTENDEDEND) U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 8);
+ test_assert(q == p + 8);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 9. position ----- unit test
// failed with old version of incxstr() ---
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" u"\0" U_CODE_EXTENDEDEND) U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 9);
+ test_assert(q == p + 9);
// --- Test for FFFF +control CODE_EXTENDED ----- (earlier p+n) with \0 after 10. position ----- unit test
// failed with old version of incxstr() -----------
p = (PKMX_WCHAR) C_CODE_EXTENDED(u"\u0001" u"\u0002" u"\u0003" u"\u0004" u"\u0005" u"\u0006" u"\u0007" U_CODE_EXTENDEDEND) u"\0" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 10);
+ test_assert(q == p + 10);
// --------------------------------------------------------------------------------------------------------------------------------------------------
// ---- UC_SENTINEL, INCOMPLETE & UNUSUAL SEQUENCES--------------------------------------------------------------------------------------------------
@@ -1194,46 +1194,46 @@ void test_decxstr() {
// --------------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL u"\0" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +one character
// ------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) U_UC_SENTINEL u"\u0062" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 1);
+ test_assert(q == p + 1);
// --- Test for FFFF +one
// -----------------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL() U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
// --- Test for FFFF + one + character
// -------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_NUL() u"\u0062" U_1F609_WINKING_FACE;
q = incxstr(p);
- assert(q == p + 2);
+ test_assert(q == p + 2);
}
void test_xstrlen() {
- assert_equal(xstrlen((PKMX_WCHAR)u""), 0);
- assert_equal(xstrlen((PKMX_WCHAR)u"1"), 1);
- assert_equal(xstrlen((PKMX_WCHAR)u"1234567890"), 10);
- assert_equal(xstrlen((PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"a"), 2);
- assert_equal(xstrlen((PKMX_WCHAR) C_CODE_IFOPT(u"\u0001", u"\u0002", u"\u0003") u"a"), 2);
- assert_equal(xstrlen((PKMX_WCHAR) C_CODE_IFSYSTEMSTORE(u"\u0001", u"\u0002", u"\u0003") u"a"), 2);
- assert_equal(xstrlen((PKMX_WCHAR) U_1F609_WINKING_FACE u"a"), 2);
+ test_assert_equal(xstrlen((PKMX_WCHAR)u""), 0);
+ test_assert_equal(xstrlen((PKMX_WCHAR)u"1"), 1);
+ test_assert_equal(xstrlen((PKMX_WCHAR)u"1234567890"), 10);
+ test_assert_equal(xstrlen((PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"a"), 2);
+ test_assert_equal(xstrlen((PKMX_WCHAR) C_CODE_IFOPT(u"\u0001", u"\u0002", u"\u0003") u"a"), 2);
+ test_assert_equal(xstrlen((PKMX_WCHAR) C_CODE_IFSYSTEMSTORE(u"\u0001", u"\u0002", u"\u0003") u"a"), 2);
+ test_assert_equal(xstrlen((PKMX_WCHAR) U_1F609_WINKING_FACE u"a"), 2);
}
void
test_xstrlen_ignoreifopt() {
- assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR)u""), 0);
- assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR)u"1"), 1);
- assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR)u"1234567890"), 10);
- assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"a"), 2);
- assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) C_CODE_IFOPT(u"\u0001", u"\u0002", u"\u0003") u"a"), 1);
- assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) C_CODE_IFSYSTEMSTORE(u"\u0001", u"\u0002", u"\u0003") u"a"), 1);
- assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) U_1F609_WINKING_FACE u"a"), 2);
+ test_assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR)u""), 0);
+ test_assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR)u"1"), 1);
+ test_assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR)u"1234567890"), 10);
+ test_assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) C_CODE_DEADKEY(u"\u0001") u"a"), 2);
+ test_assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) C_CODE_IFOPT(u"\u0001", u"\u0002", u"\u0003") u"a"), 1);
+ test_assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) C_CODE_IFSYSTEMSTORE(u"\u0001", u"\u0002", u"\u0003") u"a"), 1);
+ test_assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) U_1F609_WINKING_FACE u"a"), 2);
}
void
@@ -1241,32 +1241,32 @@ test_utf32() {
std::cout << "== " << __FUNCTION__ << std::endl;
const KMX_DWORD u295 = 0x0127; // ħ
- assert(Uni_IsBMP(u295));
+ test_assert(Uni_IsBMP(u295));
const char16_t hmaqtugha = Uni_UTF32BMPToUTF16(u295);
- assert(hmaqtugha == 0x0127);
+ test_assert(hmaqtugha == 0x0127);
char16_single c0;
int l0 = Utf32CharToUtf16(u295, c0);
- assert(l0 == 1);
- assert(c0.ch[0] == 0x0127);
- assert(c0.ch[1] == 0);
+ test_assert(l0 == 1);
+ test_assert(c0.ch[0] == 0x0127);
+ test_assert(c0.ch[1] == 0);
std::u16string s0 = std::u16string(c0.ch, l0);
- assert(s0 == std::u16string(u"ħ"));
- assert_equal(s0.at(0), 0x0127);
+ test_assert(s0 == std::u16string(u"ħ"));
+ test_assert_equal(s0.at(0), 0x0127);
const KMX_DWORD scat = 0x0001F640; // 🙀
- assert(!Uni_IsBMP(scat));
+ test_assert(!Uni_IsBMP(scat));
char16_single c1;
int l1 = Utf32CharToUtf16(scat, c1);
- assert(l1 == 2);
- assert_equal(c1.ch[0], 0xD83D);
- assert_equal(c1.ch[1], 0xDE40);
- assert_equal(c1.ch[2], 0);
+ test_assert(l1 == 2);
+ test_assert_equal(c1.ch[0], 0xD83D);
+ test_assert_equal(c1.ch[1], 0xDE40);
+ test_assert_equal(c1.ch[2], 0);
std::u16string s1 = std::u16string(c1.ch, l1);
- assert_equal(s1.at(0), 0xD83D);
- assert_equal(s1.at(1), 0xDE40);
- assert(s1 == std::u16string(u"🙀"));
+ test_assert_equal(s1.at(0), 0xD83D);
+ test_assert_equal(s1.at(1), 0xDE40);
+ test_assert(s1 == std::u16string(u"🙀"));
}
void
@@ -1275,23 +1275,23 @@ test_u16string_to_u32string() {
// normal cases
{
const std::u32string str = u16string_to_u32string(u"");
- assert_equal(str.length(), 0);
+ test_assert_equal(str.length(), 0);
}
{
const std::u32string str = u16string_to_u32string(u"e");
- assert_equal(str.length(), 1);
- assert_equal(str.at(0), 0x0065);
+ test_assert_equal(str.length(), 1);
+ test_assert_equal(str.at(0), 0x0065);
}
{
const std::u32string str = u16string_to_u32string(u"🙀");
- assert_equal(str.length(), 1);
- assert_equal(str.at(0), 0x0001F640);
+ test_assert_equal(str.length(), 1);
+ test_assert_equal(str.at(0), 0x0001F640);
}
{
const std::u32string str = u16string_to_u32string(u"Ω🙀");
- assert_equal(str.length(), 2);
- assert_equal(str.at(0), u'Ω');
- assert_equal(str.at(1), 0x0001F640);
+ test_assert_equal(str.length(), 2);
+ test_assert_equal(str.at(0), u'Ω');
+ test_assert_equal(str.at(1), 0x0001F640);
}
// error cases
@@ -1299,33 +1299,33 @@ test_u16string_to_u32string() {
std::u16string half_cat;
half_cat.push_back(0xD83D); // mismatched lead surrogate
const std::u32string str = u16string_to_u32string(half_cat);
- assert_equal(str.length(), 1);
- assert_equal(str.at(0), 0xFFFD);
+ test_assert_equal(str.length(), 1);
+ test_assert_equal(str.at(0), 0xFFFD);
}
{
std::u16string half_cat;
half_cat.push_back(0xD83D); // mismatched lead surrogate
half_cat.push_back(u'Ω'); // with following text
const std::u32string str = u16string_to_u32string(half_cat);
- assert_equal(str.length(), 2);
- assert_equal(str.at(0), 0xFFFD);
- assert_equal(str.at(1), u'Ω');
+ test_assert_equal(str.length(), 2);
+ test_assert_equal(str.at(0), 0xFFFD);
+ test_assert_equal(str.at(1), u'Ω');
}
{
std::u16string half_cat;
half_cat.push_back(0xDE40); // mismatched trail surrogate
const std::u32string str = u16string_to_u32string(half_cat);
- assert_equal(str.length(), 1);
- assert_equal(str.at(0), 0xFFFD);
+ test_assert_equal(str.length(), 1);
+ test_assert_equal(str.at(0), 0xFFFD);
}
{
std::u16string no_cat;
no_cat.push_back(0xDE40);
no_cat.push_back(0xD83D);
const std::u32string str = u16string_to_u32string(no_cat);
- assert_equal(str.length(), 2);
- assert_equal(str.at(0), 0xFFFD);
- assert_equal(str.at(1), 0xFFFD);
+ test_assert_equal(str.length(), 2);
+ test_assert_equal(str.at(0), 0xFFFD);
+ test_assert_equal(str.at(1), 0xFFFD);
}
}
@@ -1336,75 +1336,75 @@ test_u32string_to_u16string() {
// normal cases
{
const auto str = u32string_to_u16string(U"");
- assert_equal(str.length(), 0);
+ test_assert_equal(str.length(), 0);
}
{
const auto str = u32string_to_u16string(U"e");
- assert_equal(str.length(), 1);
- assert_equal(str.at(0), 0x0065);
+ test_assert_equal(str.length(), 1);
+ test_assert_equal(str.at(0), 0x0065);
}
{
const auto str = u32string_to_u16string(U"🙀");
- assert_equal(str.length(), 2);
- assert_equal(str.at(0), 0xD83D);
- assert_equal(str.at(1), 0xDE40);
+ test_assert_equal(str.length(), 2);
+ test_assert_equal(str.at(0), 0xD83D);
+ test_assert_equal(str.at(1), 0xDE40);
}
{
const auto str = u32string_to_u16string(U"Ω🙀");
- assert_equal(str.length(), 3);
- assert_equal(str.at(0), u'Ω');
- assert_equal(str.at(1), 0xD83D);
- assert_equal(str.at(2), 0xDE40);
+ test_assert_equal(str.length(), 3);
+ test_assert_equal(str.at(0), u'Ω');
+ test_assert_equal(str.at(1), 0xD83D);
+ test_assert_equal(str.at(2), 0xDE40);
}
}
void test_is_valid() {
std::cout << "== " << __FUNCTION__ << std::endl;
// valid
- assert_equal(Uni_IsValid(0x0000), true);
- assert_equal(Uni_IsValid(0x0127), true);
- assert_equal(Uni_IsValid(U'🙀'), true);
+ test_assert_equal(Uni_IsValid(0x0000), true);
+ test_assert_equal(Uni_IsValid(0x0127), true);
+ test_assert_equal(Uni_IsValid(U'🙀'), true);
// invalid
- assert_equal(Uni_IsValid(0xDECAFBAD), false); // out of range
- assert_equal(Uni_IsValid(0x566D4128), false);
- assert_equal(Uni_IsValid(0xFFFF), false); // nonchar
- assert_equal(Uni_IsValid(0xFFFE), false); // nonchar
- assert_equal(Uni_IsValid(0x10FFFF), false); // nonchar
- assert_equal(Uni_IsValid(0x10FFFE), false); // nonchar
- assert_equal(Uni_IsValid(0x01FFFF), false); // nonchar
- assert_equal(Uni_IsValid(0x01FFFE), false); // nonchar
- assert_equal(Uni_IsValid(0x02FFFF), false); // nonchar
- assert_equal(Uni_IsValid(0x02FFFE), false); // nonchar
- assert_equal(Uni_IsValid(0xFDD1), false); // nonchar
- assert_equal(Uni_IsValid(0xFDD0), false); // nonchar
+ test_assert_equal(Uni_IsValid(0xDECAFBAD), false); // out of range
+ test_assert_equal(Uni_IsValid(0x566D4128), false);
+ test_assert_equal(Uni_IsValid(0xFFFF), false); // nonchar
+ test_assert_equal(Uni_IsValid(0xFFFE), false); // nonchar
+ test_assert_equal(Uni_IsValid(0x10FFFF), false); // nonchar
+ test_assert_equal(Uni_IsValid(0x10FFFE), false); // nonchar
+ test_assert_equal(Uni_IsValid(0x01FFFF), false); // nonchar
+ test_assert_equal(Uni_IsValid(0x01FFFE), false); // nonchar
+ test_assert_equal(Uni_IsValid(0x02FFFF), false); // nonchar
+ test_assert_equal(Uni_IsValid(0x02FFFE), false); // nonchar
+ test_assert_equal(Uni_IsValid(0xFDD1), false); // nonchar
+ test_assert_equal(Uni_IsValid(0xFDD0), false); // nonchar
// positive range test
- assert_equal(Uni_IsValid(0x100000, 0x10FFFD), true);
- assert_equal(Uni_IsValid(0x10, 0x20), true);
- assert_equal(Uni_IsValid(0x100000, 0x10FFFD), true);
+ test_assert_equal(Uni_IsValid(0x100000, 0x10FFFD), true);
+ test_assert_equal(Uni_IsValid(0x10, 0x20), true);
+ test_assert_equal(Uni_IsValid(0x100000, 0x10FFFD), true);
// all valid ranges in BMP
- assert_equal(Uni_IsValid(0x0000, 0xD7FF), true);
- assert_equal(Uni_IsValid(0xD800, 0xDFFF), false);
- assert_equal(Uni_IsValid(0xE000, 0xFDCF), true);
- assert_equal(Uni_IsValid(0xFDD0, 0xFDEF), false);
- assert_equal(Uni_IsValid(0xFDF0, 0xFDFF), true);
- assert_equal(Uni_IsValid(0xFDF0, 0xFFFD), true);
+ test_assert_equal(Uni_IsValid(0x0000, 0xD7FF), true);
+ test_assert_equal(Uni_IsValid(0xD800, 0xDFFF), false);
+ test_assert_equal(Uni_IsValid(0xE000, 0xFDCF), true);
+ test_assert_equal(Uni_IsValid(0xFDD0, 0xFDEF), false);
+ test_assert_equal(Uni_IsValid(0xFDF0, 0xFDFF), true);
+ test_assert_equal(Uni_IsValid(0xFDF0, 0xFFFD), true);
// negative range test
- assert_equal(Uni_IsValid(0, 0x10FFFF), false); // ends with nonchar
- assert_equal(Uni_IsValid(0, 0x10FFFD), false); // contains lots o' nonchars
- assert_equal(Uni_IsValid(0x20, 0x10), false); // swapped
- assert_equal(Uni_IsValid(0xFDEF, 0xFDF0), false); // just outside range
- assert_equal(Uni_IsValid(0x0000, 0x010000), false); // crosses noncharacter plane boundary and other stuff
- assert_equal(Uni_IsValid(0x010000, 0x020000), false); // crosses noncharacter plane boundary
- assert_equal(Uni_IsValid(0x0000, 0xFFFF), false); // crosses other BMP prohibited and plane boundary
- assert_equal(Uni_IsValid(0x0000, 0xFFFD), false); // crosses other BMP prohibited
- assert_equal(Uni_IsValid(0x0000, 0xE000), false); // crosses surrogate space
- assert_equal(Uni_IsValid(0x0000, 0x20FFFF), false); // out of bounds
- assert_equal(Uni_IsValid(0x10FFFD, 0x20FFFF), false); // out of bounds
+ test_assert_equal(Uni_IsValid(0, 0x10FFFF), false); // ends with nonchar
+ test_assert_equal(Uni_IsValid(0, 0x10FFFD), false); // contains lots o' nonchars
+ test_assert_equal(Uni_IsValid(0x20, 0x10), false); // swapped
+ test_assert_equal(Uni_IsValid(0xFDEF, 0xFDF0), false); // just outside range
+ test_assert_equal(Uni_IsValid(0x0000, 0x010000), false); // crosses noncharacter plane boundary and other stuff
+ test_assert_equal(Uni_IsValid(0x010000, 0x020000), false); // crosses noncharacter plane boundary
+ test_assert_equal(Uni_IsValid(0x0000, 0xFFFF), false); // crosses other BMP prohibited and plane boundary
+ test_assert_equal(Uni_IsValid(0x0000, 0xFFFD), false); // crosses other BMP prohibited
+ test_assert_equal(Uni_IsValid(0x0000, 0xE000), false); // crosses surrogate space
+ test_assert_equal(Uni_IsValid(0x0000, 0x20FFFF), false); // out of bounds
+ test_assert_equal(Uni_IsValid(0x10FFFD, 0x20FFFF), false); // out of bounds
}
diff --git a/core/tests/unit/kmnkbd/meson.build b/core/tests/unit/kmnkbd/meson.build
index 7285b9bf117..c108ae5b83d 100644
--- a/core/tests/unit/kmnkbd/meson.build
+++ b/core/tests/unit/kmnkbd/meson.build
@@ -16,18 +16,18 @@ endif
local_defns = ['-DKM_CORE_LIBRARY_STATIC']
tests = [
- ['action-api', 'action_api.cpp'],
- ['action-set-api', 'action_set_api.cpp'],
- ['context-api', 'context_api.cpp'],
- ['keyboard-api', 'keyboard_api.cpp'],
- ['options-api', 'options_api.cpp'],
- ['state-api', 'state_api.cpp'],
- ['state-context-api', 'state_context_api.cpp'],
- ['debug-api', 'debug_api.cpp'],
- ['kmx_xstring', 'test_kmx_xstring.cpp'],
- ['kmx_context', 'test_kmx_context.cpp'],
- ['test_actions_normalize', 'test_actions_normalize.cpp'],
- ['test_actions_get_api', 'test_actions_get_api.cpp'],
+ ['action-api-tests', 'action_api.tests.cpp'],
+ ['action-set-api-tests', 'action_set_api.tests.cpp'],
+ ['context-api-tests', 'context_api.tests.cpp'],
+ ['keyboard-api-tests', 'keyboard_api.tests.cpp'],
+ ['options-api-tests', 'options_api.tests.cpp'],
+ ['state-api-tests', 'state_api.tests.cpp'],
+ ['state-context-api-tests', 'state_context_api.tests.cpp'],
+ ['debug-api-tests', 'debug_api.tests.cpp'],
+ ['kmx_xstring-tests', 'kmx_xstring.tests.cpp'],
+ ['kmx_context-tests', 'kmx_context.tests.cpp'],
+ ['actions_normalize-tests', 'actions_normalize.tests.cpp'],
+ ['actions_get_api-tests', 'actions_get_api.tests.cpp'],
]
test_path = join_paths(meson.current_build_dir(), '..', 'kmx')
diff --git a/core/tests/unit/kmnkbd/options_api.cpp b/core/tests/unit/kmnkbd/options_api.tests.cpp
similarity index 100%
rename from core/tests/unit/kmnkbd/options_api.cpp
rename to core/tests/unit/kmnkbd/options_api.tests.cpp
diff --git a/core/tests/unit/kmnkbd/state_api.cpp b/core/tests/unit/kmnkbd/state_api.tests.cpp
similarity index 87%
rename from core/tests/unit/kmnkbd/state_api.cpp
rename to core/tests/unit/kmnkbd/state_api.tests.cpp
index 1da70c33faf..70dcce31335 100644
--- a/core/tests/unit/kmnkbd/state_api.cpp
+++ b/core/tests/unit/kmnkbd/state_api.tests.cpp
@@ -14,6 +14,7 @@
#include "path.hpp"
#include "state.hpp"
#include "action_items.hpp"
+#include "mock/mock_processor.hpp"
#include
@@ -49,10 +50,9 @@ namespace
constexpr char const *doc1_expected = u8"\
{\n\
- \"$schema\" : \"keyman/core/doc/introspection.schema\",\n\
+ \"$schema\" : \"keyman/core/docs/introspection.schema\",\n\
\"keyboard\" : {\n\
\"id\" : \"dummy\",\n\
- \"folder\" : \"\",\n\
\"version\" : \"3.145\",\n\
\"rules\" : []\n\
},\n\
@@ -75,10 +75,9 @@ constexpr char const *doc1_expected = u8"\
constexpr char const *doc2_expected = u8"\
{\n\
- \"$schema\" : \"keyman/core/doc/introspection.schema\",\n\
+ \"$schema\" : \"keyman/core/docs/introspection.schema\",\n\
\"keyboard\" : {\n\
\"id\" : \"dummy\",\n\
- \"folder\" : \"\",\n\
\"version\" : \"3.145\",\n\
\"rules\" : []\n\
},\n\
@@ -112,7 +111,7 @@ int main(int argc, char * argv[])
km_core_keyboard * test_kb = nullptr;
km_core_state * test_state = nullptr,
* test_clone = nullptr;
- try_status(km_core_keyboard_load(km::core::path("dummy.mock").c_str(), &test_kb));
+ test_kb = (km_core_keyboard *)new km::core::mock_processor(km::core::path("dummy.mock"));
// Simple sanity tests.
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
@@ -157,23 +156,23 @@ int main(int argc, char * argv[])
try_status(km_core_process_event(test_state, KM_CORE_VKEY_S,
KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_I,
KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('I')}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('I')}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_L, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('l')}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('l')}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_BKSP, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_BACK, {0,}, {0}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_BACK, {0,}, {0}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_L,
KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('L')}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('L')}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_F2, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
km_core_action_item action = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action.option = &expected_persist_opt;
- assert(action_items(test_state, {action, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {action, {KM_CORE_IT_END}}));
// Test debug dump
auto doc1 = get_json_doc(*test_state),
diff --git a/core/tests/unit/kmnkbd/state_context_api.cpp b/core/tests/unit/kmnkbd/state_context_api.tests.cpp
similarity index 93%
rename from core/tests/unit/kmnkbd/state_context_api.cpp
rename to core/tests/unit/kmnkbd/state_context_api.tests.cpp
index f4df98c5391..34de27cd944 100644
--- a/core/tests/unit/kmnkbd/state_context_api.cpp
+++ b/core/tests/unit/kmnkbd/state_context_api.tests.cpp
@@ -5,8 +5,10 @@
#include "context.hpp"
#include "path.hpp"
+#include "mock/mock_processor.hpp"
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
#include
//-------------------------------------------------------------------------------------
@@ -41,7 +43,14 @@ setup(const char *keyboard, const km_core_cu *context, bool setup_app_context =
teardown();
km::core::path path = km::core::path::join(arg_path, keyboard);
- try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(path.native().c_str());
+ const auto mock_extension = ".mock";
+ if (strlen(keyboard) > strlen(mock_extension) && strcmp(keyboard + strlen(keyboard) - strlen(mock_extension), mock_extension) == 0) {
+ km::core::abstract_processor* kp = new km::core::mock_processor(keyboard);
+ test_kb = static_cast(kp);
+ } else {
+ try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
+ }
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
try_status(context_items_from_utf16(context, &citems));
try_status(km_core_context_set(km_core_state_context(test_state), citems));
@@ -126,7 +135,7 @@ test_context_set_if_needed__identical_context() {
km_core_cu const *new_app_context = u"This is a test";
setup("k_000___null_keyboard.kmx", cached_context, false);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UNCHANGED);
- assert(is_identical_context(cached_context));
+ test_assert(is_identical_context(cached_context));
teardown();
}
@@ -136,8 +145,8 @@ test_context_set_if_needed__different_context() {
km_core_cu const *new_app_context = u"This is a test";
setup("k_000___null_keyboard.kmx", cached_context);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(!is_identical_context(cached_context));
- assert(is_identical_context(new_app_context));
+ test_assert(!is_identical_context(cached_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -148,8 +157,8 @@ test_context_set_if_needed__cached_context_cleared() {
setup("k_000___null_keyboard.kmx", cached_context);
km_core_state_context_clear(test_state);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(!is_identical_context(cached_context));
- assert(is_identical_context(new_app_context));
+ test_assert(!is_identical_context(cached_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -159,8 +168,8 @@ test_context_set_if_needed__application_context_empty() {
km_core_cu const *new_app_context = u"";
setup("k_000___null_keyboard.kmx", cached_context);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(!is_identical_context(cached_context));
- assert(is_identical_context(new_app_context));
+ test_assert(!is_identical_context(cached_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -170,8 +179,8 @@ test_context_set_if_needed__app_context_is_longer() {
km_core_cu const *new_app_context = u"Longer This is a test";
setup("k_000___null_keyboard.kmx", cached_context);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(!is_identical_context(cached_context));
- assert(is_identical_context(new_app_context));
+ test_assert(!is_identical_context(cached_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -181,8 +190,8 @@ test_context_set_if_needed__app_context_is_shorter() {
km_core_cu const *new_app_context = u"is a test";
setup("k_000___null_keyboard.kmx", cached_context);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(!is_identical_context(cached_context));
- assert(is_identical_context(new_app_context));
+ test_assert(!is_identical_context(cached_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -228,7 +237,7 @@ test_context_set_if_needed__cached_context_shorter_and_markers() {
{KM_CORE_CT_MARKER, {0}, {3}}, {KM_CORE_CT_MARKER, {0}, {4}}, KM_CORE_CONTEXT_ITEM_END};
assert_identical_context_with_markers(km_core_state_context(test_state), expected_citems);
assert_identical_context_without_markers(km_core_state_app_context(test_state), expected_citems);
- assert(is_identical_context(new_app_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -330,7 +339,7 @@ test_context_set_if_needed__surrogate_pairs_unchanged() {
km_core_cu const *new_app_context = u"a\U00010100";
setup("k_000___null_keyboard.kmx", cached_context);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UNCHANGED);
- assert(is_identical_context(new_app_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -340,7 +349,7 @@ test_context_set_if_needed__surrogate_pairs_app_context_longer() {
km_core_cu const *new_app_context = u"xa\U00010100";
setup("k_000___null_keyboard.kmx", cached_context);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(is_identical_context(new_app_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -350,7 +359,7 @@ test_context_set_if_needed__surrogate_pairs_cached_context_longer() {
km_core_cu const *new_app_context = u"a\U00010100";
setup("k_000___null_keyboard.kmx", cached_context);
assert_equal_status(km_core_state_context_set_if_needed(test_state, new_app_context), KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(is_identical_context(new_app_context));
+ test_assert(is_identical_context(new_app_context));
teardown();
}
@@ -467,8 +476,8 @@ test_context_clear() {
km_core_cu const *cached_context = u"This is a test";
setup("k_000___null_keyboard.kmx", cached_context);
try_status(km_core_state_context_clear(test_state));
- assert(!is_identical_context(cached_context));
- assert(is_identical_context(u""));
+ test_assert(!is_identical_context(cached_context));
+ test_assert(is_identical_context(u""));
teardown();
}
@@ -479,7 +488,7 @@ void test_context_debug_empty() {
setup("k_000___null_keyboard.kmx", cached_context);
auto str = km_core_state_context_debug(test_state, KM_CORE_DEBUG_CONTEXT_CACHED);
// std::cout << str << std::endl;
- assert(std::u16string(str) == u"|| (len: 0) [ ]");
+ test_assert(std::u16string(str) == u"|| (len: 0) [ ]");
km_core_cu_dispose(str);
}
@@ -504,7 +513,7 @@ void test_context_debug_various() {
auto str = km_core_state_context_debug(test_state, KM_CORE_DEBUG_CONTEXT_CACHED);
// std::cout << str << std::endl;
- assert(std::u16string(str) == u"|123🤣| (len: 9) [ M(5) U+0031 M(1) U+0032 M(2) U+0033 M(3) M(4) U+1f923 ]");
+ test_assert(std::u16string(str) == u"|123🤣| (len: 9) [ M(5) U+0031 M(1) U+0032 M(2) U+0033 M(3) M(4) U+1f923 ]");
km_core_cu_dispose(str);
}
diff --git a/core/tests/unit/kmx/kmx.cpp b/core/tests/unit/kmx/kmx.cpp
index 2a83a11cd57..9eac21494a1 100644
--- a/core/tests/unit/kmx/kmx.cpp
+++ b/core/tests/unit/kmx/kmx.cpp
@@ -29,6 +29,7 @@
#include
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
#include "kmx_test_source.hpp"
@@ -70,7 +71,7 @@ apply_action(
) {
switch (act.type) {
case KM_CORE_IT_END:
- assert(false);
+ test_assert(false);
break;
case KM_CORE_IT_ALERT:
g_beep_found = true;
@@ -109,11 +110,11 @@ apply_action(
// in a table. Or, if Keyman has a cached context, then there may be
// additional text in the text store that Keyman can't see.
if (act.backspace.expected_type == KM_CORE_BT_MARKER) {
- assert(!context.empty());
- assert(context.back().type == KM_CORE_CT_MARKER);
+ test_assert(!context.empty());
+ test_assert(context.back().type == KM_CORE_CT_MARKER);
context.pop_back();
} else if (text_store.length() > 0) {
- assert(!context.empty() && !text_store.empty());
+ test_assert(!context.empty() && !text_store.empty());
km_core_usv ch = text_store.back();
text_store.pop_back();
if (text_store.length() > 0 && Uni_IsSurrogate2(ch)) {
@@ -125,10 +126,10 @@ apply_action(
text_store.pop_back();
}
}
- assert(ch == act.backspace.expected_value);
+ test_assert(ch == act.backspace.expected_value);
- assert(context.back().type == KM_CORE_CT_CHAR);
- assert(context.back().character == ch);
+ test_assert(context.back().type == KM_CORE_CT_CHAR);
+ test_assert(context.back().character == ch);
context.pop_back();
}
break;
@@ -163,7 +164,7 @@ apply_action(
test_source.set_caps_lock_on(act.capsLock);
break;
default:
- assert(false); // NOT SUPPORTED
+ test_assert(false); // NOT SUPPORTED
break;
}
}
@@ -191,7 +192,8 @@ run_test(const km::core::path &source, const km::core::path &compiled) {
km_core_keyboard * test_kb = nullptr;
km_core_state * test_state = nullptr;
- try_status(km_core_keyboard_load(compiled.c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(compiled.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(compiled.stem().c_str(), blob.data(), blob.size(), &test_kb));
// Setup state, environment
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
@@ -253,15 +255,15 @@ run_test(const km::core::path &source, const km::core::path &compiled) {
// not diverged
auto ci = citems;
for(auto test_ci = test_context.begin(); ci->type != KM_CORE_CT_END || test_ci != test_context.end(); ci++, test_ci++) {
- assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end()); // Verify that both lists are same length
- assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
+ test_assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end()); // Verify that both lists are same length
+ test_assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
}
km_core_context_items_dispose(citems);
if ((!context_invalidated) && (text_store != core_context_str)) {
std::cerr << "text store has unexpectedly diverged from core_context" << std::endl;
std::cerr << "text store : " << string_to_hex(text_store) << " [" << text_store << "]" << std::endl;
std::cerr << "core context: " << string_to_hex(core_context_str) << " [" << core_context_str << "]" << std::endl;
- assert(false);
+ test_assert(false);
}
}
@@ -280,8 +282,8 @@ run_test(const km::core::path &source, const km::core::path &compiled) {
// not diverged
auto ci = citems;
for(auto test_ci = test_context.begin(); ci->type != KM_CORE_CT_END || test_ci != test_context.end(); ci++, test_ci++) {
- assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end()); // Verify that both lists are same length
- assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
+ test_assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end()); // Verify that both lists are same length
+ test_assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
}
km_core_context_items_dispose(citems);
diff --git a/core/tests/unit/kmx/kmx_external_event.cpp b/core/tests/unit/kmx/kmx_external_event.tests.cpp
similarity index 87%
rename from core/tests/unit/kmx/kmx_external_event.cpp
rename to core/tests/unit/kmx/kmx_external_event.tests.cpp
index d5d48c8ad67..15f2d284dfb 100644
--- a/core/tests/unit/kmx/kmx_external_event.cpp
+++ b/core/tests/unit/kmx/kmx_external_event.tests.cpp
@@ -14,6 +14,7 @@
#include
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
#include
#include
@@ -43,7 +44,8 @@ void test_external_event(const km::core::path &source_file){
km::core::path full_path = source_file;
- try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(full_path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
// Setup state, environment
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
@@ -56,7 +58,7 @@ void test_external_event(const km::core::path &source_file){
try_status(km_core_event(test_state, event, nullptr));
// The action to turn capslock off must be in the actions list.
- assert(action_items(test_state, {{KM_CORE_IT_CAPSLOCK, {0,}, {0}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_CAPSLOCK, {0,}, {0}}, {KM_CORE_IT_END}}));
km_core_state_dispose(test_state);
km_core_keyboard_dispose(test_kb);
diff --git a/core/tests/unit/kmx/kmx_imx.cpp b/core/tests/unit/kmx/kmx_imx.tests.cpp
similarity index 89%
rename from core/tests/unit/kmx/kmx_imx.cpp
rename to core/tests/unit/kmx/kmx_imx.tests.cpp
index 12b3bc9455d..ab2bbb372b8 100644
--- a/core/tests/unit/kmx/kmx_imx.cpp
+++ b/core/tests/unit/kmx/kmx_imx.tests.cpp
@@ -14,6 +14,7 @@
#include
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
#include "utfcodec.hpp"
#include
@@ -164,7 +165,8 @@ void test_imx_list(const km::core::path &source_file){
km::core::path full_path = source_file;
- try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(full_path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
// Setup state, environment
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
@@ -179,14 +181,14 @@ void test_imx_list(const km::core::path &source_file){
str.append(u":");
str.append(imx_rule_it->function_name);
auto extracted_library_function = convert(str);
- assert(extracted_library_function == expected_imx_map[imx_rule_it->imx_id] );
+ test_assert(extracted_library_function == expected_imx_map[imx_rule_it->imx_id] );
g_extract_imx_map[imx_rule_it->imx_id] = extracted_library_function;
++x;
}
std::cout << " X Value is " << x << std::endl;
- assert(x==4);
+ test_assert(x==4);
km_core_keyboard_imx_list_dispose(kb_imx_list);
km_core_state_dispose(test_state);
@@ -211,7 +213,8 @@ void test_queue_actions (const km::core::path &source_keyboard) {
km::core::path full_path = source_keyboard;
- try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(full_path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
// Setup state, environment
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
@@ -220,13 +223,13 @@ void test_queue_actions (const km::core::path &source_keyboard) {
// Key Press that doesn't trigger a call back
try_status(km_core_process_event(test_state, KM_CORE_VKEY_S,KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('S')}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_BKSP, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('X')}}, {KM_CORE_IT_ALERT, {0,}, {0}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('X')}}, {KM_CORE_IT_ALERT, {0,}, {0}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_ESC, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, { {KM_CORE_IT_CHAR, {0,}, {km_core_usv('Y')}},
+ test_assert(action_items(test_state, { {KM_CORE_IT_CHAR, {0,}, {km_core_usv('Y')}},
{KM_CORE_IT_MARKER, {0,}, {1}},
{KM_CORE_IT_CHAR, {0,}, {km_core_usv('A')}},
{KM_CORE_IT_END}}));
@@ -236,10 +239,10 @@ void test_queue_actions (const km::core::path &source_keyboard) {
km_core_action_item bksp_a = {KM_CORE_IT_BACK};
bksp_a.backspace.expected_type = KM_CORE_BT_CHAR;
bksp_a.backspace.expected_value = 'A';
- assert(action_items(test_state, {bksp_a,{KM_CORE_IT_CHAR, {0,}, {km_core_usv('Z')}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {bksp_a,{KM_CORE_IT_CHAR, {0,}, {km_core_usv('Z')}}, {KM_CORE_IT_END}}));
try_status(km_core_process_event(test_state, KM_CORE_VKEY_2, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
- assert(action_items(test_state, {{KM_CORE_IT_INVALIDATE_CONTEXT, {0,}, {0}}, {KM_CORE_IT_END}}));
+ test_assert(action_items(test_state, {{KM_CORE_IT_INVALIDATE_CONTEXT, {0,}, {0}}, {KM_CORE_IT_END}}));
km_core_state_imx_deregister_callback(test_state);
km_core_keyboard_imx_list_dispose(kb_imx_list);
diff --git a/core/tests/unit/kmx/kmx_key_list.cpp b/core/tests/unit/kmx/kmx_key_list.tests.cpp
similarity index 92%
rename from core/tests/unit/kmx/kmx_key_list.cpp
rename to core/tests/unit/kmx/kmx_key_list.tests.cpp
index 25f3e836a2b..7b811bd0b1a 100644
--- a/core/tests/unit/kmx/kmx_key_list.cpp
+++ b/core/tests/unit/kmx/kmx_key_list.tests.cpp
@@ -15,6 +15,7 @@
#include
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
using namespace km::core::kmx;
@@ -56,7 +57,8 @@ void test_key_list(const km::core::path &source_file){
km::core::path full_path = source_file;
- try_status(km_core_keyboard_load(full_path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(full_path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(full_path.stem().c_str(), blob.data(), blob.size(), &test_kb));
// Setup state, environment
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
@@ -75,14 +77,14 @@ void test_key_list(const km::core::path &source_file){
map_key_list[std::make_pair(key_rule_it->key, key_rule_it->modifier_flag)] = key_rule_it->modifier_flag;
++n;
}
- assert(n==7);
+ test_assert(n==7);
std::map, uint32_t>::iterator it_expected;
std::map, uint32_t>::iterator it_key_list = map_key_list.begin();
while (it_key_list != map_key_list.end()){
it_expected = kb_key_expected_key_list.find(it_key_list->first);
- assert (it_expected != kb_key_expected_key_list.end());
+ test_assert (it_expected != kb_key_expected_key_list.end());
it_key_list++;
}
diff --git a/core/tests/unit/kmx/meson.build b/core/tests/unit/kmx/meson.build
index 548ab895229..a7a69079e4a 100644
--- a/core/tests/unit/kmx/meson.build
+++ b/core/tests/unit/kmx/meson.build
@@ -174,7 +174,7 @@ subdir('fixtures')
# should work for Linux, macOS, and WASM.
test_path = source_path
-key_e = executable('key_list', ['kmx_key_list.cpp', common_test_files],
+key_e = executable('key_list_tests', ['kmx_key_list.tests.cpp', common_test_files],
cpp_args: defns + warns,
include_directories: [inc, libsrc],
link_args: links + tests_flags,
@@ -193,7 +193,7 @@ test('key_list', key_e, depends: kbd_log, args: [kbd_obj] )
# test for imx list
-imx_e = executable('imx_list', ['kmx_imx.cpp', common_test_files],
+imx_e = executable('imx_list_tests', ['kmx_imx.tests.cpp', common_test_files],
cpp_args: defns + warns,
include_directories: [inc, libsrc],
link_args: links + tests_flags,
@@ -211,7 +211,7 @@ kbd_log = custom_target(test_kbd + '.kmx'.underscorify(),
)
test('imx_list', imx_e, depends: kbd_log, args: [kbd_obj] )
-external_e = executable('ext_event', ['kmx_external_event.cpp', common_test_files],
+external_e = executable('ext_event_tests', ['kmx_external_event.tests.cpp', common_test_files],
cpp_args: defns + warns,
include_directories: [inc, libsrc],
link_args: links + tests_flags,
diff --git a/core/tests/unit/ldml/test_context_normalization.cpp b/core/tests/unit/ldml/context_normalization.tests.cpp
similarity index 75%
rename from core/tests/unit/ldml/test_context_normalization.cpp
rename to core/tests/unit/ldml/context_normalization.tests.cpp
index df11ef76532..ea9d635ab23 100644
--- a/core/tests/unit/ldml/test_context_normalization.cpp
+++ b/core/tests/unit/ldml/context_normalization.tests.cpp
@@ -13,6 +13,7 @@
#include
#include "../emscripten_filesystem.h"
+#include "../load_kmx_file.hpp"
//-------------------------------------------------------------------------------------
// Context normalization tests
@@ -42,7 +43,8 @@ void setup(const char *keyboard) {
teardown();
km::core::path path = km::core::path::join(arg_path, "keyboards", keyboard);
- try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
+ auto blob = km::tests::load_kmx_file(path.native().c_str());
+ try_status(km_core_keyboard_load_from_blob(path.stem().c_str(), blob.data(), blob.size(), &test_kb));
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
}
@@ -81,9 +83,9 @@ bool is_identical_context(km_core_cu const *cached_context, km_core_debug_contex
void test_context_normalization_already_nfd() {
km_core_cu const *app_context_nfd = u"A\u0300";
setup("k_001_tiny.kmx");
- assert(km_core_state_context_set_if_needed(test_state, app_context_nfd) == KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(is_identical_context(app_context_nfd, KM_CORE_DEBUG_CONTEXT_APP));
- assert(is_identical_context(app_context_nfd, KM_CORE_DEBUG_CONTEXT_CACHED));
+ test_assert(km_core_state_context_set_if_needed(test_state, app_context_nfd) == KM_CORE_CONTEXT_STATUS_UPDATED);
+ test_assert(is_identical_context(app_context_nfd, KM_CORE_DEBUG_CONTEXT_APP));
+ test_assert(is_identical_context(app_context_nfd, KM_CORE_DEBUG_CONTEXT_CACHED));
teardown();
}
@@ -91,9 +93,9 @@ void test_context_normalization_basic() {
km_core_cu const *application_context = u"This is a test À";
km_core_cu const *cached_context = u"This is a test A\u0300";
setup("k_001_tiny.kmx");
- assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP));
- assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
+ test_assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
+ test_assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP));
+ test_assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
teardown();
}
@@ -102,9 +104,9 @@ void test_context_normalization_hefty() {
km_core_cu const *application_context = u"À" u"é̖" u"\u1e69" u"\u212b" u"\U000114BC";
km_core_cu const *cached_context = u"A\u0300" u"e\u0316\u0301" u"\u0073\u0323\u0307" u"\u0041\u030a" u"\U000114B9\U000114B0";
setup("k_001_tiny.kmx");
- assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP));
- assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
+ test_assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
+ test_assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP));
+ test_assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
teardown();
}
@@ -113,9 +115,9 @@ void test_context_normalization_invalid_unicode() {
km_core_cu const application_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 };
km_core_cu const cached_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 };
setup("k_001_tiny.kmx");
- assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP));
- assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
+ test_assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
+ test_assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP));
+ test_assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
teardown();
}
@@ -124,9 +126,9 @@ void test_context_normalization_lone_trailing_surrogate() {
km_core_cu const application_context[] = { 0xDC01, 0x0020, 0x0020, 0x0000 };
km_core_cu const cached_context[] = /* skipped*/ { 0x0020, 0x0020, 0x0000 };
setup("k_001_tiny.kmx");
- assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
- assert(is_identical_context(application_context+1, KM_CORE_DEBUG_CONTEXT_APP)); // first code unit is skipped
- assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
+ test_assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
+ test_assert(is_identical_context(application_context+1, KM_CORE_DEBUG_CONTEXT_APP)); // first code unit is skipped
+ test_assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED));
teardown();
}
diff --git a/core/tests/unit/ldml/core_ldml_min.cpp b/core/tests/unit/ldml/core_ldml_min.tests.cpp
similarity index 56%
rename from core/tests/unit/ldml/core_ldml_min.cpp
rename to core/tests/unit/ldml/core_ldml_min.tests.cpp
index ed7ea1b3fd5..9657b104b51 100644
--- a/core/tests/unit/ldml/core_ldml_min.cpp
+++ b/core/tests/unit/ldml/core_ldml_min.tests.cpp
@@ -11,9 +11,10 @@
*/
#include
-#include
+#include
#include "keyman_core.h"
+#include "../load_kmx_file.hpp"
int main(int argc, const char *argv[]) {
@@ -22,18 +23,21 @@ int main(int argc, const char *argv[]) {
km_core_status status;
km_core_path_name nowhere = {0}; // this is a narrow or wide char string
- status = km_core_keyboard_load(nowhere, &test_kb);
+ auto blob = km::tests::load_kmx_file(nowhere);
+ status = km_core_keyboard_load_from_blob(nowhere, blob.data(), blob.size(), &test_kb);
- std::cerr << "null km_core_keyboard_load = " << status << std::endl;
- assert(status == KM_CORE_STATUS_INVALID_ARGUMENT);
- assert(test_kb == nullptr);
+ std::cerr << "null km_core_keyboard_load_from_blob = " << status << std::endl;
+ test_assert(status == KM_CORE_STATUS_INVALID_ARGUMENT);
+ test_assert(test_kb == nullptr);
km_core_keyboard_dispose(test_kb);
km_core_state_dispose(nullptr);
km_core_cu_dispose(nullptr);
status = km_core_process_event(nullptr, 0, 0, 0, 0);
- /* NOTREACHED - assertion fails above. */
- assert(status == KM_CORE_STATUS_INVALID_ARGUMENT);
+ /* Note: an assertion fails in km_core_process_event on debug builds,
+ but we will fail on the line below on release builds (as assertions
+ are disabled); actual return value is KM_CORE_STATUS_INVALID_ARGUMENT */
+ test_assert(status == KM_CORE_STATUS_OK);
return 0;
}
diff --git a/core/tests/unit/ldml/test_kmx_plus.cpp b/core/tests/unit/ldml/kmx_plus.tests.cpp
similarity index 65%
rename from core/tests/unit/ldml/test_kmx_plus.cpp
rename to core/tests/unit/ldml/kmx_plus.tests.cpp
index 5b14043b742..eed021664c0 100644
--- a/core/tests/unit/ldml/test_kmx_plus.cpp
+++ b/core/tests/unit/ldml/kmx_plus.tests.cpp
@@ -34,27 +34,27 @@ int test_COMP_KMXPLUS_KEYS_KEY() {
0x00000000 // flags: CHAR
}};
std::u16string s0 = e[0].get_to_string();
- assert_equal(s0.length(), 1);
- assert_equal(s0.at(0), 0x0127);
- assert(s0 == std::u16string(u"ħ"));
+ test_assert_equal(s0.length(), 1);
+ test_assert_equal(s0.at(0), 0x0127);
+ test_assert(s0 == std::u16string(u"ħ"));
std::u16string s1 = e[1].get_to_string();
- assert_equal(s1.length(), 2);
- assert_equal(s1.at(0), 0xD83D);
- assert_equal(s1.at(1), 0xDE40);
- assert(s1 == std::u16string(u"🙀"));
+ test_assert_equal(s1.length(), 2);
+ test_assert_equal(s1.at(0), 0xD83D);
+ test_assert_equal(s1.at(1), 0xDE40);
+ test_assert(s1 == std::u16string(u"🙀"));
// now, elems. Parallel.
std::u16string es0 = elems[0].get_element_string();
- assert_equal(es0.length(), 1);
- assert_equal(es0.at(0), 0x0127);
- assert(es0 == std::u16string(u"ħ"));
+ test_assert_equal(es0.length(), 1);
+ test_assert_equal(es0.at(0), 0x0127);
+ test_assert(es0 == std::u16string(u"ħ"));
std::u16string es1 = elems[1].get_element_string();
- assert_equal(es1.length(), 2);
- assert_equal(es1.at(0), 0xD83D);
- assert_equal(es1.at(1), 0xDE40);
- assert(es1 == std::u16string(u"🙀"));
+ test_assert_equal(es1.length(), 2);
+ test_assert_equal(es1.at(0), 0xD83D);
+ test_assert_equal(es1.at(1), 0xDE40);
+ test_assert(es1 == std::u16string(u"🙀"));
return EXIT_SUCCESS;
}
@@ -89,67 +89,67 @@ int test_ldml_vkeys() {
vk.add(km::tests::get_vk("K_F"), 0, u""); // K_F as a 'gap' key
bool found = false;
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_F"), 0, found), u"");
- assert_equal(found, true); // K_F found, but empty string (gap)
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(found, true); // K_F found, but empty string (gap)
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_ENTER"), 0, found), u"");
- assert_equal(found, false); // K_ENTER not found, empty string
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(found, false); // K_ENTER not found, empty string
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_A"), 0, found), u"K_A-0");
- assert_equal(found, true); // expect
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(found, true); // expect
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_A"), LCTRLFLAG, found), u"K_A-LCTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_A"), RCTRLFLAG, found), u"K_A-RCTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_A"), LALTFLAG, found), u"K_A-LALTFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_A"), RALTFLAG, found), u"K_A-RALTFLAG");
// now try either-side keys :should get the same result with either or both
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_B"), LCTRLFLAG, found), u"K_B-K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_B"), RCTRLFLAG, found), u"K_B-K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_B"), LCTRLFLAG|RCTRLFLAG, found), u"K_B-K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_B"), LALTFLAG, found), u"K_B-K_ALTFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_B"), RALTFLAG, found), u"K_B-K_ALTFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_B"), LALTFLAG|RALTFLAG, found), u"K_B-K_ALTFLAG");
// OOOkay now try BOTH side
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_C"), LCTRLFLAG|LALTFLAG, found), u"K_C-K_ALTFLAG|K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_C"), LCTRLFLAG|RALTFLAG, found), u"K_C-K_ALTFLAG|K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_C"), RCTRLFLAG|LALTFLAG, found), u"K_C-K_ALTFLAG|K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_C"), RCTRLFLAG|RALTFLAG, found), u"K_C-K_ALTFLAG|K_CTRLFLAG");
// OOOkay now try either alt
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_D"), LCTRLFLAG|LALTFLAG, found), u"K_D-LALTFLAG|K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_D"), LCTRLFLAG|RALTFLAG, found), u"K_D-RALTFLAG|K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_D"), RCTRLFLAG|LALTFLAG, found), u"K_D-LALTFLAG|K_CTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_D"), RCTRLFLAG|RALTFLAG, found), u"K_D-RALTFLAG|K_CTRLFLAG");
// OOOkay now try either ctrl
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_E"), LCTRLFLAG|LALTFLAG, found), u"K_E-K_ALTFLAG|LCTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_E"), LCTRLFLAG|RALTFLAG, found), u"K_E-K_ALTFLAG|LCTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_E"), RCTRLFLAG|LALTFLAG, found), u"K_E-K_ALTFLAG|RCTRLFLAG");
- assert_equal(vk.lookup(km::tests::get_vk(
+ test_assert_equal(vk.lookup(km::tests::get_vk(
"K_E"), RCTRLFLAG|RALTFLAG, found), u"K_E-K_ALTFLAG|RCTRLFLAG");
return EXIT_SUCCESS;
@@ -164,13 +164,13 @@ int test_uset() {
};
SimpleUSet u0(&r[0], 2);
- assert_equal(u0.contains(0x62), true); // b
- assert_equal(u0.contains(0x41), false); // A
- assert_equal(u0.contains(0x127), true); // ħ
+ test_assert_equal(u0.contains(0x62), true); // b
+ test_assert_equal(u0.contains(0x41), false); // A
+ test_assert_equal(u0.contains(0x127), true); // ħ
SimpleUSet uempty;
- assert_equal(uempty.contains(0x62), false);
- assert_equal(uempty.contains(0x127), false);
+ test_assert_equal(uempty.contains(0x62), false);
+ test_assert_equal(uempty.contains(0x127), false);
return EXIT_SUCCESS;
}
diff --git a/core/tests/unit/ldml/ldml.cpp b/core/tests/unit/ldml/ldml.cpp
index 12e0c94399a..78e0aacebac 100644
--- a/core/tests/unit/ldml/ldml.cpp
+++ b/core/tests/unit/ldml/ldml.cpp
@@ -35,6 +35,7 @@
#include "ldml/ldml_markers.hpp"
#include "processor.hpp"
+#include "../load_kmx_file.hpp"
namespace {
@@ -82,7 +83,7 @@ apply_action(
std::vector &test_context) {
switch (act.type) {
case KM_CORE_IT_END:
- assert(false);
+ test_assert(false);
break;
case KM_CORE_IT_ALERT:
g_beep_found = true;
@@ -119,8 +120,8 @@ apply_action(
km_core_usv ch = 0;
bool matched_text = false;
// assume the backspace came from set_action() and there's no further info.
- assert(act.backspace.expected_type == KM_CORE_BT_CHAR);
- assert(act.backspace.expected_value == 0);
+ test_assert(act.backspace.expected_type == KM_CORE_BT_CHAR);
+ test_assert(act.backspace.expected_value == 0);
// It is valid for a backspace to be received with an empty text store
// as the user can press backspace with no text in the store and Keyman
// will pass that back to the client, as the client may do additional
@@ -129,7 +130,7 @@ apply_action(
// additional text in the text store that Keyman can't see.
// If there's anything in the text store, pop it off. Two if a pair.
if (text_store.length() > 0) {
- assert(!context.empty() && !text_store.empty());
+ test_assert(!context.empty() && !text_store.empty());
const auto ch1 = text_store.back();
text_store.pop_back();
if (text_store.length() > 0 && Uni_IsSurrogate2(ch1)) {
@@ -152,16 +153,16 @@ apply_action(
auto end = context.rbegin();
while (end != context.rend()) {
if (end->type == KM_CORE_CT_CHAR) {
- assert(!matched_text);
- assert_equal(end->character, ch); // expect popped char to be same as what's in context
+ test_assert(!matched_text);
+ test_assert_equal(end->character, ch); // expect popped char to be same as what's in context
matched_text = true;
context.pop_back();
break; // exit on first real char
}
- assert(end->type != KM_CORE_CT_END); // inappropriate here.
+ test_assert(end->type != KM_CORE_CT_END); // inappropriate here.
context.pop_back();
}
- assert(matched_text);
+ test_assert(matched_text);
}
break;
case KM_CORE_IT_PERSIST_OPT:
@@ -174,7 +175,7 @@ apply_action(
km_core_context_item* new_context_items = nullptr;
// We replace the cached context with the current application context
km_core_status status = context_items_from_utf16(text_store.c_str(), &new_context_items);
- assert(status == KM_CORE_STATUS_OK);
+ test_assert(status == KM_CORE_STATUS_OK);
copy_context_items_to_vector(new_context_items, context);
// also update the test context
copy_context_items_to_vector(new_context_items, test_context);
@@ -192,7 +193,7 @@ apply_action(
test_source.set_caps_lock_on(act.capsLock);
break;
default:
- assert(false); // NOT SUPPORTED
+ test_assert(false); // NOT SUPPORTED
break;
}
}
@@ -253,16 +254,16 @@ verify_context(std::u16string &text_store, km_core_state *&test_state, std::vect
break; // success
}
// fail if only ONE is at end
- assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end());
+ test_assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end());
// fail if type and marker don't match.
- assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
+ test_assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
}
km_core_context_items_dispose(citems);
if (text_store != buf) {
std::cerr << "text store has diverged from buf" << std::endl;
std::cerr << "text store: " << string_to_hex(text_store) << " [" << text_store << "]" << std::endl;
- assert(false);
+ test_assert(false);
}
delete[] buf;
}
@@ -273,7 +274,8 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
km_core_state * test_state = nullptr;
const km_core_status expect_load_status = test_source.get_expected_load_status();
- assert_equal(km_core_keyboard_load(compiled.c_str(), &test_kb), expect_load_status);
+ auto blob = km::tests::load_kmx_file(compiled.native().c_str());
+ test_assert_equal(km_core_keyboard_load_from_blob(compiled.stem().c_str(), blob.data(), blob.size(), &test_kb), expect_load_status);
if (expect_load_status != KM_CORE_STATUS_OK) {
std::cout << "Keyboard was expected to be invalid, so exiting " << std::endl;
@@ -362,7 +364,7 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
} break;
case km::tests::LDML_ACTION_CHECK_EXPECTED: {
if (!normalization_disabled) {
- assert(km::core::util::normalize_nfd(action.string)); // TODO-LDML: should be NFC
+ test_assert(km::core::util::normalize_nfd(action.string)); // TODO-LDML: should be NFC
}
std::cout << "- check expected" << std::endl;
std::cout << "expected : " << string_to_hex(action.string) << " [" << action.string << "]" << std::endl;
@@ -451,7 +453,7 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled,
const km::tests::JsonTestMap& json_tests = json_factory.get_tests();
size_t skip_count = 0;
- assert(json_tests.size() > 0);
+ test_assert(json_tests.size() > 0);
// Loop over all tests
for (const auto& n : json_tests) {
const auto test_name = n.first;
diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp
index f2cca9ee5fc..94e10e7ac43 100644
--- a/core/tests/unit/ldml/ldml_test_source.cpp
+++ b/core/tests/unit/ldml/ldml_test_source.cpp
@@ -40,6 +40,9 @@
#include "unicode/uniset.h"
#include "unicode/usetiter.h"
+#include "../load_kmx_file.hpp"
+
+#include
#include
#define assert_or_return(expr) if(!(expr)) { \
@@ -56,8 +59,6 @@ namespace km {
namespace tests {
-
-
/** string munging */
static void append_to_str(std::u16string &str, const char *buf) {
const PKMX_WCHAR p = km::core::kmx::strtowstr((char *)buf); /** cast away const, unused*/
@@ -162,20 +163,20 @@ LdmlTestSource::parse_source_string(std::string const &s) {
p++;
km_core_usv v;
bool had_open_curly = false;
- assert(p != s.end());
+ test_assert(p != s.end());
if (*p == 'u' || *p == 'U') {
// Unicode value
p++;
if (*p == '{') {
p++;
- assert(p != s.end());
+ test_assert(p != s.end());
had_open_curly = true;
}
size_t n;
std::string s1 = s.substr(p - s.begin(), 8);
v = std::stoul(s1, &n, 16);
// Allow deadkey_number (U+0001) characters and onward
- assert(v >= 0x0001 && v <= 0x10FFFF);
+ test_assert(v >= 0x0001 && v <= 0x10FFFF);
p += n - 1;
if (v < 0x10000) {
t += km_core_cu(v);
@@ -186,13 +187,13 @@ LdmlTestSource::parse_source_string(std::string const &s) {
if (had_open_curly) {
p++;
// close what you opened
- assert(*p == '}'); // close curly
- assert(p != s.end());
+ test_assert(*p == '}'); // close curly
+ test_assert(p != s.end());
}
} else if (*p == 'd') {
// Deadkey
// TODO, not yet supported
- assert(false);
+ test_assert(false);
}
} else {
t += *p;
@@ -211,13 +212,13 @@ LdmlTestSource::parse_u8_source_string(std::string const &u8s) {
p++;
km_core_usv v;
bool had_open_curly = false;
- assert(p != s.end());
+ test_assert(p != s.end());
if (*p == 'u' || *p == 'U') {
// Unicode value
p++;
if (*p == '{') {
p++;
- assert(p != s.end());
+ test_assert(p != s.end());
had_open_curly = true;
}
size_t n;
@@ -226,7 +227,7 @@ LdmlTestSource::parse_u8_source_string(std::string const &u8s) {
std::string s1b = convert(s1);
v = std::stoul(s1b, &n, 16);
// Allow deadkey_number (U+0001) characters and onward
- assert(v >= 0x0001 && v <= 0x10FFFF);
+ test_assert(v >= 0x0001 && v <= 0x10FFFF);
p += n - 1;
if (v < 0x10000) {
t += km_core_cu(v);
@@ -237,13 +238,13 @@ LdmlTestSource::parse_u8_source_string(std::string const &u8s) {
if (had_open_curly) {
p++;
// close what you opened
- assert(*p == '}'); // close curly
- assert(p != s.end());
+ test_assert(*p == '}'); // close curly
+ test_assert(p != s.end());
}
} else if (*p == 'd') {
// Deadkey
// TODO, not yet supported
- assert(false);
+ test_assert(false);
}
} else {
t += *p;
@@ -349,7 +350,7 @@ LdmlTestSource::set_caps_lock_on(bool caps_lock_on) {
key_event
LdmlTestSource::char_to_event(char ch) {
- assert(ch >= 32);
+ test_assert(ch >= 32);
return {
km::core::kmx::s_char_to_vkey[(int)ch - 32].vk,
(uint16_t)(km::core::kmx::s_char_to_vkey[(int)ch - 32].shifted ? KM_CORE_MODIFIER_SHIFT : 0)};
@@ -383,7 +384,7 @@ LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
if (vk == 0) {
std::cerr << "Error parsing [" << vk_event << "] - could not find vkey or modifier: " << s << std::endl;
}
- assert(vk != 0);
+ test_assert(vk != 0);
break; // only one vkey allowed
}
}
@@ -391,9 +392,9 @@ LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
// The string should be empty at this point
if (std::getline(f, s, ' ')) {
std::cerr << "Error parsing vkey ["<key2 != nullptr);
+ test_assert(kmxplus->key2 != nullptr);
- assert(kmxplus->key2Helper.valid());
+ test_assert(kmxplus->key2Helper.valid());
// First, find the string
KMX_DWORD strId = kmxplus->strs->find(id);
if (strId == 0) {
@@ -504,7 +505,7 @@ bool LdmlJsonTestSource::set_key_from_id(key_event& k, const std::u16string& id)
// Now, look for the _first_ candidate vkey match in the kmap.
for (KMX_DWORD kmapIndex = 0; kmapIndex < kmxplus->key2->kmapCount; kmapIndex++) {
auto *kmap = kmxplus->key2Helper.getKmap(kmapIndex);
- assert(kmap != nullptr);
+ test_assert(kmap != nullptr);
if (kmap->key == keyIndex) {
k = {(km_core_virtual_key)kmap->vkey, (uint16_t)kmap->mod};
return true;
@@ -536,7 +537,7 @@ LdmlJsonTestSource::next_action(ldml_action &fillin) {
fillin.type = LDML_ACTION_CHECK_EXPECTED;
fillin.string = LdmlTestSource::parse_u8_source_string(result.get());
if (!get_normalization_disabled()) {
- assert(km::core::util::normalize_nfd(fillin.string)); // TODO-LDML: will be NFC when core is normalizing to NFC
+ test_assert(km::core::util::normalize_nfd(fillin.string)); // TODO-LDML: will be NFC when core is normalizing to NFC
}
return;
} else if (type == "keystroke") {
@@ -551,7 +552,7 @@ LdmlJsonTestSource::next_action(ldml_action &fillin) {
fillin.type = LDML_ACTION_EMIT_STRING;
fillin.string = LdmlTestSource::parse_u8_source_string(to.get());
if (!get_normalization_disabled()) {
- assert(km::core::util::normalize_nfd(fillin.string)); // TODO-LDML: will be NFC when core is normalizing to NFC
+ test_assert(km::core::util::normalize_nfd(fillin.string)); // TODO-LDML: will be NFC when core is normalizing to NFC
}
return;
} else if (type == "backspace") {
@@ -578,7 +579,7 @@ LdmlJsonTestSource::get_context() {
auto startContext = data["/startContext/to"_json_pointer];
context = LdmlTestSource::parse_u8_source_string(startContext);
if (!get_normalization_disabled()) {
- assert(km::core::util::normalize_nfd(context)); // TODO-LDML: should be NFC
+ test_assert(km::core::util::normalize_nfd(context)); // TODO-LDML: should be NFC
}
}
loaded_context = true;
@@ -649,7 +650,7 @@ LdmlJsonRepertoireTestSource::next_action(ldml_action &fillin) {
std::size_t len = km::core::kmx::Utf32CharToUtf16(ch, ch16);
std::u16string chstr = std::u16string(ch16.ch, len);
if (!get_normalization_disabled()) {
- assert(km::core::util::normalize_nfd(chstr)); // TODO-LDML: will be NFC when core is normalizing to NFC
+ test_assert(km::core::util::normalize_nfd(chstr)); // TODO-LDML: will be NFC when core is normalizing to NFC
}
// append to expected
expected.append(chstr);
@@ -660,14 +661,14 @@ LdmlJsonRepertoireTestSource::next_action(ldml_action &fillin) {
// TODO-LDML: no transforms yet.
// TODO-LDML: looking for an exact single key for now
- assert(kmxplus != nullptr);
+ test_assert(kmxplus != nullptr);
// lookup the id
- assert(kmxplus->strs != nullptr);
- assert(kmxplus->key2 != nullptr);
- assert(kmxplus->layr != nullptr);
+ test_assert(kmxplus->strs != nullptr);
+ test_assert(kmxplus->key2 != nullptr);
+ test_assert(kmxplus->layr != nullptr);
- assert(kmxplus->key2Helper.valid());
- assert(kmxplus->layrHelper.valid());
+ test_assert(kmxplus->key2Helper.valid());
+ test_assert(kmxplus->layrHelper.valid());
// First, find the string as an id
// TODO-LDML: will not work for multi string cases
@@ -684,7 +685,7 @@ LdmlJsonRepertoireTestSource::next_action(ldml_action &fillin) {
// Now, look for the _first_ candidate vkey match in the kmap.
for (KMX_DWORD kmapIndex = 0; kmapIndex < kmxplus->key2->kmapCount; kmapIndex++) {
auto *kmap = kmxplus->key2Helper.getKmap(kmapIndex);
- assert(kmap != nullptr);
+ test_assert(kmap != nullptr);
if (kmap->key == keyIndex) {
fillin.k = {(km_core_virtual_key)kmap->vkey, (uint16_t)kmap->mod};
std::cout << "found vkey " << fillin.k.vk << ":" << fillin.k.modifier_state << std::endl;
@@ -757,7 +758,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
}
// check and load the KMX (yes, once again)
- if(!km::core::ldml_processor::is_kmxplus_file(compiled, rawdata)) {
+ rawdata = km::tests::load_kmx_file(compiled);
+ if (!km::core::ldml_processor::is_handled(rawdata)) {
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
return __LINE__;
}
diff --git a/core/tests/unit/ldml/ldml_test_source.hpp b/core/tests/unit/ldml/ldml_test_source.hpp
index 4637af9bd44..8d2b05159a9 100644
--- a/core/tests/unit/ldml/ldml_test_source.hpp
+++ b/core/tests/unit/ldml/ldml_test_source.hpp
@@ -8,6 +8,9 @@
#include "kmx/kmx_plus.h"
+#include
+#include
+
namespace km {
namespace tests {
@@ -92,7 +95,7 @@ class LdmlTestSource {
}
bool get_normalization_disabled() const {
- assert(setup); // make sure set_ was called first
+ test_assert(setup); // make sure set_ was called first
return normalization_disabled;
}
diff --git a/core/tests/unit/ldml/meson.build b/core/tests/unit/ldml/meson.build
index 20ad621b506..08512bd5500 100644
--- a/core/tests/unit/ldml/meson.build
+++ b/core/tests/unit/ldml/meson.build
@@ -4,16 +4,6 @@
# Authors: Marc Durdin
#
-# TODO -- why are these differing from the standard.meson.build flags?
-if cpp_compiler.get_id() == 'gcc' or cpp_compiler.get_id() == 'clang' or cpp_compiler.get_id() == 'emscripten'
- warns = [
- '-Wno-missing-field-initializers',
- '-Wno-unused-parameter'
- ]
-else
- warns = []
-endif
-
# Build all keyboards in output folder; these are defined here and used in the
# keyboards subdir
@@ -82,8 +72,11 @@ ldml = executable('ldml',
objects: lib.extract_all_objects(recursive: false),
)
-core_ldml_min = executable('core_ldml_min',
- ['core_ldml_min.cpp'],
+core_ldml_min = executable('core_ldml_min_tests',
+ [
+ 'core_ldml_min.tests.cpp',
+ common_test_files,
+ ],
cpp_args: defns + warns,
include_directories: [inc, libsrc],
link_args: links,
@@ -91,12 +84,12 @@ core_ldml_min = executable('core_ldml_min',
link_with: [lib],
# objects: lib.extract_all_objects(recursive: false),
)
-test('core_ldml_min', core_ldml_min, suite: 'ldml', should_fail: true)
+test('core_ldml_min_tests', core_ldml_min, suite: 'ldml', should_fail: true)
# Build and run additional test_kmx_plus test
-e = executable('test_kmx_plus', 'test_kmx_plus.cpp',
+e = executable('kmx_plus_tests', 'kmx_plus.tests.cpp',
'ldml_test_utils.cpp',
common_test_files,
cpp_args: defns + warns,
@@ -104,18 +97,18 @@ e = executable('test_kmx_plus', 'test_kmx_plus.cpp',
link_args: links + tests_flags,
dependencies: [icu_uc, icu_i18n],
objects: lib.extract_all_objects(recursive: false))
-test('test_kmx_plus', e, suite: 'ldml')
+test('kmx_plus_tests', e, suite: 'ldml')
# run transforms / ldml utilities unit test
-t = executable('test_transforms', 'test_transforms.cpp',
+t = executable('transforms_tests', 'transforms.tests.cpp',
common_test_files,
cpp_args: defns + warns,
include_directories: [inc, libsrc, '../../../../developer/src/ext/json'],
link_args: links + tests_flags,
dependencies: [icu_uc, icu_i18n],
objects: lib.extract_all_objects(recursive: false))
-test('test_transforms', t, suite: 'ldml')
+test('transforms_tests', t, suite: 'ldml')
# run test_context_normalization ldml unit test
@@ -125,19 +118,19 @@ if cpp_compiler.get_id() == 'emscripten'
normalization_tests_flags += ['-lnodefs.js', wasm_exported_runtime_methods]
endif
-test_context_normalization = executable('test_context_normalization',
- ['test_context_normalization.cpp', common_test_files],
+test_context_normalization = executable('context_normalization_tests',
+ ['context_normalization.tests.cpp', common_test_files],
cpp_args: defns + warns,
include_directories: [inc, libsrc, '../../../../developer/src/ext/json'],
link_args: links + normalization_tests_flags,
dependencies: [icu_uc, icu_i18n],
objects: lib.extract_all_objects(recursive: false))
-test('test_context_normalization', test_context_normalization, suite: 'ldml')
+test('context_normalization_tests', test_context_normalization, suite: 'ldml')
# Build and run additional test_unicode test
-test_unicode = executable('test_unicode', 'test_unicode.cpp',
- ['test_unicode.cpp', common_test_files, generated_headers],
+test_unicode = executable('unicode_tests', 'unicode.tests.cpp',
+ ['unicode.tests.cpp', common_test_files, generated_headers],
cpp_args: defns + warns,
include_directories: [inc, libsrc, '../../../../developer/src/ext/json'],
link_args: links + tests_flags,
@@ -146,7 +139,7 @@ test_unicode = executable('test_unicode', 'test_unicode.cpp',
)
-test('test_unicode', test_unicode, suite: 'ldml',
+test('unicode_tests', test_unicode, suite: 'ldml',
args: [
test_unicode_path / 'nodeversions.json',
test_unicode_path / 'package.json',
diff --git a/core/tests/unit/ldml/test_transforms.cpp b/core/tests/unit/ldml/transforms.tests.cpp
similarity index 88%
rename from core/tests/unit/ldml/test_transforms.cpp
rename to core/tests/unit/ldml/transforms.tests.cpp
index fa909516bc9..411d9410476 100644
--- a/core/tests/unit/ldml/test_transforms.cpp
+++ b/core/tests/unit/ldml/transforms.tests.cpp
@@ -227,14 +227,14 @@ test_reorder_standalone() {
element es(U'a', 0xF4500000 | LDML_ELEM_FLAGS_PREBASE | LDML_ELEM_FLAGS_TERTIARY_BASE); // tertiary -12, primary 80
std::cout << "es flags" << std::hex << es.get_flags() << std::dec << std::endl;
// verify element metadata
- assert_equal(es.is_uset(), false);
- assert_equal(es.get_order(), 0x50);
- assert_equal(es.get_tertiary(), -12);
- assert_equal(es.is_prebase(), true);
- assert_equal(es.is_tertiary_base(), true);
+ test_assert_equal(es.is_uset(), false);
+ test_assert_equal(es.get_order(), 0x50);
+ test_assert_equal(es.get_tertiary(), -12);
+ test_assert_equal(es.is_prebase(), true);
+ test_assert_equal(es.is_tertiary_base(), true);
// verify element matching
- assert_equal(es.matches(U'a'), true);
- assert_equal(es.matches(U'b'), false);
+ test_assert_equal(es.matches(U'a'), true);
+ test_assert_equal(es.matches(U'b'), false);
}
std::cout << __FILE__ << ":" << __LINE__ << " - nod-Lana " << std::endl;
@@ -255,8 +255,8 @@ test_reorder_standalone() {
const COMP_KMXPLUS_USET_USET &toneMarksUset = usets[0];
const SimpleUSet toneMarks(&ranges[toneMarksUset.range], toneMarksUset.count);
// validate that the range [1A75, 1A79] matches
- assert_equal(toneMarks.contains(0x1A76), true);
- assert_equal(toneMarks.contains(0x1A60), false);
+ test_assert_equal(toneMarks.contains(0x1A76), true);
+ test_assert_equal(toneMarks.contains(0x1A60), false);
std::cout << __FILE__ << ":" << __LINE__ << " - element API test " << std::endl;
// element test
@@ -264,40 +264,40 @@ test_reorder_standalone() {
element es(U'a', (80 << LDML_ELEM_FLAGS_ORDER_BITSHIFT) | LDML_ELEM_FLAGS_PREBASE); // tertiary -12, primary 80
std::cout << "es flags" << std::hex << es.get_flags() << std::dec << std::endl;
// verify element metadata
- assert_equal(es.is_uset(), false);
- assert_equal(es.get_order(), 0x50);
- assert_equal(es.get_tertiary(), 0);
- assert_equal(es.is_prebase(), true);
- assert_equal(es.is_tertiary_base(), false);
+ test_assert_equal(es.is_uset(), false);
+ test_assert_equal(es.get_order(), 0x50);
+ test_assert_equal(es.get_tertiary(), 0);
+ test_assert_equal(es.is_prebase(), true);
+ test_assert_equal(es.is_tertiary_base(), false);
// verify element matching
- assert_equal(es.matches(U'a'), true);
- assert_equal(es.matches(U'b'), false);
+ test_assert_equal(es.matches(U'a'), true);
+ test_assert_equal(es.matches(U'b'), false);
element eu(toneMarks, 0x37F40000);
// element metadata
std::cout << "eu flags" << std::hex << eu.get_flags() << std::dec << std::endl;
- assert_equal(eu.is_uset(), true);
+ test_assert_equal(eu.is_uset(), true);
std::cout << "order" << (int)eu.get_order() << std::endl;
- assert_equal(eu.get_order(), -12);
- assert_equal(eu.get_tertiary(), 55);
- assert_equal(eu.is_prebase(), false);
- assert_equal(eu.is_tertiary_base(), false);
+ test_assert_equal(eu.get_order(), -12);
+ test_assert_equal(eu.get_tertiary(), 55);
+ test_assert_equal(eu.is_prebase(), false);
+ test_assert_equal(eu.is_tertiary_base(), false);
// element matching
- assert_equal(eu.matches(U'a'), false);
- assert_equal(eu.matches(U'\u1A76'), true);
- assert_equal(eu.matches(U'\u1A75'), true);
+ test_assert_equal(eu.matches(U'a'), false);
+ test_assert_equal(eu.matches(U'\u1A76'), true);
+ test_assert_equal(eu.matches(U'\u1A75'), true);
element_list l; // '[tones]a'
l.emplace_back(es);
l.emplace_back(eu);
std::cout << __FILE__ << ":" << __LINE__ << " - list test " << std::endl;
- assert_equal(l.match_end(U"asdfasdf"), 0); // no match
- assert_equal(l.match_end(U"a"), 0); // partial substring, fastpath because it's short
- assert_equal(l.match_end(U"\u1A76"), 0); // partial substring, fastpath because it's short
- assert_equal(l.match_end(U"a\u1A76"), 2); // Match
- assert_equal(l.match_end(U"a\u1A75"), 2); // Match
- assert_equal(l.match_end(U"SomethingSomethingSomethinga\u1A76"), 2); // Sub-Match
+ test_assert_equal(l.match_end(U"asdfasdf"), 0); // no match
+ test_assert_equal(l.match_end(U"a"), 0); // partial substring, fastpath because it's short
+ test_assert_equal(l.match_end(U"\u1A76"), 0); // partial substring, fastpath because it's short
+ test_assert_equal(l.match_end(U"a\u1A76"), 2); // Match
+ test_assert_equal(l.match_end(U"a\u1A75"), 2); // Match
+ test_assert_equal(l.match_end(U"SomethingSomethingSomethinga\u1A76"), 2); // Sub-Match
// generate sort keys
std::cout << __FILE__ << ":" << __LINE__ << " - get_sort_key test " << std::endl;
@@ -309,24 +309,24 @@ test_reorder_standalone() {
i->dump();
}
// make sure it matches
- assert_equal(l.match_end(str), 2);
+ test_assert_equal(l.match_end(str), 2);
// update the keylist with these elements.
l.update_sort_key(0, keylist);
std::cout << __FILE__ << ":" << __LINE__ << " updated sortkey" << std::endl;
- assert_equal(keylist.size(), 2);
+ test_assert_equal(keylist.size(), 2);
reorder_weight secondary = 0;
for (auto i = keylist.begin(); i < keylist.end(); i++) {
i->dump();
- assert_equal(i->secondary, secondary);
- assert_equal(i->quaternary, secondary);
+ test_assert_equal(i->secondary, secondary);
+ test_assert_equal(i->quaternary, secondary);
secondary++;
}
std::cout << std::endl;
// spot check first sortkey
- assert_equal(keylist.begin()->primary, 80);
- assert_equal(keylist.begin()->tertiary, 0);
- assert_equal(keylist.begin()->ch, 0x61);
+ test_assert_equal(keylist.begin()->primary, 80);
+ test_assert_equal(keylist.begin()->tertiary, 0);
+ test_assert_equal(keylist.begin()->ch, 0x61);
std::cout << __FILE__ << ":" << __LINE__ << " sorted sortkey" << std::endl;
// now sort them
@@ -336,9 +336,9 @@ test_reorder_standalone() {
}
std::cout << std::endl;
// spot check first sort key
- assert_equal(keylist.begin()->primary, -12);
- assert_equal(keylist.begin()->tertiary, 55);
- assert_equal(keylist.begin()->ch, 0x1A78);
+ test_assert_equal(keylist.begin()->primary, -12);
+ test_assert_equal(keylist.begin()->tertiary, 55);
+ test_assert_equal(keylist.begin()->ch, 0x1A78);
}
std::cout << __FILE__ << ":" << __LINE__ << " - key test " << std::endl;
@@ -486,7 +486,7 @@ test_reorder_standalone() {
std::u32string output;
size_t len = tr.apply(text, output);
zassert_string_equal(output, U"");
- assert_equal(len, 0);
+ test_assert_equal(len, 0);
}
}
}
@@ -626,16 +626,16 @@ test_map() {
std::cout << __FILE__ << ":" << __LINE__ << " transform_entry::findIndex" << std::endl;
{
std::deque list;
- assert_equal(km::core::util::km_regex::findIndex(U"Does Not Exist", list), -1);
+ test_assert_equal(km::core::util::km_regex::findIndex(U"Does Not Exist", list), -1);
list.emplace_back(U"0th");
list.emplace_back(U"First");
list.emplace_back(U"Second");
- assert_equal(km::core::util::km_regex::findIndex(U"First", list), 1);
- assert_equal(km::core::util::km_regex::findIndex(U"0th", list), 0);
- assert_equal(km::core::util::km_regex::findIndex(U"Second", list), 2);
- assert_equal(km::core::util::km_regex::findIndex(U"Nowhere", list), -1);
+ test_assert_equal(km::core::util::km_regex::findIndex(U"First", list), 1);
+ test_assert_equal(km::core::util::km_regex::findIndex(U"0th", list), 0);
+ test_assert_equal(km::core::util::km_regex::findIndex(U"Second", list), 2);
+ test_assert_equal(km::core::util::km_regex::findIndex(U"Nowhere", list), -1);
}
return EXIT_SUCCESS;
@@ -659,7 +659,7 @@ test_strutils() {
const std::u32string src = U"abc";
const std::u32string dst = remove_markers(src, map);
zassert_string_equal(dst, src); // unchanged
- assert_equal(count_markers(map), 0);
+ test_assert_equal(count_markers(map), 0);
}
{
marker_map map;
@@ -670,7 +670,7 @@ test_strutils() {
zassert_string_equal(dst, expect);
marker_map expm = {{U'e', 0x1L}};
assert_marker_map_equal(map, expm); // marker 1 @ e
- assert_equal(count_markers(map), 1);
+ test_assert_equal(count_markers(map), 1);
}
{
marker_map map;
@@ -679,7 +679,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src;
zassert_string_equal(dst, expect);
- assert_equal(count_markers(map), 0);
+ test_assert_equal(count_markers(map), 0);
}
{
marker_map map;
@@ -688,7 +688,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src; // 'q' removed
zassert_string_equal(dst, expect);
- assert_equal(count_markers(map), 0);
+ test_assert_equal(count_markers(map), 0);
}
{
marker_map map;
@@ -697,7 +697,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src;
zassert_string_equal(dst, expect);
- assert_equal(count_markers(map), 0);
+ test_assert_equal(count_markers(map), 0);
}
{
marker_map map;
@@ -706,7 +706,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src;
zassert_string_equal(dst, expect);
- assert_equal(count_markers(map), 0);
+ test_assert_equal(count_markers(map), 0);
}
{
marker_map map;
@@ -717,7 +717,7 @@ test_strutils() {
zassert_string_equal(dst, expect);
marker_map expm({{MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 1);
+ test_assert_equal(count_markers(map), 1);
}
{
marker_map map;
@@ -730,7 +730,7 @@ test_strutils() {
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x0320, 0x2L}, {0x0300, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 4);
+ test_assert_equal(count_markers(map), 4);
}
{
std::cout << __FILE__ << ":" << __LINE__ << " - prepend hex quad" << std::endl;
@@ -752,11 +752,11 @@ test_strutils() {
}
{
std::cout << __FILE__ << ":" << __LINE__ << " - parse hex quad" << std::endl;
- assert_equal(parse_hex_quad(U"0001"), 0x0001);
- assert_equal(parse_hex_quad(U"CAFE"), 0xCAFE);
- assert_equal(parse_hex_quad(U"D00d"), 0xD00D);
- assert_equal(parse_hex_quad(U"FFFF"), 0xFFFF);
- assert_equal(parse_hex_quad(U"zzzz"), 0); // err
+ test_assert_equal(parse_hex_quad(U"0001"), 0x0001);
+ test_assert_equal(parse_hex_quad(U"CAFE"), 0xCAFE);
+ test_assert_equal(parse_hex_quad(U"D00d"), 0xD00D);
+ test_assert_equal(parse_hex_quad(U"FFFF"), 0xFFFF);
+ test_assert_equal(parse_hex_quad(U"zzzz"), 0); // err
}
return EXIT_SUCCESS;
}
@@ -773,9 +773,9 @@ test_normalize() {
const std::u32string src = U"6e\U00000320\U00000300"; // already NFD
const std::u32string expect = src;
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
zassert_string_equal(dst, expect);
- assert_equal(count_markers(map), 0);
+ test_assert_equal(count_markers(map), 0);
}
{
marker_map map;
@@ -783,9 +783,9 @@ test_normalize() {
const std::u32string src = U"6e\U00000300\U00000320"; // swapped
const std::u32string expect = U"6e\U00000320\U00000300"; // correct NFD
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
zassert_string_equal(dst, expect);
- assert_equal(count_markers(map), 0);
+ test_assert_equal(count_markers(map), 0);
}
{
@@ -796,11 +796,11 @@ test_normalize() {
U"\U0000ffff\U00000008\U00000004";
const std::u32string expect = src;
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x320, 0x2L}, {0x300, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 4);
+ test_assert_equal(count_markers(map), 4);
}
{
@@ -811,11 +811,11 @@ test_normalize() {
U"\U0000ffff\U00000008\U00000004";
const std::u32string expect = src;
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x320, 0x2L}, {0x300, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 4);
+ test_assert_equal(count_markers(map), 4);
}
{
marker_map map;
@@ -827,7 +827,7 @@ test_normalize() {
U"6\U0000ffff\U00000008\U00000001e\U0000ffff\U00000008\U00000003\U00000320\U0000ffff\U00000008\U00000002\U00000300"
U"\U0000ffff\U00000008\U00000004";
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
if (dst != expect) {
std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl;
@@ -835,7 +835,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x300, 0x2L}, {0x320, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 4);
+ test_assert_equal(count_markers(map), 4);
}
{
@@ -845,7 +845,7 @@ test_normalize() {
const std::u32string src = U"4e\u0300\uFFFF\u0008\u0001\u0320";
const std::u32string expect = U"4e\uFFFF\u0008\u0001\u0320\u0300";
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
if (dst != expect) {
std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl;
@@ -853,7 +853,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x1L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 1);
+ test_assert_equal(count_markers(map), 1);
}
{
@@ -863,7 +863,7 @@ test_normalize() {
const std::u32string src = U"9ce\u0300\uFFFF\u0008\u0002\u0320\uFFFF\u0008\u0001";
const std::u32string expect = U"9ce\uFFFF\u0008\u0002\u0320\u0300\uFFFF\u0008\u0001";
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
if (dst != expect) {
std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl;
@@ -871,7 +871,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x2L}, {MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 2);
+ test_assert_equal(count_markers(map), 2);
}
{
@@ -881,7 +881,7 @@ test_normalize() {
const std::u32string src = U"9ce\u0300\\uffff\\u0008\\u0002\u0320\\uffff\\u0008\\u0001";
const std::u32string expect = U"9ce\\uffff\\u0008\\u0002\u0320\u0300\\uffff\\u0008\\u0001";
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map, regex_sentinel));
+ test_assert(normalize_nfd_markers_segment(dst, map, regex_sentinel));
if (dst != expect) {
std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl;
std::cout << " " << dst << std::endl;
@@ -891,7 +891,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x2L}, {MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 2);
+ test_assert_equal(count_markers(map), 2);
}
{
// from tests - regex edition
@@ -900,7 +900,7 @@ test_normalize() {
const std::u32string src = U"9ce\u0300\\uffff\\u0008[\\u0001-\\ud7fe]\u0320\\uffff\\u0008\\u0001";
const std::u32string expect = U"9ce\\uffff\\u0008[\\u0001-\\ud7fe]\u0320\u0300\\uffff\\u0008\\u0001";
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map, regex_sentinel));
+ test_assert(normalize_nfd_markers_segment(dst, map, regex_sentinel));
if (dst != expect) {
std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl;
@@ -908,7 +908,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, LDML_MARKER_ANY_INDEX}, {MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 2);
+ test_assert_equal(count_markers(map), 2);
}
{
@@ -918,7 +918,7 @@ test_normalize() {
const std::u32string src = U"9ce\u0300\uFFFF\u0008\u0002\uFFFF\u0008\u0002\u0320";
const std::u32string expect = U"9ce\uFFFF\u0008\u0002\uFFFF\u0008\u0002\u0320\u0300";
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
if (dst != expect) {
std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl;
@@ -926,7 +926,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x2L}, {0x320, 0x2L}});
assert_marker_map_equal(map, expm);
- assert_equal(count_markers(map), 2);
+ test_assert_equal(count_markers(map), 2);
}
{
@@ -936,7 +936,7 @@ test_normalize() {
const std::u32string src = U"9ce\u0300\uFFFF\u0008\u0002\uFFFF\u0008\u0001\uFFFF\u0008\u0003\u0320";
const std::u32string expect = U"9ce\uFFFF\u0008\u0002\uFFFF\u0008\u0001\uFFFF\u0008\u0003\u0320\u0300";
std::u32string dst = src;
- assert(normalize_nfd_markers_segment(dst, map));
+ test_assert(normalize_nfd_markers_segment(dst, map));
if (dst != expect) {
std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl;
@@ -972,7 +972,7 @@ test_normalize() {
assert_marker_map_equal(map, expm);
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -990,7 +990,7 @@ test_normalize() {
marker_map expm({{0x09C7, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -1008,7 +1008,7 @@ test_normalize() {
marker_map expm({{0x09C7, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -1026,7 +1026,7 @@ test_normalize() {
marker_map expm({{0x09C7, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -1044,7 +1044,7 @@ test_normalize() {
marker_map expm({{0x0308, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -1064,7 +1064,7 @@ test_normalize() {
marker_map expm({{0x0300, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -1082,7 +1082,7 @@ test_normalize() {
marker_map expm({{0x300, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -1100,7 +1100,7 @@ test_normalize() {
marker_map expm({{0x0308, 0x1L},{0x0308, 0x2L},{MARKER_BEFORE_EOT, 0x3L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
- assert(normalize_nfd_markers(dst_nfd));
+ test_assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
@@ -1117,7 +1117,7 @@ test_normalize() {
const std::u32string src = x; \
const std::u32string expect_nfd = y; \
std::u32string dst_nfd = src; \
- assert(normalize_nfd_markers(dst_nfd)); \
+ test_assert(normalize_nfd_markers(dst_nfd)); \
if (dst_nfd != expect_nfd) { \
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl; \
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl; \
@@ -1145,43 +1145,43 @@ test_util_regex() {
{
std::cout << __FILE__ << ":" << __LINE__ << " * util_regex.hpp null tests" << std::endl;
km::core::util::km_regex r;
- assert(!r.valid()); // not valid because of an empty string
+ test_assert(!r.valid()); // not valid because of an empty string
}
{
std::cout << __FILE__ << ":" << __LINE__ << " * util_regex.hpp simple tests" << std::endl;
km::core::util::km_regex r(U"ion");
- assert(r.valid());
+ test_assert(r.valid());
const std::u32string to(U"ivity");
const std::deque fromList;
const std::deque toList;
std::u32string output;
auto apply0 = r.apply(U"not present", output, to, fromList, toList);
- assert_equal(apply0, 0); // not found
+ test_assert_equal(apply0, 0); // not found
const std::u32string input(U"action");
auto apply1 = r.apply(input, output, to, fromList, toList);
- assert_equal(apply1, 3); // matched last 3 codepoints
+ test_assert_equal(apply1, 3); // matched last 3 codepoints
std::u32string expect(U"ivity");
zassert_string_equal(output, expect)
}
{
std::cout << __FILE__ << ":" << __LINE__ << " * util_regex.hpp wide tests" << std::endl;
km::core::util::km_regex r(U"e𐒻");
- assert(r.valid());
+ test_assert(r.valid());
const std::u32string to(U"𐓏");
const std::deque fromList;
const std::deque toList;
std::u32string output;
const std::u32string input(U":e𐒻");
auto apply1 = r.apply(input, output, to, fromList, toList);
- assert_equal(apply1, 2); // matched last 2 codepoints
+ test_assert_equal(apply1, 2); // matched last 2 codepoints
std::u32string expect(U"𐓏");
zassert_string_equal(output, expect)
}
{
std::cout << __FILE__ << ":" << __LINE__ << " * util_regex.hpp simple map tests" << std::endl;
km::core::util::km_regex r(U"(A|B|C)");
- assert(r.valid());
+ test_assert(r.valid());
const std::u32string to(U"$[1:alpha2]"); // ignored
std::deque fromList;
fromList.emplace_back(U"A");
@@ -1193,18 +1193,18 @@ test_util_regex() {
toList.emplace_back(U"P");
std::u32string output;
auto apply0 = r.apply(U"not present", output, to, fromList, toList);
- assert_equal(apply0, 0); // not found
+ test_assert_equal(apply0, 0); // not found
const std::u32string input(U"WHOA");
auto apply1 = r.apply(input, output, to, fromList, toList);
- assert_equal(apply1, 1); // matched last 1 codepoint
+ test_assert_equal(apply1, 1); // matched last 1 codepoint
std::u32string expect(U"N");
zassert_string_equal(output, expect)
}
{
std::cout << __FILE__ << ":" << __LINE__ << " * util_regex.hpp wide map tests" << std::endl;
km::core::util::km_regex r(U"(𐒷|𐒻|𐓏𐓏|x)");
- assert(r.valid());
+ test_assert(r.valid());
const std::u32string to(U"$[1:alpha2]"); // ignored
std::deque fromList;
fromList.emplace_back(U"𐒷");
@@ -1218,15 +1218,15 @@ test_util_regex() {
toList.emplace_back(U"𐓏");
std::u32string output;
auto apply0 = r.apply(U"not present", output, to, fromList, toList);
- assert_equal(apply0, 0); // not found
+ test_assert_equal(apply0, 0); // not found
- assert_equal(r.apply(U"WHO𐓏𐒷", output, to, fromList, toList), 1);
+ test_assert_equal(r.apply(U"WHO𐓏𐒷", output, to, fromList, toList), 1);
zassert_string_equal(output, U"x");
- assert_equal(r.apply(U"WHO𐓏x", output, to, fromList, toList), 1);
+ test_assert_equal(r.apply(U"WHO𐓏x", output, to, fromList, toList), 1);
zassert_string_equal(output, U"𐓏");
- assert_equal(r.apply(U"WHO𐓏𐓏", output, to, fromList, toList), 2); // 2 codepoints
+ test_assert_equal(r.apply(U"WHO𐓏𐓏", output, to, fromList, toList), 2); // 2 codepoints
zassert_string_equal(output, U"𐒻");
}
diff --git a/core/tests/unit/ldml/test_unicode.cpp b/core/tests/unit/ldml/unicode.tests.cpp
similarity index 95%
rename from core/tests/unit/ldml/test_unicode.cpp
rename to core/tests/unit/ldml/unicode.tests.cpp
index 937125d1727..f9d95893dca 100644
--- a/core/tests/unit/ldml/test_unicode.cpp
+++ b/core/tests/unit/ldml/unicode.tests.cpp
@@ -66,7 +66,7 @@ nlohmann::json load_json(const km::core::path &jsonpath) {
std::ifstream json_file(jsonpath.native());
if (!json_file) {
std::cerr << "ERROR Could not load: " << jsonpath << std::endl;
- assert (json_file);
+ test_assert (json_file);
}
nlohmann::json data = nlohmann::json::parse(json_file);
return data;
@@ -74,7 +74,7 @@ nlohmann::json load_json(const km::core::path &jsonpath) {
/** @returns the major version of 'ver', skipping initial '^'. empty on err */
std::string get_major(const std::string& ver) {
- assert(!ver.empty());
+ test_assert(!ver.empty());
auto start = 0;
// skip leading '^'
if (ver[start] == '^') {
@@ -82,7 +82,7 @@ std::string get_major(const std::string& ver) {
}
// find first '.'
auto end = ver.find('.', start);
- assert(end != std::string::npos);
+ test_assert(end != std::string::npos);
return ver.substr(start, end - start);
}
@@ -94,9 +94,9 @@ std::string get_block_unicode_ver(const char *blocks_path) {
// open Blocks.txt
std::ifstream blocks_file(
km::core::path(blocks_path).native());
- assert(blocks_file.good());
+ test_assert(blocks_file.good());
std::string block_line;
- assert(std::getline(blocks_file, block_line)); // first line
+ test_assert(std::getline(blocks_file, block_line)); // first line
// The first line is something such as '# Blocks-15.1.0.txt'
// We skip the prefix, and then stop before the suffix
@@ -105,12 +105,12 @@ std::string get_block_unicode_ver(const char *blocks_path) {
const std::string txt_suffix = ".txt";
// find and skip the prefix - "15.1.0.txt"
- assert(block_line.length() > prefix.length());
+ test_assert(block_line.length() > prefix.length());
std::string result = block_line.substr(prefix.length()); // "15.1.0"
// find and trim before the suffix
auto txt_pos = result.find(txt_suffix, 0);
- assert(txt_pos != std::string::npos);
+ test_assert(txt_pos != std::string::npos);
result.resize(txt_pos);
return result;
@@ -167,7 +167,7 @@ const std::string &block_unicode_ver) {
// allow the Node.js version to be >= required
auto node_engine_num = std::atoi(node_engine_major.c_str());
auto node_num = std::atoi(node_major.c_str());
- assert(node_num >= node_engine_num);
+ test_assert(node_num >= node_engine_num);
// the cxx_icu can come from the Ubuntu environment, so do not depend on it
// for now.
@@ -213,7 +213,7 @@ void test_has_boundary_before() {
std::cerr << "Error: util_normalize_table.h said " << boolstr(km_hbb) << " but ICU said " << boolstr(icu_hbb) << " for "
<< "has_nfd_boundary_before(0x" << std::hex << cp << std::dec << ")" << std::endl;
}
- assert(km_hbb == icu_hbb);
+ test_assert(km_hbb == icu_hbb);
}
std::cout << "All OK!" << std::endl;
}
@@ -224,11 +224,11 @@ int test_all(const char *jsonpath, const char *packagepath, const char *blockspa
// load the dump of node's process.versions which the meson.build file generated
auto versions = load_json(km::core::path(jsonpath));
- assert(!versions.empty());
+ test_assert(!versions.empty());
// load our top level package.json
auto package = load_json(km::core::path(packagepath));
- assert(!package.empty());
+ test_assert(!package.empty());
const auto block_unicode_ver = get_block_unicode_ver(blockspath);
diff --git a/core/tests/unit/load_kmx_file.cpp b/core/tests/unit/load_kmx_file.cpp
new file mode 100644
index 00000000000..41ccea0cacc
--- /dev/null
+++ b/core/tests/unit/load_kmx_file.cpp
@@ -0,0 +1,34 @@
+#include
+#include
+#include
+#include
+
+#include "kmx_file.h"
+#include "path.hpp"
+#include "utfcodec.hpp"
+
+namespace km::tests {
+
+std::vector load_kmx_file(km::core::path const& kb_path) {
+ std::vector data;
+ std::ifstream file(static_cast(kb_path), std::ios::binary | std::ios::ate);
+ if (file.fail()) {
+ return std::vector();
+ }
+ const std::streamsize size = file.tellg();
+ if (size >= KMX_MAX_ALLOWED_FILE_SIZE) {
+ return std::vector();
+ }
+
+ file.seekg(0, std::ios::beg);
+
+ data.resize((size_t)size);
+ if (!file.read((char*)data.data(), size)) {
+ return std::vector();
+ }
+
+ file.close();
+ return data;
+}
+
+}
diff --git a/core/tests/unit/load_kmx_file.hpp b/core/tests/unit/load_kmx_file.hpp
new file mode 100644
index 00000000000..1a94b249c24
--- /dev/null
+++ b/core/tests/unit/load_kmx_file.hpp
@@ -0,0 +1,14 @@
+#ifndef __LOAD_KMX_FILE_HPP__
+#define __LOAD_KMX_FILE_HPP__
+
+#include "path.hpp"
+
+namespace km {
+namespace tests {
+
+std::vector load_kmx_file(km::core::path const& kb_path);
+
+}
+}
+
+#endif // __LOAD_KMX_FILE_HPP__
diff --git a/core/tests/unit/meson.build b/core/tests/unit/meson.build
index ee141cc12d3..0637ace96e5 100644
--- a/core/tests/unit/meson.build
+++ b/core/tests/unit/meson.build
@@ -1,13 +1,53 @@
node = find_program('node', required: true)
-common_test_files = [
+gtest = subproject('gtest')
+gtest_dep = gtest.get_variable('gtest_dep')
+gmock_dep = gtest.get_variable('gmock_dep')
+
+# TODO -- why are these differing from the standard.meson.build flags?
+# -Wno-unused-parameter --> test_color.h
+if cpp_compiler.get_id() == 'gcc' or cpp_compiler.get_id() == 'clang' or cpp_compiler.get_id() == 'emscripten'
+ warns = [
+ '-Wno-missing-field-initializers',
+ '-Wno-unused-parameter'
+ ]
+else
+ warns = []
+endif
+
+test_util_files = [
meson.current_source_dir() / 'emscripten_filesystem.cpp',
+ meson.current_source_dir() / 'load_kmx_file.cpp',
+]
+
+common_test_files = [
+ test_util_files,
meson.global_source_root() / '../common/include/test_color.cpp'
]
hextobin_root = meson.global_source_root() / '../common/tools/hextobin/build/hextobin.js'
hextobin_cmd = [node, hextobin_root]
+if cpp_compiler.get_id() == 'emscripten'
+ extra_link_args = [ '-lnodefs.js' ]
+else
+ extra_link_args = []
+endif
+
+kmcorekeyboardapitests = executable('km_core_keyboard_api.tests',
+ [
+ 'km_core_keyboard_api.tests.cpp',
+ common_test_files,
+ ],
+ include_directories: [inc, libsrc],
+ cpp_args: defns + warns,
+ link_args: [ links, extra_link_args ],
+ dependencies: [icu_uc, icu_i18n, gtest_dep, gmock_dep],
+ objects: lib.extract_all_objects(recursive: false),
+)
+
+test('km-core-keyboard-api-tests', kmcorekeyboardapitests)
+
subdir('json')
subdir('utftest')
subdir('kmnkbd')
diff --git a/core/tests/unit/utftest/meson.build b/core/tests/unit/utftest/meson.build
index 4ab6cb74ca0..c82b6f98c55 100644
--- a/core/tests/unit/utftest/meson.build
+++ b/core/tests/unit/utftest/meson.build
@@ -4,7 +4,7 @@
# Authors: Tim Eves (TSE)
#
-e = executable('utftest', 'utftest.cpp',
+e = executable('utftest', 'utftest.tests.cpp',
objects: lib.extract_objects('../../common/cpp/utfcodec.cpp'),
include_directories: [libsrc])
test('utftest', e)
diff --git a/core/tests/unit/utftest/utftest.cpp b/core/tests/unit/utftest/utftest.tests.cpp
similarity index 100%
rename from core/tests/unit/utftest/utftest.cpp
rename to core/tests/unit/utftest/utftest.tests.cpp
diff --git a/core/tools/api-header-extractor/build.sh b/core/tools/api-header-extractor/build.sh
index aa87a2c4bfc..fe048ce48b3 100755
--- a/core/tools/api-header-extractor/build.sh
+++ b/core/tools/api-header-extractor/build.sh
@@ -8,7 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
CORE_ROOT="$KEYMAN_ROOT/core"
CORE_H_FILE="$CORE_ROOT/include/keyman/keyman_core_api.h"
CORE_H_DOC="index.md"
-OUTPATH="$CORE_ROOT/build/doc"
+OUTPATH="$CORE_ROOT/build/docs/internal"
################################ Main script ################################
diff --git a/developer/docs/help/context/about-tike.md b/developer/docs/help/context/about-tike.md
new file mode 100644
index 00000000000..550a5d7410f
--- /dev/null
+++ b/developer/docs/help/context/about-tike.md
@@ -0,0 +1,8 @@
+---
+title: About Dialog
+---
+
+![About dialog](../images/ui/frmAboutTike.png)
+
+The About dialog displays copyright and registration information for
+Keyman Developer, and has a link to the Keyman website.
\ No newline at end of file
diff --git a/developer/docs/help/context/character-map.md b/developer/docs/help/context/character-map.md
new file mode 100644
index 00000000000..d300455a2b4
--- /dev/null
+++ b/developer/docs/help/context/character-map.md
@@ -0,0 +1,136 @@
+---
+title: Using the Character Map
+---
+
+![Character Map tool](../images/ui/frmCharacterMapNew.png)
+
+To display the Character Map:
+
+- Press Ctrl + Shift + c
+- Select **View** → **Character Map**
+
+The Character Map will appear by default docked to the right hand side
+of the IDE but can be undocked or re-docked on any side of the window.
+
+### Using the Character Map
+
+To insert a character:
+1. Position the text cursor where you want to insert the character
+2. Locate the desired character in the Character Map and insert it by:
+ - selecting the character and pressing the Enter key, or
+ - double-clicking on the character, or
+ - right-clicking on the character and selecting Insert Character.
+
+ > ### Note
+ If the Insert Mode is set to Code or Name, pressing Enter or
+ double-clicking on the character will insert the Unicode code or
+ name of the character, not the character itself. These options are
+ most useful when writing or editing keyboard source files (.KMN).
+
+### Insert Modes
+
+There are three insert modes: Code, Character and Name:
+
+- Code: inserts the character's Unicode code
+- Character: inserts the character itself
+- Name: inserts the character's Unicode name
+
+To change the insert mode:
+
+1. Right-click on the Character Map.
+2. Highlight the Insert Mode submenu.
+3. Choose the desired mode from the Insert Mode submenu.
+
+### Character Map Options
+
+When you right click on the Character Map, the following menu options
+are displayed:
+
+Insert Character
+: Inserts the selected character at the cursor position in your
+ document.
+
+Filter...
+: Displays the Filter dialog box. See [Filtering](#toc-filtering) below.
+
+Goto...
+: Displays the Goto dialog box. See Using [Goto](#toc-goto) below.
+
+Font...
+: Allows you to choose a different font for viewing the Character Map.
+ Note: This does not affect the font being used in your document!
+
+Insert Mode
+: Allows you to select the character insert mode you want. See [Insert
+ Modes](#toc-insert-modes) above.
+
+Display Quality
+: Allows you to select a different font display quality for the
+ Character Map. The options are:
+ - Plain: no font smoothing
+ - Antialiased: uses the Windows standard font smoothing
+ - ClearType: uses the Windows ClearType font smoothing method
+
+### Characters and Fonts
+
+When a character is not available in the selected font, the Character
+Map will fall back to **Code2000**, **Code2001**, **Code2002**, **Arial Unicode MS** and
+**Lucida Sans Unicode** fonts. If the character is not found in these
+fallback fonts, the Character Map will use font linking to attempt to
+find a character from other fonts installed on the system, and as a last
+resort, will show a square box if no suitable font can be found.
+
+If a character is not displayed from other than the selected font, it
+will be shown in blue instead of black.
+
+### Filtering
+
+![Character Map Filter dialog](../images/ui/frmCharacterMapFilter.png)
+
+The Character Map can be filtered by Unicode character name or character
+range. Filters can be entered in the Filter By field at the bottom of
+the Character Map; a quick reference user interface dialog to help you
+edit the filter is available by clicking the \[...\] button.
+
+When no filter is applied, the Character Map will show all characters
+from the Unicode 8.0 standard.
+
+All filter strings are case insensitive. Character names use the letters
+`A-Z`, numerals `0-9`, and punctuation underscore (`_`) and hyphen
+(`-`).
+
+Spaces between words in the filter are represented either by underscore
+(`_`) or space (` `) and can be used interchangeably.
+
+A filter can be for a range or on the Unicode Character Name.
+
+The filter format for a range is: `[U+]XXXX-[U+]YYYY`, where `U+` is
+optional, `XXXX` is the starting Unicode value and `YYYY` is the
+finishing Unicode value.
+
+The filtering options for Unicode Character Names are:
+
+### Examples
+
+| | |
+|--------------------------|-------------------------|
+| Filter | Result |
+| `LATIN CAPITAL LETTER A` | Finds all Latin capital A variations |
+| `LATIN * LETTER A` | finds all Latin capital and lower case A variations |
+| `LATIN * LETTER [AEIOU]` | finds all Latin A,E,I,O or U vowel combinations |
+| `LAO` | finds all characters with names starting in "LAO" in the current font |
+| `YI_` | finds all characters with names starting in "YI " - you must use `_` (underscore) and not `( )` space at the end of a search string. Note that without the `_`, this search also matches YIN_YANG. |
+| `LATIN * LETTER A$` | finds only "a" and "A" |
+| `1000-119F` | finds all characters between U+1000 and U+119F (inclusive) - the Myanmar alphabet in this case |
+
+### Goto
+
+The Goto tool can be used to move directly to a specific Unicode
+character or value.
+
+1. Right click on a character and select Goto. The **Enter Unicode character value or name** dialog box will be displayed.
+2. Enter the Unicode character value - e.g. `006B` or `U+006B` OR enter the start of the character name, e.g. `LAO_LETTER` will go
+ to **LAO_LETTER_KO**, being the first character that matches. You
+ can substitute spaces for underscores if you prefer.
+3. Click OK. The character map will attempt to move to the character you specified.
diff --git a/developer/docs/help/context/debug.md b/developer/docs/help/context/debug.md
new file mode 100644
index 00000000000..07c3bb9e143
--- /dev/null
+++ b/developer/docs/help/context/debug.md
@@ -0,0 +1,165 @@
+---
+title: Debug Window
+---
+
+![Debug window - Debug State](../images/ui/frmKeymanWizard_Debug_State.png)
+
+The debug window is shown at the bottom of the keyboard editor when
+debugging the keyboard. There are several user interface areas: The
+debugger input window, the debugger status window, a debugger toolbar
+(usually docked under the menu), and the status bar will show the
+current debugger status.
+
+## Debugger input window
+
+![Debug window - Debug State](../images/ui/frmDebug.png)
+
+The debugger input window is used for typing input to test the keyboard.
+In the top half of this window, input you type while testing your
+keyboard will be displayed, exactly the same as in use, with one
+exception: deadkeys will be shown visually with an
+![OBJ](../images//ui/obj.gif) symbol.
+
+The lower half of the window shows a grid of the characters to the
+virtual left of the insertion point, or the selected characters if you
+make a selection. Deadkeys will be identified in the grid. The grid will
+show characters in right-to-left scripts in backing store order, from
+left to right. If there are more characters in your text than can fit on
+the screen, then only those that fit will be shown in the grid.
+
+## Debug controls
+
+The debug menu and debug toolbar control the debugger.
+
+![Debug toolbar](../images/ui/Debug_Toolbar.png)
+
+![Debug menu](../images/ui/Debug_Menu.png)
+
+Set/Clear breakpoint
+: A breakpoint can be put on a line of code to ask Keyman Developer to
+ stop and show what is happening in the keyboard layout when that
+ line is reached. Typically, you would put a breakpoint on a
+ particular rule, so you could debug an unexpected behaviour on that
+ rule.
+
+Start Debugging
+: Starts the debugger
+
+Stop Debugger
+: Stops the current debug session
+
+Single Step Mode
+: When this option is active, you will step through every group and
+ rule that your keyboard applies when a keystroke is pressed. The
+ Step Forward and Run controls are only relevant in Single Step Mode.
+
+Step Forward
+: Move to the next step in your keyboard code.
+
+Run
+: Stop processing the current keystroke event in single step mode
+ (unless a breakpoint is hit). The next keystroke event will drop
+ into single step mode again.
+
+Pause
+: Use the Pause button or press Shift + Esc to pause the debugger. When
+ the debugger is paused, it will not accept any input, and ordinary shortcut keys (Shift + F5 , Alt + Tab , etc.) will function as usual. Press Pause again to resume debugging.
+
+
+
+## Debug State box
+
+The debug state box shows the internal state of the keyboard
+interpreter.
+
+### State
+
+![Debug window - Debug State](../images/ui/frmKeymanWizard_Debug_State.png)
+
+This window shows the current keystroke state, and the sequence of
+keystrokes that were typed to arrive at this state. Clearing the text in
+the debug window will also clear the keystroke log; as will pressing the
+Restart button.
+
+The Restart button is disabled while stepping through an event.
+
+### Elements
+
+![Debug window - Debug Elements](../images/ui/frmKeymanWizard_Debug_Elements.png)
+
+This shows the elements that make up rule currently being processed: the
+context, the key, and also what the output will be. If the rule uses
+stores, the contents of the store will be shown in the right-hand
+column, with the matched letter in red.
+
+### Call Stack
+
+![Debug window - Debug Call stack](../images/ui/frmKeymanWizard_Debug_CallStack.png)
+
+Here all the lines that have been processed to this point are shown in a
+list. You can double-click on any entry in the list to display the line
+in the keyboard source.
+
+### Deadkeys
+
+![Debug window - Debug Deadkeys](../images/ui/frmKeymanWizard_Debug_Deadkeys.png)
+
+This lists all the deadkeys that are currently in the context. You can
+select one from the list to see it highlighted in the debug input box.
+This information can also be seen in the character grid in the lower
+half of the debugger input window.
+
+### Regression Testing
+
+![Debug window - Debug Regression Test](../images/ui/frmKeymanWizard_Debug_RegressionTest.png)
+
+The idea in regression testing is to record a sequence of keystrokes and
+the output the keyboard produced, in order to test for the same
+behaviour when you make changes to the keyboard.
+
+Use Start Log/Stop Log to record the input and output. You can then use
+Start Test to run the test again, or go the Options menu to clear the log,
+or save or load a test, or use the batch mode to run several tests in a
+row.
+
+If the output produced while running a test is different to that stored
+when recording it, Keyman will halt the test on the line where the
+failure occurred, and activate Single Step mode.
+
+### Status bar
+
+The input status is shown in the second pane of the status bar. It can
+show one of the following messages:
+
+Ready for input
+: The debugger is waiting for more input
+
+Focused for input
+: The debugger is waiting for more input, and the Debug Input window
+ is active
+
+Paused
+: The debugger is paused
+
+Receiving events
+: The debugger is processing input
+
+Debugging
+: The debugger is active during Single Step mode or after a breakpoint
+
+## Test mode
+
+Enter test mode by clicking the **Test Mode**
+button on the debugger toolbar.
+
+The test mode lets you test your keyboard without the debugger being
+active. This lets you test functionality that is not available within
+the debugger - primarily [IMX code](../guides/develop/imx/).
+
+Once in test mode, no debugger controls are available.
+
+You can see the character codes for the current or selected characters
+in the status bar, which can be useful when debugging your keyboard.
diff --git a/developer/docs/help/context/edit-language-example.md b/developer/docs/help/context/edit-language-example.md
new file mode 100644
index 00000000000..0a2307bf027
--- /dev/null
+++ b/developer/docs/help/context/edit-language-example.md
@@ -0,0 +1,44 @@
+---
+title: Add/Edit Language Example Dialog
+---
+
+![Add/Edit Language Example dialog](../images/ui/frmEditLanguageExample.png)
+
+This dialog allows you to create or edit example key sequences to demonstrate
+how to use your keyboard. Examples should be relatively short -- one to five
+words is ideal, but if possible, should demonstrate sequences that may be hard
+for users to discover on their own.
+
+These language examples can be seen live, for example, on
+[keymanweb.com](https://keymanweb.com).
+
+## Language tag
+
+Enter the BCP 47 language tag that this example corresponds to. It should be a
+language tag that is listed in the package file.
+
+## Key sequence
+
+This is a space separated list of keys. For spacebar, use `space`. For modifier
+key combinations, use the modifier key(s) followed by `+` and the key that they
+are pressed together with, e.g. `right-alt+shift+A`.
+
+* modifiers indicated with "+"
+* spacebar is "space"
+* plus key is "shift+=" or "plus" on US English (all other punctuation as per
+ key cap).
+* Hardware modifiers are: "shift", "ctrl", "alt", "left-ctrl", "right-ctrl",
+ "left-alt", "right-alt"
+* Key caps should generally be their character for desktop (Latin script case
+ insensitive), or the actual key cap for touch
+* Caps Lock should be indicated with "caps-on", "caps-off"
+
+## Expected text
+
+Here, put the actual output generated by the key sequence.
+
+## Note
+
+You may include a translation of the text or another note that highlights an
+aspect of how the text is typed.
+
diff --git a/developer/docs/help/context/editor.md b/developer/docs/help/context/editor.md
new file mode 100644
index 00000000000..79e98806c5c
--- /dev/null
+++ b/developer/docs/help/context/editor.md
@@ -0,0 +1,23 @@
+---
+title: Editor Window
+---
+
+Editor windows in Keyman Developer supports standard Windows editing
+keystrokes. Many file formats, including .kmn, .kps, .xml, .html, .js
+and .json, support syntax highlighting. The text editor in Keyman uses
+the Monaco component from Visual Studio Code, so all the functionality
+available in that editor is also available here.
+
+## Special Functionality
+
+Aside from standard Windows keys, the following keystrokes are defined
+for the editor:
+
+| Keystroke | Action |
+|----------------|---------------|
+| Ctrl + Shift + M | Toggle Message Window visibility |
+| Ctrl + Shift + C | Open Character Map |
+| Ctrl + Shift + I | Toggle Character Identifier visibility |
+| Ctrl + F | Find text |
+| Ctrl + H | Replace text |
+| Ctrl + Shift + U | Convert the selection to/from characters to code values|
\ No newline at end of file
diff --git a/developer/docs/help/context/images/banner.png b/developer/docs/help/context/images/banner.png
new file mode 100644
index 00000000000..b141413e345
Binary files /dev/null and b/developer/docs/help/context/images/banner.png differ
diff --git a/developer/docs/help/context/images/character-grid.png b/developer/docs/help/context/images/character-grid.png
new file mode 100644
index 00000000000..dbe78ceca33
Binary files /dev/null and b/developer/docs/help/context/images/character-grid.png differ
diff --git a/developer/docs/help/context/images/config.png b/developer/docs/help/context/images/config.png
new file mode 100644
index 00000000000..ad8a3710315
Binary files /dev/null and b/developer/docs/help/context/images/config.png differ
diff --git a/developer/docs/help/context/images/device-menu.png b/developer/docs/help/context/images/device-menu.png
new file mode 100644
index 00000000000..0c50b2e59af
Binary files /dev/null and b/developer/docs/help/context/images/device-menu.png differ
diff --git a/developer/docs/help/context/images/hamburger-menu.png b/developer/docs/help/context/images/hamburger-menu.png
new file mode 100644
index 00000000000..d014314192a
Binary files /dev/null and b/developer/docs/help/context/images/hamburger-menu.png differ
diff --git a/developer/docs/help/context/images/keyboard-menu.png b/developer/docs/help/context/images/keyboard-menu.png
new file mode 100644
index 00000000000..cb15d6d3469
Binary files /dev/null and b/developer/docs/help/context/images/keyboard-menu.png differ
diff --git a/developer/docs/help/context/images/lexical-model-test.png b/developer/docs/help/context/images/lexical-model-test.png
new file mode 100644
index 00000000000..4b434fc568f
Binary files /dev/null and b/developer/docs/help/context/images/lexical-model-test.png differ
diff --git a/developer/docs/help/context/images/model-menu.png b/developer/docs/help/context/images/model-menu.png
new file mode 100644
index 00000000000..4572b234f6a
Binary files /dev/null and b/developer/docs/help/context/images/model-menu.png differ
diff --git a/developer/docs/help/context/images/on-screen-keyboard.png b/developer/docs/help/context/images/on-screen-keyboard.png
new file mode 100644
index 00000000000..4aaf2aef520
Binary files /dev/null and b/developer/docs/help/context/images/on-screen-keyboard.png differ
diff --git a/developer/docs/help/context/images/server-iphone.png b/developer/docs/help/context/images/server-iphone.png
new file mode 100644
index 00000000000..d6ce6589034
Binary files /dev/null and b/developer/docs/help/context/images/server-iphone.png differ
diff --git a/developer/docs/help/context/images/status-bar.png b/developer/docs/help/context/images/status-bar.png
new file mode 100644
index 00000000000..7439322aab5
Binary files /dev/null and b/developer/docs/help/context/images/status-bar.png differ
diff --git a/developer/docs/help/context/images/taskbar-menu.png b/developer/docs/help/context/images/taskbar-menu.png
new file mode 100644
index 00000000000..3c352a24a7d
Binary files /dev/null and b/developer/docs/help/context/images/taskbar-menu.png differ
diff --git a/developer/docs/help/context/images/taskbar.png b/developer/docs/help/context/images/taskbar.png
new file mode 100644
index 00000000000..8f4611fe2e3
Binary files /dev/null and b/developer/docs/help/context/images/taskbar.png differ
diff --git a/developer/docs/help/context/images/text-area.png b/developer/docs/help/context/images/text-area.png
new file mode 100644
index 00000000000..4255e550196
Binary files /dev/null and b/developer/docs/help/context/images/text-area.png differ
diff --git a/developer/docs/help/context/index.md b/developer/docs/help/context/index.md
new file mode 100644
index 00000000000..04028b35c1e
--- /dev/null
+++ b/developer/docs/help/context/index.md
@@ -0,0 +1,59 @@
+---
+title: Context Help
+---
+
+[Project Window](project)
+
+[Keyboard Editor](keyboard-editor)
+
+[Model Editor](model-editor)
+
+[Debug Window](debug)
+
+[LDML Keyboard Debug Window](ldml-debug)
+
+[LDML Keyboard Editor](ldml-editor)
+
+[Package Editor](package-editor)
+
+[Add/Edit Language Example dialog](edit-language-example)
+
+[Text Editor](editor)
+
+[New File Dialog](new)
+
+[New Project Dialog](new-project)
+
+[New Project Parameters Dialog](new-project-parameters)
+
+[New Lexical Model Project Parameters Dialog](new-lm-project-parameters)
+
+[New LDML Project Parmaeters dialog](new-ldml-project-parameters)
+
+[About Dialog](about-tike)
+
+[Virtual Key Identifier](key-test)
+
+[Options Dialog](options)
+
+[KMComp Command-line Options](kmcomp)
+
+[KMConvert Command-line Options](kmconvert)
+
+[kmc Command-line Options](kmc)
+
+[kmlmc Command-line Options](kmlmc)
+
+[kmlmi Command-line Options](kmlmi)
+
+[kmlmp Command-line Options](kmlmp)
+
+[Character Map](character-map)
+
+[Message Window](messages)
+
+[Keyman Developer Server](server) (web test host)
+
+[Keyman Developer](keyman-developer)
+
+[Select Web fonts](select-web-fonts)
diff --git a/developer/docs/help/context/key-test.md b/developer/docs/help/context/key-test.md
new file mode 100644
index 00000000000..d51ed334372
--- /dev/null
+++ b/developer/docs/help/context/key-test.md
@@ -0,0 +1,21 @@
+---
+title: Virtual Key Identifier
+---
+
+![Virtual Key Identifier dialog](../images/ui/frmKeyTest.png)
+
+This dialog lets you check the virtual key code for any key combination
+(except Window reserved key combinations such as
+Alt + Tab ). You can then
+insert the virtual key code into the last active edit window at the
+current cursor position.
+
+Press
+Ctrl / Shift / Alt
+and the key you wish to discover. You can see the virtual key codes for
+left and right Ctrl / Alt combinations by checking the "Distinguish between left and right ctrl/alt" checkbox.
+
+To close the dialog, click the Close button or press
+Shift + Esc .
+
+Press Shift + Enter to insert the current virtual key code into your source at the insertion point.
\ No newline at end of file
diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md
new file mode 100644
index 00000000000..ef43a9b4e87
--- /dev/null
+++ b/developer/docs/help/context/keyboard-editor.md
@@ -0,0 +1,632 @@
+---
+title: Keyboard Editor
+---
+
+The keyboard editor is the heart of Keyman Developer. This window allows
+you to edit all aspects of a keyboard layout, from the visual layout of
+the keyboard, to logic rules, to touch interactions, and more.
+
+The editor is divided into multiple tabs. Initially, only three tabs
+will be visible: Details, Layout, and Build. A keyboard can have
+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.
+
+![Keyboard Editor - New file, Details tab](../images/ui/frmKeymanWizard_New.png)
+
+> ### 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
+
+A keyboard contains multiple source files. The following table lists the
+file types and which tabs are used to edit them. The filenames shown are
+defaults and can be modified, by editing the corresponding line in the
+source file (and renaming the file). However, where possible the default
+names should be used.
+
+| File type | Tabs | Feature | Store name | Description |
+|-------------------|-----------------|--------------------|----------------------|------------------------|
+| `file.kmn` | Details, Layout | | The primary keyboard source file. Required. | |
+| `file.kvks` | On-Screen | Desktop On-Screen Keyboard | [`&visualkeyboard`](/developer/language/reference/visualkeyboard) | The visual presentation of the keyboard for desktop computers. |
+| `file.ico`, `file.bmp` | Icon | Icon | [`&bitmap`](/developer/language/reference/bitmap) | An icon that represents the keyboard in the user interface on desktop computers. |
+| `file.keyman-touch-layout` | Touch Layout | Touch-Optimised Keyboard | [`&layoutfile`](/developer/language/reference/layoutfile) | The touch layout visual description file |
+| `file-code.js` | Embedded JS | Embedded Javascript | [`&kmw_embedjs`](/developer/language/reference/kmw_embedjs) | Additional Javascript code for [IMX](../guides/develop/imx) functionality |
+| `file.css` | Embedded CSS | Embedded CSS | [`&kmw_embedcss`](/developer/language/reference/kmw_embedcss) | Additional CSS stylesheet for custom touch and web layout styling |
+| `file-help.htm` | Web Help | Web Help | [`&kmw_helpfile`](/developer/language/reference/kmw_helpfile) | HTML file that replaces the On Screen Keyboard on desktop web layouts |
+| `file-codes.txt` | Include Codes | Include Codes | [`&includecodes`](/developer/language/reference/includecodes) | Code dictionary for use with [named constants](/developer/language/guide/constants) |
+
+Three tabs include both visual and code editors: the Layout, Touch
+Layout and JSON Metadata tabs. Changes to one view will be immediately
+reflected in the other view. The code in the Layout tab is important
+because that reflects both the Details and the Layout tab.
+
+## Details tab
+
+![Keyboard Editor - Details tab](../images/ui/frmKeymanWizard_Details.png)
+
+The Details tab grows as you add more options to a keyboard. The fields
+here are:
+
+Name
+: The name of the keyboard, corresponding to the [`&name` store](/developer/language/reference/name).
+
+Targets
+: The intended target devices and operating systems, corresponding to
+ the [`&targets` store](/developer/language/reference/targets).
+ Changing the targets will hide or show parts of the Details and
+ Compile tabs. If no targets are selected, Keyman Developer will
+ treat the keyboard as a Windows-only keyboard, for backward
+ compatibility reasons.
+
+Copyright
+: Enter the details of the copyright owner for the keyboard.
+ [`©right` store](/developer/language/reference/copyright).
+
+Message
+: Enter a message that will be shown at install time for the keyboard
+ (on Windows, macOS). [`&message` store](/developer/language/reference/message).
+
+Keyboard is right-to-left
+: Check this box to indicate that the keyboard is a right-to-left
+ keyboard, for web and touch layouts. [`&kmw_rtl` store](/developer/language/reference/kmw_rtl).
+
+Web Help Text
+: A single line of text, with basic HTML allowed, shown at the bottom
+ of the desktop web On Screen Keyboard. [`&kmw_helptext` store](/developer/language/reference/kmw_helptext).
+
+Keyboard version
+: The version of the keyboard. Read the reference documentation for
+ [`&keyboardversion` store](/developer/language/reference/keyboardversion) to understand
+ the legal values for this field. This is not the same as the
+ [`&version` store](/developer/language/reference/version), which
+ controls the Keyman (and Keyman Developer) version for which a
+ keyboard is designed.
+
+Comments
+: This field corresponds to the first lines of comments in the
+ keyboard source, and is visible only to the keyboard designer.
+
+Features
+: The Features grid controls which additional file components are
+ included in the keyboard, as listed in the Component Files section
+ above. Each of the features relates to a system store. Adding a
+ feature will add an extra tab to the editor, and add the
+ corresponding store to the keyboard source. Removing a feature will
+ not delete the component file, but will just remove the store from
+ the keyboard source.
+
+## Layout tab
+
+![Keyboard Editor - Layout tab, Design view](../images/ui/frmKeymanWizard_Layout_Design.png)
+
+The Layout tab gives you a simple interface to quickly create a keyboard
+using a visual representation of a desktop/laptop computer keyboard. You
+can drag and drop characters from the character map to create keyboard
+layouts. You cannot access most of Keyman's more powerful features from
+the Layout Design view, but it will be useful to get you started on your
+design.
+
+Each key can have zero or more characters assigned to it. Each key can
+be assigned a different set of characters for each shift state. You can
+change the shift state by clicking on the Shift, Ctrl, and Alt keys.
+
+There are two ways to assign characters to the keys:
+
+- Click on a key, and then type the character's code into the Unicode
+ Character Value(s) field, or the type/paste the character itself
+ into the Output character(s) field.
+- Select the character from the Character Map and drag and drop it
+ 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.
+
+
+> ### 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,
+ or it can distinguish between them. The Design view allows one or
+ the other mode (in Code view, you can use both interchangeably).
+
+Display 102nd Key (as on European keyboards)
+: European keyboards have one extra key that is not on US keyboards.
+ 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
+ Windows layout, the 102nd key will always be visible in the
+ designer. 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.
+
+You can press and release Ctrl to select
+another key on the keyboard using your keyboard.
+
+
+
+![Keyboard Editor - Layout tab, Code view](../images/ui/frmKeymanWizard_Layout_Code.png)
+
+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
+additional logic and complex keyboard layouts will be edited entirely in
+the Code view.
+
+See the [Editor topic](editor) for more information on how to use the
+editor shown within this view.
+
+## On-Screen tab
+
+![Keyboard Editor - On-Screen tab](../images/ui/OnScreenKeyboard.png)
+
+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
+with your keyboard. The visual representation is used only in desktop
+and desktop web; however if no touch layout is defined, this layout will
+be synthesized into a touch layout automatically.
+
+An On-Screen keyboard is optional but in most keyboards is recommended.
+The On-Screen keyboard may not always match the actual layout
+identically, because you may choose to hide some of the details of
+encoding from the interface presented to the user. The user can also
+choose to print the layout.
+
+This keyboard layout can also be printed or included in HTML or other
+documentation. The editor allows you to export the file to HTML, PNG or
+BMP formats.
+
+Fill from layout
+: Compiles the keyboard file, and processes each possible keystroke in
+ the keyboard file to automatically generate an On-Screen Keyboard
+ that matches the layout. This can be used to effectively
+ pre-populate the On-Screen Keyboard and reduces the complexity of
+ designing it from scratch.
+
+Import
+: Imports an On-Screen Keyboard from an XML file.
+
+Export
+: Exports the On-Screen Keyboard to an XML file.
+
+Text
+: The key cap for the selected key
+
+Bitmap
+: A bitmap that is displayed on the selected key (not recommended, as
+ it will not scale cleanly)
+
+Display underlying layout characters
+: If checked, then the base or underlying layout will show in small
+ letters at the top left of each key cap. This can be helpful to
+ provide users with a guide as to the position of each key on the
+ keyboard, especially for non-Latin scripts.
+
+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.
+ 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
+ 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
+ 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.
+
+Auto-fill underlying layout
+: When the Fill from layout button is clicked, if this option is
+ checked, then keys without corresponding rules in the Layout will be
+ filled with the base layout character.
+
+## Touch Layout tab
+
+![Keyboard Editor - Touch Layout tab, Design view](../images/ui/TouchLayout_Design.png)
+
+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
+Keyboard Editor conceptually, but has a number of additional features
+specific to touch. Keys on the touch layout trigger rules within the
+normal Keyman keyboard; if no rule is defined for a given key, it will
+be given output if it has a standard code beginning with `K_`, or if it
+is a Unicode value code, starting with `U_`.
+
+Follow this
+[guide](../guides/develop/touch-keyboard-tutorial/making-touch-keyboard)
+for learning how to create a Touch Layout keyboard.
+
+In Design view, the editor can show a number of different device types,
+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.
+
+![Keyboard Editor - Touch Layout tab, Design view closeup](../images/ui/TouchLayout_Design_2.png)
+
+### Left sidebar controls
+
+Template...
+: The Template button allows you to choose a standard layout of keys
+ from a predefined set. These layouts move the standard set of keys
+ around and between layers to reduce the number of keys on each
+ layer, optimising for mobile phone or other sizes. When Keyman
+ Developer switches to a new template, it will transfer key
+ definitions to the new layout as far as possible, but if keys on the
+ current layout are not in the new template, their definitions will
+ be lost. Therefore, selecting an appropriate template early in the
+ development process is suggested.
+
+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
+ development time considerably. Once you have imported, you will
+ probably want to select a new template to transform the
+ desktop-oriented design to a tablet or phone design automatically.
+
+View Controls
+: This selector allows you to choose the simulated view of the
+ keyboard, either landscape or portrait, for a number of devices. The
+ presented keyboard may not be 100% identical to the final layout as
+ seen on the device, but this allows you to get a feel for the design
+ before testing.
+
+Platform
+: The platform controls allow you to select and add or remove platform
+ support for a given layout. If a platform is not defined, then
+ Keyman Engine will transform the layout from another platform
+ automatically, so you don't necessarily need to define layouts for
+ each platform. If the 'Display underlying' checkbox is checked, then
+ the base or underlying layout will show in small letters at the top
+ left of each key cap. This can be helpful to provide users with a
+ guide as to the position of each key on the keyboard, especially for
+ non-Latin scripts.
+
+Layer
+: Each layout for each platform is made up of one or more layers of
+ keys. Each layer can have a default shift state associated with it,
+ which allows keys to trigger specific rules in the Layout code.
+
+### Keyboard area
+
+On the right of the sidebar is the keyboard design area. This shows a device with
+a presentation of the key layout. Within the key layout, you can click
+on any key to edit it. There are a number of controls:
+
+Red circle with an X
+: This button to the top right of the key will delete the key from the
+ row; if it is the last key on the row, the entire row will be
+ deleted.
+
+Green arrow with a +
+: These buttons will insert a key to the left or right, or a single
+ key on a new row above or below the selected key. Adding extra keys
+ scales the entire keyboard.
+
+Dragging the right hand side of the key
+: Dragging the right hand side of the key will resize the key;
+ resizing the key will rescale the entire keyboard so it still fits
+ within the device screen.
+
+Metrics
+: The metrics displayed on the right hand side of the keyboard show
+ the virtual width of each row of the keyboard, and the number of
+ keys. For small devices, 10 keys is the maximum recommended number
+ in a row; each key has a standard width of 100. While each row can
+ be a different length, the last key in the row will be stretched to
+ balance the final design; 'spacer' keys can be used to leave a
+ visual gap on the right hand side of the keyboard. You should aim to
+ make each row the same total width for consistent results.
+
+### Right sidebar controls
+
+Next to the keyboard area is another control bar for editing details of
+the selected key.
+
+Keycap Value
+: Assign a type to a key. This is to specifies which keys are Text, AltGr, Shift, Menu, TabLeft, ZWSp and more.
+
+Text
+: You can drop characters from Character Map directly into the key cap
+ edit box, or type directly.
+
+Text Unicode
+: Clicking on a text unicode will focus the unicode edit box
+ instead of the text edit box.
+
+Hint and Hint Unicode
+: Add any Unicode to indicate the presence of long-press keys for that key, and the hint will be visible in the top right corner of the key.
+
+Key Type
+: The general appearance of each key is determined by the key type,
+ which is selected (in Keyman Developer 10) from a drop-down list:
+
+ | Key Type | Description |
+ |----------------------|----------------------------|
+ | Default (normal) key | A standard letter or character key on the keyboard |
+ | Special (shift) key | The grey control type keys, layer shifts, Enter, Backspace, etc. |
+ | Special (Active) | A variation on the Special keys, which shows a highlight, typically used to indicate that a shift key for example is down. |
+ | Dead-key | While this key type has no difference in function to default keys, it will have a different style to indicate that it is different; the logic in the keyboard source may well be a deadkey, for example. |
+ | Blank | This type of key cannot be selected in use and shows as a blank key. |
+ | Spacer | Leaves a space in the keyboard at the point it is inserted, in the background colour of the keyboard area. |
+
+Modifier
+: Determines the state that Keyman Engine will receive from the
+ key stroke, such as Default, Right-Alt, Shift, Right-Alt Shift, or Caps. This makes it simpler to design a single keyboard that works across both desktop and mobile interfaces.
+
+ID
+: Each key must be given an identifying ID which is unique to
+ the key layer. ID by and large correspond to the virtual key
+ codes used when creating a keyboard program for a desktop keyboard,
+ and should start with `K_`, for keys mapped to standard Keyman
+ virtual key names, e.g. `K_HYPHEN`, and `T_` or `U_` for
+ user-defined names, e.g. `T_ZZZ`.
+ If keyboard rules exist matching the key code in context, then the output from the key will be
+ determined by the processing of those rules. It is usually best to
+ include explicit rules to manage the output from each key, but if no
+ rules matching the key code are included in the keyboard program,
+ and the key code matches the pattern `U_xxxx[_yyyy...]`
+ (where `xxxx` and `yyyy` are 4 to 6-digit hex strings), then the
+ Unicode characters `U+xxxx` and `U+yyyy` will be output. As of
+ Keyman 15, you can use more than one Unicode character value in the
+ id (earlier versions permitted only one). The ID is always
+ required, and a default code will usually be generated automatically
+ by Keyman Developer.
+
+ - `K_xxxx` is used for a standard Keyman for Windows's key name, e.g.
+ `K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
+ Many of the `K_` ids have overloaded output behaviour, for
+ instance, if no rule is matched for `K_W`, Keyman will output
+ 'w' when it is touched. The standard key names are listed in
+ [Virtual Keys and Virtual Character Keys](/developer/language/guide/virtual-keys). Typically,
+ you would use only the "common" virtual ID.
+ - `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`.
+ If you wanted to use it, `T_ENTER` would also be valid. If no
+ rule matches it, the key will have no output behaviour.
+ - `U_####[_####]` is used as a shortcut for a key that
+ will output those Unicode values, if no rule matches it. This is
+ similar to the overloaded behaviour for `K_` ids. Thus `####`
+ must be valid Unicode characters. E.g. `U_0259` would generate a
+ schwa if no rule matches. It is still valid to have a rule such
+ as `+ [U_0259] > ...`
+
+Padding Left
+: Padding to the left of each key can be adjusted, and specified as a
+ percentage of the default key width (100). If not specified, a
+ standard padding of 15% of the key width is used between adjacent
+ keys.
+
+Width
+: The layout is scaled to fit the widest row of keys in the device
+ width, assuming a default key width of 100 units. Keys that are to
+ be wider or narrower than the default width should have width
+ specified as a percentage of the default width. For any key row that
+ is narrower than the widest row, the width of the last key in the
+ row will be automatically increased to align the right hand side of
+ the key with the key with the right edge of the keyboard. However,
+ where this is not wanted, a "spacer" key can be inserted to leave a
+ visible space instead. As shown in the above layouts, where the
+ spacer key appears on the designer screen as a narrow key, but will
+ not be visible in actual use.
+
+Next layer
+: The virtual keys `K_SHIFT`, `K_CONTROL`, `K_MENU`, etc. are normally
+ used to switch to another key layer, which is implied by the ID. The left and right variants of those ID, and also
+ additional layer-switching keys mentioned above (`K_NUMERALS`,
+ `K_SYMBOLS`, `K_CURRENCIES`, `K_ALTGR`) can also be used to
+ automatically switch to the appropriate key layer instead of
+ outputting a character. However, it is sometimes useful for a key to
+ output a character first, then switch to a new layer, for example,
+ switching back to the default keyboard layer after a punctuation key
+ on a secondary layer had been used. Specifying the `nextlayer` for a
+ key allows a different key layer to be selected automatically
+ following the output of the key. Of course, that can be manually
+ overridden by switching to a different layer if preferred.
+
+Drag and drop
+: Keys can be moved around the keyboard by dragging them with the
+ mouse; a ghost landing box will appear beneath the key when it can
+ be dropped. If the key is dropped in an invalid area, no action will
+ occur.
+
+Double-click
+: Double clicking on a key with a "Next Layer" defined will switch to
+ that layer.
+
+### Middle bar controls
+
+#### Long-press keys area
+
+The keys in the long press area cannot be resized but the controls
+otherwise work the same as in the main keyboard area. The keys cannot be
+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.
+
+![Keyboard Editor - Touch Layout tab, Code view](../images/ui/frmKeymanWizard_TouchLayout_Code.png)
+
+The Code view allows you to edit the JSON source of the touch layout
+file. This makes certain operations simpler, such as batched rearranging
+of keys, or using another text editor to make bulk changes. The Code
+view uses the standard [editor](editor).
+
+The format of the layout file is described in [Creating a touch keyboard layout guide](../guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty).
+
+#### Long-press, Flicks, and Multitaps controls
+
+Underneath the keyboard area is the Long-press, Flicks, and Multitaps controls.
+
+![Long-press, Flicks, and Multitaps layout](../images/ui/LongPress_Flicks_Multitaps.png)
+
+Red circle with an X
+: This button to the top right of the key will delete the key from the
+ row; if it is the last key on the row, the entire row will be
+ deleted.
+
+Green arrow with a + *(Applies to Long-press and Multitaps)*
+: These buttons will insert a key to the left or right, or a single
+ key on a new row above or below the selected key. Adding extra keys
+ scales the entire keyboard.
+
+Drag and drop *(Applies to Long-press and Multitaps)*
+: Keys can be moved around the keyboard by dragging them with the
+ mouse; a ghost landing box will appear beneath the key when it can
+ be dropped. If the key is dropped in an invalid area, no action will
+ occur.
+
+#### Right sidebar controls
+
+Next to the three controls is another control bar for editing details of
+the selected key for different Gesture Type. The only difference between this control and the [keyboard right sidebar](#toc-right-sidebar-controls) controls is:
+* Include Gesture Type box and Default selection check box.
+* **No** Hint, Hint Unicode, Padding Left, and Width.
+
+## Icon tab
+
+![Keyboard Editor - Icon tab](../images/ui/frmKeymanWizard_Icon.png)
+
+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:
+
+Pencil
+: Freehand drawing with the selected colour; use right mouse button
+ for secondary colour.
+
+Line
+: Draw a straight line
+
+Open box
+: Draw an outline box
+
+Filled box
+: Draw a filled box in current colour
+
+Open circle
+: Draw an outline circle or ellipse
+
+Filled circle
+: Draw a filled circle or ellipse in the current colour
+
+Fill
+: Fill from the current point all contiguous pixels of the same colour
+
+Text
+: Use this tool to add a letter or letters to the icon. Click in the
+ lower left of the icon area and a window opens where you can type
+ the text you want. You can also change the quality of the font as
+ well as the font properties by clicking on the Change button. Note
+ that to change the color of the font, you need to change the color
+ setting in the font properties window.
+
+The icon can be exported or imported to/from various other formats. The
+transparent colour is the top left colour in the grid with an X in the
+box. The icon can be rotated in any direction with the arrow controls
+below the toolbox; this will not lose data from the icon but will
+instead rotate the edge moving off the grid to the other side of the
+grid.
+
+## Embedded JS tab
+
+This tab allows you to edit JavaScript code in the file referenced by
+the [`&kmw_embedjs`](/developer/language/reference/kmw_embedjs) store,
+implementing [IMX functionality](../guides/develop/imx) of the keyboard.
+
+## Embedded CSS tab
+
+This tab allows you to edit the Cascading Style Sheet rules in the file
+referenced by the
+[`&kmw_embedcss`](/developer/language/reference/kmw_embedcss) store for
+the keyboard.
+
+## Web Help tab
+
+This tab allows you to add a custom HTML snippet to replace the desktop
+on screen keyboard, for example useful if the keyboard uses sequences of
+keys to produce characters.
+
+## Include Codes tab
+
+This tab allows you to edit the [named constants](/developer/language/guide/constants) for the keyboard.
+
+## Build tab
+
+![Keyboard Editor - Build tab](../images/ui/BuildTab.png)
+
+A set of common controls is across the top of this tab:
+
+Compile Keyboard
+: You can compile the keyboard for testing and installation by
+ clicking the Compile button, or
+ selecting Keyboard, Compile Keyboard. This will compile the keyboard
+ for all the selected Targets from the Details tab.
+
+Start Debugging
+: To test your keyboard within Keyman Developer, press F5 to start the
+ [debugger](debug), or press the Start Debugging button. This is
+ suitable for testing desktop layouts.
+
+Open source, build, and project folder
+: Opens the folder which contains the keyboard source, build, and project files.
+
+
+
+### Windows, macOS, and Linux Targets
+
+If your keyboard is designed to target Windows and macOS, then the
+following buttons will also be visible:
+
+Install
+: Installs the keyboard for use with Keyman Desktop on your computer
+
+Uninstall
+: Uninstalls the keyboard from Keyman Desktop if previously installed
+
+
+
+### Web and Mobile Targets
+
+If your keyboard is designed to target KeymanWeb, mobile or tablet
+devices, the following controls will also be visible:
+
+Test Keyboard on Web
+: Starts the Keyman Developer Web Server for the current keyboard. This will list the various IP addresses and hostnames that Keyman Developer is listening on
+
+Configure...
+: Pressing this allows the [Options](./options) dialog to pop-up.
+
+Open in browser
+: Starts your default browser with the selected address to allow
+ testing of the keyboard directly
+
+Send to email
+: Sends the list of web addresses as an email so that you can more
+ easily open the test site on your mobile device, or share with
+ someone else on your network.
+
+Copy link
+: Once the IP addresses are shown, feel free to copy any one of the links.
+
+To test on other platforms, open one of the debug host addresses listed
+on your device. Your device will need to be on the same network as the
+Keyman Developer computer.
+
+You can test within the web browser on your device, or in the native app
+if you have it installed: just click the Install button on the debugger
+web page.
+
+The Send to email function makes it easier to open the addresses on your
+mobile device without having to enter them manually.
+
+Learn more about [testing on the web and mobile devices](../guides/test/).
\ No newline at end of file
diff --git a/developer/docs/help/context/keyman-developer.md b/developer/docs/help/context/keyman-developer.md
new file mode 100644
index 00000000000..38544801847
--- /dev/null
+++ b/developer/docs/help/context/keyman-developer.md
@@ -0,0 +1,5 @@
+---
+title: Keyman Developer Main Window
+---
+
+- [Project Window](project)
\ No newline at end of file
diff --git a/developer/docs/help/context/kmc.md b/developer/docs/help/context/kmc.md
new file mode 100644
index 00000000000..088b04ca0d3
--- /dev/null
+++ b/developer/docs/help/context/kmc.md
@@ -0,0 +1,5 @@
+---
+title: kmc Command Line Compiler Toolset
+---
+
+* [kmc documentation](../reference/kmc)
\ No newline at end of file
diff --git a/developer/docs/help/context/kmcomp.md b/developer/docs/help/context/kmcomp.md
new file mode 100644
index 00000000000..0a4b382bbce
--- /dev/null
+++ b/developer/docs/help/context/kmcomp.md
@@ -0,0 +1,7 @@
+---
+title: kmcomp - Command Line Compiler (deprecated)
+---
+
+kmcomp has been replaced by [kmc](../reference/kmc).
+
+* [Migrate from kmcomp to kmc](../reference/kmc/cli/kmcomp-migration)
\ No newline at end of file
diff --git a/developer/docs/help/context/kmconvert.md b/developer/docs/help/context/kmconvert.md
new file mode 100644
index 00000000000..d620841473d
--- /dev/null
+++ b/developer/docs/help/context/kmconvert.md
@@ -0,0 +1,125 @@
+---
+title: KMConvert Command Line Keyboard Conversion Utility
+---
+
+KMConvert generates keyboards and models from templates, and converts keyboard
+layouts between different formats.
+
+The [New Project dialog](new-project) in Keyman Developer provides a graphical
+version of the functionality in KMConvert.
+
+In a default installation of Keyman Developer on Windows, it is located in
+`%ProgramFiles(x86)%\Keyman\Keyman Developer`, and should be on your `PATH`.
+KMConvert will generate a full Keyman template project from the imported layout
+or data, in a new folder named with the ID of the keyboard. It will not
+overwrite an existing folder.
+
+The following parameters are available:
+
+```shell
+kmconvert import-windows -klid [additional-options]
+kmconvert template -id [additional-options]
+kmconvert lexical-model -id-author -id-language -id-uniq [additional-options]
+```
+
+### Available conversion modes
+
+The conversion mode must be specified as the first parameter.
+
+`import-windows`
+: Imports a Windows keyboard into a new Keyman keyboard project. The `-klid`
+parameter is required. This mode only functions on Windows platforms.
+
+`template`
+: Creates a blank keyboard project in the repository template format. The `-id`
+parameter is required.
+
+`lexical-model`
+: Creates a wordlist lexical model project in the repository template format.
+The `-id-author`, `-id-language`, and `-id-uniq` parameters are required.
+
+### General parameters
+
+`-author `
+: Name of author of the keyboard or model, no default.
+
+`-copyright `
+: Copyright string for the keyboard or model, defaults to `Copyright (C)`,
+ used everywhere except license file.
+
+`-full-copyright `
+: Longer copyright string for the keyboard or model, defaults to
+ `Copyright (C) yyyy`, used in license file.
+
+`-languages `
+: Space-separated list of BCP 47 tags, e.g. `en-US tpi-PG`
+
+`-name `
+: Name of the keyboard or model, e.g. `My First Keyboard`, `%s Basic`. Format
+strings are only valid in `import-windows` mode; `%s` will be replaced with the
+friendly English name of the imported keyboard.
+
+`-nologo`
+: Don't show the program description and copyright banner
+
+`-o `
+: The target folder to write the keyboard project into, defaults to `.\`
+
+`-version `
+: Version number of the keyboard or model, defaults to `1.0`
+
+### `template` and `import-windows` parameters
+
+`-id `
+: The id of the keyboard to create. Required for `template`, optional for
+`import-windows`, not valid for `-lexical-model`. In `import-windows` mode, you
+can use a format string with the placeholder `%s` which will be replaced with
+the source filename, e.g. `kbdus`. If not specified, then the source filename
+will form the id of the generated keyboard.
+
+`-targets `
+: Space-separated list of [targets](/developer/language/reference/targets), e.g.
+`linux windows phone`
+
+### `import-windows` parameters
+
+`-klid `
+: The KLID of the keyboard to import, per LoadKeyboardLayout. This must be an
+eight digit hex identifier, such as `00000409` for English (US), kbdus.dll. This
+parameter is only valid for `import-windows` conversion mode.
+
+### Lexical model parameters
+
+`-id-author `
+: Identifier for author of model
+
+`-id-language `
+: Single BCP 47 tag identifying primary language of model
+
+`-id-uniq `
+: Unique name for the model
+
+Note: model identifiers are constructed from parameters:
+`..`
+
+The values for `-id-author` and `-id-uniq` must be valid tokens: lower case a-z, 0-9, and underscore. These should also start with a letter or underscore.
+
+### Examples
+
+Create a lexical model for Tok Pisin:
+
+```shell
+kmconvert lexical-model -id-author james -id-language tpi-PG -id-uniq tokpisin -name "Tok Pisin" -languages tpi-PG
+```
+
+Create a keyboard for Javanese from a template:
+
+```shell
+kmconvert template -id sample -author "James Example" -name "My Javanese Keyboard" -copyright "Copyright (C) James Example" -full-copyright "Copyright (C) 2021 James Example" -languages jv-java
+```
+
+Create a new keyboard based on the Windows German keyboard:
+
+```shell
+kmconvert import-windows -klid 00000407 -id german_plus -name "German Plus" -copyright "Copyright (C) James Example" -full-copyright "Copyright (C) 2021 James Example" -author "James Example"
+```
diff --git a/developer/docs/help/context/kmlmc.md b/developer/docs/help/context/kmlmc.md
new file mode 100644
index 00000000000..33edf500f5c
--- /dev/null
+++ b/developer/docs/help/context/kmlmc.md
@@ -0,0 +1,5 @@
+---
+title: kmlmc - Command Line Lexical Model Compiler (deprecated)
+---
+
+kmlmc has been replaced by [kmc](../reference/kmc).
diff --git a/developer/docs/help/context/kmlmi.md b/developer/docs/help/context/kmlmi.md
new file mode 100644
index 00000000000..dc47cbbc39a
--- /dev/null
+++ b/developer/docs/help/context/kmlmi.md
@@ -0,0 +1,5 @@
+---
+title: kmlmi - Command Line Lexical Model model_info Compiler (deprecated)
+---
+
+kmlmi has been replaced by [kmc](../reference/kmc).
diff --git a/developer/docs/help/context/kmlmp.md b/developer/docs/help/context/kmlmp.md
new file mode 100644
index 00000000000..3442d8f1424
--- /dev/null
+++ b/developer/docs/help/context/kmlmp.md
@@ -0,0 +1,5 @@
+---
+title: kmlmp - Command Line Lexical Model Package Compiler (deprecated)
+---
+
+kmlmp has been replaced by [kmc](../reference/kmc).
diff --git a/developer/docs/help/context/ldml-debug.md b/developer/docs/help/context/ldml-debug.md
new file mode 100644
index 00000000000..e3eb2f717be
--- /dev/null
+++ b/developer/docs/help/context/ldml-debug.md
@@ -0,0 +1,25 @@
+---
+title: LDML Keyboard Debug Window
+---
+
+The LDML keyboard debug window is shown at the bottom of an LDML keyboard editor
+when testing the keyboard. In version 17.0, the LDML keyboard debug window is
+a simple test window, without support for interactive debugging, unlike the
+Keyman keyboard debug window.
+
+## Debugger input window
+
+![Debug window - Debug State](../images/ui/frmDebug.png)
+
+The debugger input window is used for typing input to test the keyboard.
+In the top half of this window, input you type while testing your
+keyboard will be displayed, exactly the same as in use, with one
+exception: deadkeys will be shown visually with an
+![OBJ](../images//ui/obj.gif) symbol.
+
+The lower half of the window shows a grid of the characters to the virtual left
+of the insertion point, or the selected characters if you make a selection.
+Markers will be identified in the grid, but only by numeric value in version
+17.0. The grid will show characters in right-to-left scripts in backing store
+order, from left to right. If there are more characters in your text than can
+fit on the screen, then only those that fit will be shown in the grid.
\ No newline at end of file
diff --git a/developer/docs/help/context/ldml-editor.md b/developer/docs/help/context/ldml-editor.md
new file mode 100644
index 00000000000..94eb3ecc45e
--- /dev/null
+++ b/developer/docs/help/context/ldml-editor.md
@@ -0,0 +1,18 @@
+---
+title: LDML Keyboard Editor Window
+---
+
+![Keyboard Editor - New file, Details tab](../images/ui/frmLDMLEditor.png)
+
+The LDML Keyboard file format is new in Keyman Developer 17. It is an
+XML file, and follows the specification at https://www.unicode.org/reports/tr35/tr35-keyboards.html
+
+The LDML editor in this release is very basic, supporting syntax highlighting
+and basic editing, but specific support for the keyboard3 format is scheduled
+for release in version 18.0.
+
+LDML keyboards can be tested by pressing F5 . This opens the [LDML Debug window](ldml-debug).
+
+The text editor supports standard Windows editing keystrokes. It uses the Monaco
+component from Visual Studio Code, so much of the functionality available in
+that editor is also available here.
diff --git a/developer/docs/help/context/messages.md b/developer/docs/help/context/messages.md
new file mode 100644
index 00000000000..9c9ad133359
--- /dev/null
+++ b/developer/docs/help/context/messages.md
@@ -0,0 +1,18 @@
+---
+title: Message Window
+---
+
+![Message window](../images/ui/frmMessages.png)
+
+The message window appears at the bottom of the screen, or floating in a toolbar window. It contains a list of error and warning messages returned from a compilation session.
+
+You can double click or press Enter to select a
+line corresponding to an error in the window. To open and close the
+window, press Ctrl +Shift +M .
+You can save the messages listed in the window, or clear them, through
+the context menu available by clicking the right mouse button in the
+window (or pressing the Context Menu key).
+
+You can undock and dock the window by dragging its title bar.
+
+The hotkeys for moving to next and previous messages in the list are F4 and Shift +F4 , respectively.
diff --git a/developer/docs/help/context/model-editor.md b/developer/docs/help/context/model-editor.md
new file mode 100644
index 00000000000..2366eaeba9a
--- /dev/null
+++ b/developer/docs/help/context/model-editor.md
@@ -0,0 +1,185 @@
+---
+title: Lexical Model Editor
+---
+
+The lexical model editor allows you to create and maintain lexical
+models for predictive text.
+
+## Lexical model component files
+
+A lexical model can have multiple source files, all of which can be
+managed in this one editor. The source files currently supported by the
+editor are:
+
+| File type | Tabs | Description |
+|-------------------|-----------------|-------------|
+| `file.model.ts` | Details, Source | The primary lexical model source file, a TypeScript module. |
+| `wordlist.tsv` | wordlist.tsv | One or more wordlists, UTF-8 format tab-separated value files. These files are referenced in the .model.ts source file. |
+
+## Details tab
+
+The following image shows the first tab of the editor, the Details tab.
+Changes made to this tab are reflected immediately in the Source tab.
+
+![Model Editor - Details tab](../images/ui/frmModelEditor_Details.png)
+
+The fields here have corresponding definitions in the [.model.ts source file](../reference/file-types/model-ts), which can be seen in the
+**Source** tab:
+
+Format
+: The implementation of the lexical model. This can be either
+ **Wordlist** (`trie-1.0`), or **Custom** (`custom-1.0`). Corresponds
+ to the `format` property in the [.model.ts source file](../reference/file-types/model-ts).
+
+Word breaker
+: What method is used to separate words in text. This can be one of
+ `default`, `ascii`, or `custom`. Corresponds to the `wordBreaker`
+ property in the [.model.ts source file](../reference/file-types/model-ts).
+
+Insert after word
+: Many languages insert a space after a word. Some languages, like Thai or Khmer, do not use spaces. To suppress the space, you may set insertAfterWord to the empty string:
+
+ ```keyman
+ punctuation: {
+ insertAfterWord: "",
+ },
+ ```
+
+[Read more](../guides/lexical-models/advanced/punctuation#toc-customizing-insertafterword)
+
+Quotation marks
+: These are the quotation marks that surround the "keep" suggestion when it's displayed in the suggestion banner. [Read about Quotation marks here](../guides/lexical-models/advanced/punctuation#toc-customizing-quotesforkeepsuggestion).
+
+Right-to-left script
+: Check this box to indicate that the lexical-model is for a right-to-left script.
+
+Language uses casing
+: Tick this box for uppercase, capitalize, or any casing in the predicted word.
+
+Comments
+: This field corresponds to the first lines of comments in the model
+ source, and is visible only to the model designer.
+
+Wordlists
+: The Wordlists grid controls which additional wordlist files are
+ referenced by the model. Adding a wordlist file will add an extra
+ tab to the editor, and add the corresponding reference to the
+ lexical model source. Removing a wordlist will not delete the
+ component file, but will just remove the reference from the lexical
+ model source. Corresponds to the `sources` property in the
+ [.model.ts source file](../reference/file-types/model-ts).
+
+> ### Note
+If the [.model.ts](../reference/file-types/model-ts) file cannot
+be parsed by the model editor, the field shown here will be read only
+and you will need to make changes only in the **Source** tab. This can
+happen if the .model.ts contains more complex code.
+
+## Wordlist tabs
+
+Each [wordlist file](../reference/file-types/tsv) referenced in the
+Wordlists grid is given its own tab. Wordlist tabs have two views:
+Design, and Code. Changes to one view are reflected immedaitely in the
+other view. [Learn more about wordlists](../guides/lexical-models/tutorial/step-3).
+
+Wordlist files should be stored in UTF-8 encoding (preferably without
+BOM), and tab-separated format. Some applications, such as Microsoft
+Excel, are unable to export in this format, so the compiler will also
+accept UTF-16 encoded files, but this encoding is not recommended as it
+is more difficult to manage in version control systems such as Git,
+which is the system used by the Keyman Cloud lexical model repository.
+
+![Model Editor - Wordlist tab - Design view](../images/ui/frmModelEditor_Wordlist_Design.png)
+
+Every line of the tab-separated format file is shown here, and can be
+edited directly. For most wordlists, it will be more effective to use an
+external dictionary tool, such as SIL Fieldworks or SIL PrimerPrep to
+generate the wordlist from a text corpus, and use this tab just to
+preview the contents of the file.
+
+The **Sort by frequency** button has no effect on the functioning of the
+wordlist, but can help you, the editor, by showing more common words
+earlier in the list.
+
+![Model Editor - Wordlist tab - Code view](../images/ui/frmModelEditor_Wordlist_Code.png)
+
+> ### Note
+The wordlist Code editor will accept the Tab key to insert a Tab
+character. In other code editors in Keyman Developer, the Tab key
+inserts spaces.
+
+## Source tab
+
+![Model Editor - Source tab](../images/ui/frmModelEditor_Source.png)
+
+This tab shows the source view of the model, written in the Typescript
+programming language. The format of this file is documented in the
+[Lexical Model File Format Reference](../reference/file-types/model-ts).
+
+## Build tab
+
+![Model Editor - Build tab](../images/ui/frmModelEditor_Build.png)
+
+A set of common controls is across the top of this tab:
+
+Compile Model
+: You can compile the model for testing and installation by clicking
+ the **Compile Model** button, or
+ selecting Model, Compile Model. This will compile the model into a
+ single Javascript file, named as shown in the **Target filename**
+ field.
+
+Open source, build, and project folder
+: Opens the folder which contains the model source, build, and project files.
+
+
+
+### Test Lexical Model in Web Browser
+
+Before distributing the lexical model, you can test it in a web browser.
+[Learn more about testing lexical models](../guides/test/lexical-model).
+
+Keyboard for testing
+: Allows you to select a compiled keyboard to provide input for
+ testing the lexical model. You can also choose to start testing a
+ keyboard directly from the keyboard source file and it will then be
+ available for use when testing the lexical model, until you restart
+ Keyman Developer. The keyboard must be a .js format keyboard with a
+ touch keyboard.
+
+Test Lexical Model
+: Starts the Keyman Developer Web Server for the current lexical
+ model. This will list the various IP addresses and hostnames that Keyman Developer
+ is listening on
+
+Configure...
+: Pressing this allows the [Options](./options) dialog to pop-up.
+
+Open in browser
+: Starts your default browser with the selected address to allow
+ testing of the model directly
+
+Send to email...
+: Sends the list of web addresses as an email so that you can more
+ easily open the test site on your mobile device, or share with
+ someone else on your network.
+
+Copy link
+: Once the IP addresses are shown, feel free to copy any one of the links.
+
+To test on other platforms, open one of the debug host addresses listed
+on your device. Your device will need to be on the same network as the
+Keyman Developer computer.
+
+There is also a QR code which you can scan with your mobile device
+camera to automatically load the selected site in the list of debug web
+addresses. This is the quickest way to start testing on your mobile
+device.
+
+The Send to email function is another way to open the addresses on your
+mobile device without having to enter them manually.
+
+Learn more about [testing on the web and mobile devices](../guides/test/lexical-model).
\ No newline at end of file
diff --git a/developer/docs/help/context/new-ldml-project-parameters.md b/developer/docs/help/context/new-ldml-project-parameters.md
new file mode 100644
index 00000000000..c1bf7cabb6c
--- /dev/null
+++ b/developer/docs/help/context/new-ldml-project-parameters.md
@@ -0,0 +1,63 @@
+---
+title: New LDML Keyboard Project Parameters Dialog
+---
+
+Allows you to quickly fill in common parameters for a new LDML keyboard project, adding keyboard, package, documentation and metadata files, following the [file layout](/developer/keyboards/) used in the [Keyman keyboards repository](https://github.com/keymanapp/keyboards).
+
+Projects can also be created from the command line with [KMConvert](kmconvert).
+
+![Keyboard Editor - New file, Details tab](../images/ui/frmNewLDMLProjectParameters.png)
+
+### Parameters
+
+Keyboard Name
+
+: **Required.** The descriptive name of the keyboard. This will be set in the
+ `
+
+Proxy Settings...
+: Configure the HTTP proxy settings for online functionality within
+ Keyman Developer, such as uploading files to Tavultesoft.
+
+SMTP Settings...
+: Configure your SMTP server settings for email functionality within
+ Keyman Developer, such as emailing debug URLs for the touch layout
+ debugger.
+
+## Editor tab
+
+![Options dialog - Editor tab](../images/ui/frmOptions_Editor.png)
+
+Use tab character
+: Sets whether to use the Tab character or spaces for indents.
+
+Indent size
+: The indent size, measured in number of spaces.
+
+Link quoted font size to primary font size
+: If checked, the quoted font size will be the same as the default
+ font size.
+
+Default font
+: Sets the font for ordinary text in the editor.
+
+Quoted font
+: Sets the font for displaying comments text and strings in Keyman
+ source files in single or double quotes.
+
+Editor theme
+: Sets the display theme and syntax highlighting options for the
+ editor. A [custom theme](../reference/editor-themes) can be defined
+ in a JSON file.
+
+## Debugger tab
+
+![Options dialog - Debugger tab](../images/ui/frmOptions_Debugger.png)
+
+Enable test window (from version 5.0)
+: Enables the Keyman 5-style Test window instead of the debugger.
+
+Breakpoints fire also when exiting line
+: Breakpoints will fire when a rule has finished being processed also.
+
+Turn on single step after breakpoint
+: Activates single-step mode after a breakpoint fires.
+
+Show matched character offsets in stores
+: Displays numeric indices of characters in stores
+
+Automatically recompile if no debug information available
+: When the debugger starts, rebuild a keyboard without prompting if
+ the keyboard has no debug symbols included.
+
+Automatically reset debugger before recompiling
+: If you are debugging a keyboard, make a change to the keyboard, and recompile it,
+ then the debugger needs to be reset in order to get access to the new keyboard.
+ Keyman Developer will normally prompt you to do this, but if you set this option,
+ then it will reset the debugger automatically instead.
+
+## Character Map tab
+
+![Options dialog - Character Map tab](../images/ui/frmOptions_CharacterMap.png)
+
+Find character under cursor automatically
+: The character beneath the cursor in the edit window will be
+ highlighted in the Character Map. This will intelligently parse the
+ character data under the cursor so that a character code (e.g.
+ U+1234) will be highlighted correctly.
+
+Disable database lookups
+: Disables looking characters up in the database.
+
+Update database
+: Rebuilds the Unicode character database from source unicodedata.txt
+ and blocks.txt. These files can be downloaded from the Unicode
+ website at
+ [http://www.unicode.org/ucd/](http://www.unicode.org/ucd/).
+ This lets you update the character map with a newer version of Unicode.
+ Keyman Developer 17.0 was released with Unicode version 15.1 data.
+
+## Server tab
+
+![Options dialog - Server tab](../images/ui/frmOptions_Server.png)
+
+There are two configurations in the Keyman Developer Server section:
+1. Configure Server...
+2. List local URLs for Server
+
+Configure Server...
+
+: After clicking the button, a new window will pop-up
+
+ ![Keyman Developer Server Options dialog](../images/ui/frmKeyman_Developer_Server_Options.png)
+
+ You can customize the Keyman Developer server for testing the usability of your keyboard. Features of the Keyman Developer server include:
+
+ * Seamless integration with the IDE (Start, Stop, Live reload, Recompile...)
+ * Port configuration
+ * Allow testing cross devices
+ * Wider range support for NGROK...
+
+ [Find out more](https://github.com/keymanapp/keyman/pull/6073)
+
+ Understanding the three tick boxes will enhance the usability of Keyman Developer:
+ - **Leave Server running after closing IDE**: allows keyboard testing on local URLs after closing Keyman Developer.
+ - **Use ngrok to provide public url for web debugger**: configure NGROK as instructed, once it looks something like this:
+ ![Keyman Developer NGROK](../images/ui/frmNGROK_Config.png)
+
+ Please restart the machine, and the URL (*link ending in .ngrok-free.app*) will appear in the specified locations:
+ ![Keyman Developer NGROK URL 1](../images/ui/frmNGROK_Server_1.png)
+ ![Keyman Developer NGROK URL 2](../images/ui/frmNGROK_Server_2.png)
+
+ Now, freely share it across multiple devices using the link or QR code.
+ ![Keyboard Test on Mobile](../images/ui/NGROK_Phone.png)
+
+ - **Show server console window on start**: once ticked, Keyman Developer's server console will appear as an independent window as a node application with the default message:
+ `Starting Keyman Developer Server 17.0.290-beta, listening on port 8008.`
+
+List local URLs for Server
+: Below Configure Server, untick the box, and it will quit displaying any local URLs for keyboard testing.
+ ![Keyman Developer Server Options dialog](../images/ui/frmList_Local_URLs.png)
+
+Explore more: [Keyman Developer Server](server#toc-configuring-keyman-developer-server)
diff --git a/developer/docs/help/context/package-editor.md b/developer/docs/help/context/package-editor.md
new file mode 100644
index 00000000000..38026732bc7
--- /dev/null
+++ b/developer/docs/help/context/package-editor.md
@@ -0,0 +1,194 @@
+---
+title: Package Editor
+---
+
+The Package Editor allows you to edit a .kps package source file, which will be
+compiled into a .kmp package file. These files contain a set of keyboards,
+documentation, fonts, and other related files, which make distribution and
+installation of a keyboard in Keyman simple.
+
+## Files tab
+
+![Package Editor - Files tab](../images/ui/frmPackageEditor_Files.png)
+
+Use the Add... button to select the files to be included in the package, such
+as .kmx and .js keyboard files, documentation, and related fonts; but you can
+include any type of file here as well.
+
+If you add another .kmp package file, it will also be installed when this
+package is installed, but will be managed separately and uninstalled separately.
+
+## Keyboards tab
+
+![Package Editor - Keyboards tab](../images/ui/frmPackageEditor_Keyboards.png)
+
+The Keyboards tab shows you some status information of keyboards you've added to
+the package. There are also optional font dropdowns and a required "Languages"
+section. The fields on the tab are:
+
+**Description**
+
+: The keyboard name (`&name` system store).
+
+**Files**
+
+: This shows the associated keyboard files (.kmx and .js files)
+
+**Version**
+
+: The keyboard version (`&keyboardversion` system store).
+
+**Is right-to-left**
+
+: Currently, this is only applicable to Android, iOS and Web (that is, .js
+ format) keyboards. This reflects the checkbox in the [Details tab](./keyboard-editor#details-fields) of the Keyboard editor.
+
+> ### Note
+ **Is right-to-left** will only be true if the following occur:
+ * "Keyboard is right-to-left" is checked in the Keyboard editor
+ * The compiled .js keyboard is added to the package
+
+**Keyboard font**
+
+: When font files are added to the package, this dropdown tells the Keyman apps
+ which font to use when rendering the On Screen Keyboard touch keyboard.
+ Optional.
+
+**Display font**
+
+: When font files are added to the package, this dropdown tells the Keyman apps
+ for iOS and Android which font to use in edit fields. It only applies within
+ the Keyman app and apps that support this functionality. Optional.
+
+**Web fonts**
+
+: It is possible now to specify additional font filenames that will be made
+ available to KeymanWeb, for example, providing fonts in WOFF or WOFF2 formats.
+ [More on web fonts](select-web-fonts)
+
+**Languages**
+
+: Because the language information in the .kmn source is deprecated, the
+ "Languages" section is required. Each language listed here is a [BCP 47 language tag](../reference/bcp-47). Use the **Add** button to
+ bring up the "Select BCP 47 Tag" dialog. When Keyman installs the keyboard
+ package, it will associate the keyboard with the language(s) you select here.
+ Required.
+
+ ![Package Editor - Select BCP 47 Tag dialog](../images/ui/frmPackageEditor_Select_BCP_47_Tag.png)
+
+**Examples**
+
+: Examples allow you to provide short keystroke sequences to type sample text
+ with your keyboard. This can be the easiest way for a new user to start using
+ your keyboard, and is particularly helpful when your keyboard makes use of
+ keying sequences that may not be immediately obvious.
+
+ As the keying examples may vary by language, you should include the BCP 47 tag
+ for each example, plus a short description (in English) of what the text is
+ about.
+
+ The key sequence must list each key combination, separated by space. The
+ actual text for each key is reasonably arbitrary, to allow you to provide
+ examples for touch keyboards as well as desktop keyboards. There are three
+ special kinds of key strings:
+ * Modifier keys may be specified with the `+` character, e.g. `shift+e` or `right-alt+k`. Use lower case keys. The suggested standard modifiers are: `shift`, `ctrl`, `alt`, `left-alt`, `right-alt`, `left-ctrl`, `right-ctrl`, and `option` (mac).
+ * The space key itself may be specified with `space`.
+ * To avoid confusion with modifier keys, the + key can be specified with `plus`.
+
+ For example, the key sequence x , j , m , Shift +e , r may be specified as `x j m shift+e r` or `x j m E r`.
+
+ ![Package Editor - Edit Example dialog](../images/ui/frmPackageEditor_EditExample.png)
+
+## Details tab
+
+![Package Editor - Details tab](../images/ui/frmPackageEditor_Details.png)
+
+Enter the name of the package into the Package Name field; this will be
+automatically filled from the name of the first keyboard you add to the package.
+
+Select a HTML readme file if you have one to include in the package; this will
+be displayed before the package is installed as information about the package.
+
+Version numbers should be in the form `major.minor[.patch]`. Patch is optional
+but is helpful for small bug fix releases. Each of the sections of the version
+should be an integer. Keyman Desktop does integer comparisons on the version
+number components, so, for example, version 2.04 is regarded as newer than
+version 2.1. Alphabetic or date formats should be avoided as the installer for
+the keyboard cannot determine which version is older reliably.
+
+You can also tick the checkbox labelled "Package version follows keyboard
+version" to have the package version automatically track the keyboard version.
+
+The Copyright indicates the overall copyright of the package and all its
+contents.
+
+Fill in the individual or organisation who authored the package in the Author
+field, and a contact email address into the E-mail address field. These fields
+are optional.
+
+A web site is encouraged and should be filled in the web site field, including
+the initial "http://" or "https://".
+
+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.
+
+### Related packages
+
+If a keyboard package is intended to replace an existing keyboard, or if there
+are related packages, then the identifiers for these packages should be listed
+here.
+
+There are two types of package relationships:
+
+* Deprecation: if the package replaces an existing package, ensure that you
+ select the 'Deprecated' check box. This information will be used on the Keyman
+ website, both to help users select the most recent version of a keyboard, and
+ also to offer upgrades to new packages for existing users of the package.
+
+* Related: if the 'Deprecated' check box is not selected, then this the
+ relationship information is used to provide links to the related packages on
+ the Keyman website. For example, you may create two keyboards for the same
+ language with different keying orders; then it would be appropriate to
+ cross-reference them with this field.
+
+![Package Editor - Edit Related Packages dialog](../images/ui/frmPackageEditor_EditRelatedPackage.png)
+
+## Shortcuts tab
+
+![Package Editor - Shortcuts tab](../images/ui/frmPackageEditor_Shortcuts.png)
+
+You can optionally have the package create a Start Menu folder, and populate it
+with shortcuts to the files in the package, and optionally also a shortcut for
+uninstallation.
+
+To add an entry, click New, and set the description, and choose a program or
+document to load.
+
+An uninstall shortcut is no longer recommended; uninstall should be managed by
+the user in Keyman Configuration.
+
+## Source tab
+
+![Package Editor - Source tab](../images/ui/frmPackageEditor_Source.png)
+
+The source of the .kps file in XML format. All details from previous tabs can be
+seen in the Source tab, and changes in either the Source tab or the other tabs
+will be reflected immediately in the other.
+
+## Compile tab
+
+![Package Editor - Compile tab](../images/ui/frmPackageEditor_Build.png)
+
+The final step is building the package. You must save the package file before
+building, then choose Compile Package to build the package file. You can then
+test the package to verify that it will install correctly with the Install
+Package button.
+
+Once you are satisfied with your package, you should consider submitting the
+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
diff --git a/developer/docs/help/context/project.md b/developer/docs/help/context/project.md
new file mode 100644
index 00000000000..84bfb38722c
--- /dev/null
+++ b/developer/docs/help/context/project.md
@@ -0,0 +1,36 @@
+---
+title: Project Window
+---
+
+The Project allows you to manage all the files that you are working on in Keyman Developer, and guides you on the steps to creating a keyboard solution. Changes to the project are automatically saved.
+
+You can open the Project window by pressing Ctrl +Shift +P , or selecting View, Project.
+
+## Welcome
+
+![Project window - Welcome tab](../images/ui/frmProject_Welcome.png)
+
+
+This tab lists all the files that you have recently worked on.
+
+## Keyboards
+
+![Project window - Keyboards tab](../images/ui/frmProject_Keyboards.png)
+
+
+This tab lists all the keyboard source files in your project.
+
+## Packaging
+
+![Project window - Packaging tab](../images/ui/frmProject_Packaging.png)
+
+
+This tab lists all the package source files in your project.
+
+## Distribution
+
+![Project window - Distribution tab](../images/ui/frmProject_Distribution.png)
+
+
+This tab lists all other files in your project, such as the
+[.keyboard_info file used for uploading to the Keyboards repository](/developer/keyboards/).
\ No newline at end of file
diff --git a/developer/docs/help/context/select-web-fonts.md b/developer/docs/help/context/select-web-fonts.md
new file mode 100644
index 00000000000..f03d437faf0
--- /dev/null
+++ b/developer/docs/help/context/select-web-fonts.md
@@ -0,0 +1,17 @@
+---
+title: Package Editor - Select Web Fonts
+---
+
+The Select Web Fonts dialog allows you to choose additional fonts that will be
+made available as suggested fonts for KeymanWeb. These fonts should be included
+in the package.
+
+Even though the fonts are included in the package, KeymanWeb will source the
+fonts from its own infrastructure, so they will need to be shared in the Keyman
+content delivery network repository
+[s.keyman.com](https://github.com/keymanapp/s.keyman.com).
+
+Separate fonts can be specified for the On-Screen Keyboard and the recommended
+Display font.
+
+![Select Web Fonts dialog](../images/ui/frmPackageEditor_SelectWebFonts.png)
\ No newline at end of file
diff --git a/developer/docs/help/context/server.md b/developer/docs/help/context/server.md
new file mode 100644
index 00000000000..0a04d425d32
--- /dev/null
+++ b/developer/docs/help/context/server.md
@@ -0,0 +1,398 @@
+---
+title: Keyman Developer Server
+---
+
+# Introduction
+
+Keyman Developer Server provides a web-based front-end for testing and sharing
+keyboards, lexical models and packages.
+
+The primary benefits of Keyman Developer Server are:
+
+* Test keyboards in a browser on your computer, including simulation of mobile
+ devices and touch keyboards
+* Test lexical models in a browser on your computer
+* Test keyboards, lexical models and packages on mobile devices
+* Share keyboards, lexical models and packages to mobile devices and other users
+
+Keyman Developer Server was added in Keyman Developer 15, and replaces the old web
+Test feature.
+
+![](images/server-iphone.png)
+
+# Installation and Startup
+
+## Windows
+
+Keyman Developer Server is always installed as a part of Keyman Developer.
+
+When you start Keyman Developer, Keyman Developer Server will start in the
+background.
+
+> **Note:** The first time Keyman Developer Server starts, you may be presented
+with a warning from your firewall or security software about Node.js Javascript
+Runtime running from the Keyman Developer folder. This is the Keyman Developer
+Server host process, and you should choose to allow Keyman Developer Server to
+communicate at least on your local private network, or as your circumstances
+dictate.
+
+By default, Keyman Developer Server will run when Keyman Developer is running,
+and will shutdown when Keyman Developer is closed. However, you can also
+configure Keyman Developer Server to stay running even after Keyman Developer is
+closed.
+
+An icon will be displayed in the system notification area in the Taskbar
+(it may be hidden in the expansion area):
+
+![](images/taskbar.png)
+
+Clicking on the icon opens a menu:
+
+![](images/taskbar-menu.png)
+
+The following menu items are available:
+
+**Open `http://localhost:8008` in browser**
+
+: Loads Keyman Developer Server home page in your default browser
+
+**Show console**, **Hide console**
+
+: Controls the visibility of the Node console for Keyman Developer Server. You
+ would only need to view the Node console if you were diagnosing issues with
+ Keyman Developer Server
+
+**Exit Keyman Developer Server**
+
+: Shuts down the Server. The Server can be restarted by closing and re-opening
+ Keyman Developer.
+
+## Linux and macOS
+
+Keyman Developer Server is included in the kmcomp.zip standalone distribution.
+It requires nodeJS 16.0 or later, which should be installed according to your
+platform's instructions.
+
+On macOS, if you use Homebrew, you can install nodeJS in Terminal:
+
+```shell
+brew install node
+```
+
+You can download the latest kmcomp.zip with the following commands:
+
+```shell
+mkdir kmcomp
+cd kmcomp
+curl -L https://keyman.com/go/download/kmcomp -o kmcomp.zip
+unzip kmcomp.zip
+```
+
+To start the server, from the kmcomp folder you created earlier, run the
+following command:
+
+```shell
+cd server
+npm run prod
+```
+
+You can stop the server at any time by pressing Ctrl +C
+in the Terminal / shell.
+
+# Using Keyman Developer Server
+
+## Testing a Keyman keyboard in Keyman Developer Server
+
+In Keyman Developer, open a keyboard project, and load the keyboard .kmn file.
+In the Build tab, click **Compile keyboard**, then click **Test keyboard on
+web**. Finally, click **Open in browser** to access the keyboard in your
+local Keyman Developer Server instance.
+
+You will be presented with the default page for Server.
+
+You will need to select your keyboard from the **Keyboard** menu. The keyboard will
+be presented and you can test it either by typing using your hardware keyboard,
+or interacting with the On-Screen Keyboard.
+
+![](images/server-iphone.png)
+
+When you make changes to your keyboard and recompile, the keyboard will be
+reloaded instantly. There is no need to click the **Test keyboard on web**
+button again.
+
+Keyman Developer Server remembers the ten most recent keyboards that you have
+been testing, even after exiting and restarting. This makes it easier to switch
+between projects in Keyman Developer.
+
+## Testing a lexical model in Keyman Developer Server
+
+In Keyman Developer, open a lexical model project, and load the model .model.ts
+file. In the Build tab, click **Compile model**, then click **Test lexical
+model**. Finally, click **Open in browser** to access the model in your local
+Keyman Developer Server instance.
+
+You will be presented with the default page for Server.
+
+You will need to select a keyboard that works with the model from the
+**Keyboard** menu, and also the model you have just started testing from the
+**Model** menu. You will have the best results if you select a mobile device to
+test the model.
+
+![](images/lexical-model-test.png)
+
+When you make changes to your model and recompile, the model will be reloaded
+instantly. There is no need to click the **Test lexical model** button again.
+
+Keyman Developer Server remembers the ten most recent lexical models that you
+have been testing, even after exiting and restarting.
+
+## Sharing a package to test from Keyman Developer
+
+In Keyman Developer, open either a keyboard or a lexical mdoel project, and load
+the .kps file. In the Build tab, click **Compile package**, then click **Test
+package on web**. Finally, click **Open in browser** to access the package in
+your local Keyman Developer Server instance.
+
+The package will be available for install from the hamburger menu at the top
+left of the banner. This is most useful when you access Server from another
+device, as you can then easily install the package into Keyman on that device
+without having to worry about copying files around manually.
+
+![](images/hamburger-menu.png)
+
+Keyman Developer Server remembers the ten most recent packages that you have
+been testing, even after exiting and restarting.
+
+## Keyman Developer Server interface
+
+![](images/server-iphone.png)
+
+This describes the various elements of the Keyman Developer Server web interface.
+
+### Banner
+
+![](images/banner.png)
+
+On desktop devices, the top banner of Server includes four menus. On a mobile
+device, the Device menu will not be visible, as the keyboard view will always be
+the touch keyboard view at the bottom of the screen.
+
+### Status Bar
+
+![](images/status-bar.png)
+
+Below the banner, you will find a smaller status bar showing the currently
+selected keyboard, model, and device.
+
+### Character Grid
+
+![](images/character-grid.png)
+
+Under this is a character preview grid, which shows you the underlying Unicode
+values of the characters in your text area. This character grid also shows the
+current cursor position and highlighted text, so you can easily correlate the
+shaped characters with their underlying representation in Unicode.
+
+The example image above shows a Khmer keyboard with a sample string saying
+"Khmer Language". Observe how the character preview grid shows the unshaped
+underlying characters, how the order of characters stored in the text content
+may differ from the presentation, and how diacritics and joined marks are
+indicated with dotted circles.
+
+### Text Area
+
+![](images/text-area.png)
+
+The text area is immediately below this. You can enter text into this text area
+using your hardware keyboard or using the On-Screen Keyboard.
+
+### On-Screen Keyboard
+
+![](images/on-screen-keyboard.png)
+
+The On-Screen Keyboard, shown directly below, is fully interactive, including
+support for longpress and other gestures on touch, using your mouse on a
+desktop device. The layout of the On Screen Keyboard and the device frame are
+controlled from the **Device** menu.
+
+### Hamburger menu (three horizontal lines)
+
+![](images/hamburger-menu.png)
+
+**Download and install Keyman**
+
+: This is a shortcut to install Keyman for the current device. It detects
+ the type of device you are viewing this page on, and provides a direct
+ link to the install, whether that be a Windows executable, a Play Store
+ link, an App Store link or other method.
+
+**List of packages**
+
+: A list of up to ten most recent packages that you have been testing in
+ Keyman Developer. This will open the package in Keyman on the device, which
+ is most useful when testing on a mobile device or sharing the package to
+ other computers.
+
+**Upload file...**
+
+: Allows you to manually upload any compiled .js keyboard, compiled .model.js
+ lexical model, compiled .kmp file, or .ttf font to be available for testing.
+ This will be most useful when running Server on macOS or Linux, where
+ integration with the Keyman Developer IDE is not available.
+
+ You can also drag and drop these same files onto the page.
+
+**Help...**
+
+: Opens this help site.
+
+**About Keyman Developer Server**
+
+: Provides information about the version of Keyman Developer Server.
+
+### Keyboard menu
+
+![](images/keyboard-menu.png)
+
+Lists up to ten keyboards that you have been testing most recently from Keyman
+Developer. Select a keyboard to start testing it.
+
+If you select **(system keyboard)**, this disables testing of Keyman keyboards.
+
+### Model menu
+
+![](images/model-menu.png)
+
+Lists up to ten lexical models that you have been testing most recently from
+Keyman Developer. Select a model to start testing it. Note that the
+model/keyboard correlation is controlled by you within this view -- language
+association is not available in Keyman Developer Server.
+
+While the desktop keyboard views will show predictions if a model is enabled,
+this is not currently fully supported outside of Keyman Developer Server. You
+will have better results selecting a touch device in the Device menu for testing
+lexical models.
+
+If you select **(no model)**, this hides the predictions banner.
+
+### Device menu
+
+![](images/device-menu.png)
+
+This function is available only from desktop / laptop computers. On mobile
+phones and tablets, the Device menu is hidden and only the device's default
+Keyman keyboard form factor is available.
+
+When available, this allows you to simulate use of the devices shown, letting
+you test the keyboard and/or model as you would see it and use it on that
+device. The device selected changes the presentation of the keyboard, and is
+reflected in the [`platform()` statement](/developer/language/reference/platform)
+context as well.
+
+The devices listed are representative of the various types of platforms that
+Keyman supports, to allow testing on each of the available form factors.
+
+## Making your Keyman Developer Server instance globally available with ngrok
+
+One of the fiddliest parts of testing keyboards and models with mobile devices
+is transferring the files to your phone or tablet. Keyman Developer Server will
+make files available on your local network for access, but it can be difficult
+to configure firewalls and network settings successfully.
+
+To simplify testing, Keyman Developer Server integrates with a free tool called
+[ngrok](https://ngrok.com) that provides a public URL to your instance of Keyman
+Developer Server. This URL is dynamic and can change over time, and is only
+available while Keyman Developer Server is running. This URL can be accessed
+from any mobile device that is online, whether or not it is on your local
+network. This means you can also quickly share keyboards, packages, and models
+with other users, with a minimum of difficulty.
+
+**Warning:** While the ngrok service is running, your computer will be sharing
+your Keyman Developer Server instance to anyone in the world. The Server
+instance has read-only access to the keyboards that you have loaded into it, but
+you may wish to turn ngrok support on only when you need it.
+
+ngrok does need a little bit of configuration. These steps will take you through
+the process on Windows; on macOS and Linux, you will need to download and
+configure ngrok manually.
+
+1. In Keyman Developer, open **Tools | Options**, select **Server** tab, and
+ click **Configure Server...**. You should be presented with a window similar
+ to this:
+
+ ![](images/config.png)
+
+2. Tick the box **Use ngrok to provide public url for Server**
+3. If ngrok is available, a version number will be shown to the right of the
+ **Download or update ngrok** button. Otherwise, click this button to download
+ the latest version.
+4. You'll need a free ngrok account, so click the **Create free ngrok
+ account...** button to open the ngrok website, and follow the steps there
+ to finish the account creation process.
+5. After creating the account, you should be presented with an authentication
+ token. You can also access this by clicking the **Get token** link in the
+ Keyman Developer Server Options dialog box. Paste this token into the
+ **Authentication token** edit box.
+6. Select the most appropriate region for your location, to minimize the latency
+ (delay) when using the ngrok service.
+7. Click **OK** to save changes, and start the ngrok service within Keyman
+ Developer Server.
+
+Once ngrok is configured, you should see an additional **Server** menu item in
+Keyman Developer's **Tools** menu, with three items:
+
+**Open `https://some-url.ngrok.io` in browser**
+
+: Opens the public URL to your current Keyman Developer Server instance in the
+ default browser on your computer.
+
+**Copy URL to clipboard**
+
+: Copies the public URL to the clipboard so you can share it via email, instant
+ message, or other medium.
+
+**Configure...**
+
+: Opens the Keyman Developer Server Options dialog box.
+
+# Configuring Keyman Developer Server
+
+Keyman Developer Server has a number of configuration options. On Windows, using
+Keyman Developer is the easiest way to manage these options. Open **Tools |
+Options**, select **Server** tab, and click **Configure Server...**.
+
+![](images/config.png)
+
+## Configuration Files
+
+The configuration file `config.json` is stored in the following locations:
+* Windows: `%appdata%\Keyman\Keyman Developer\Server\config.json`
+* Linux: `~/.local/share/Keyman/Keyman Developer/Server/config.json`
+* macOS: `~/Library/Preferences/Keyman/Keyman Developer/Server/config.json`
+
+## Configuration settings
+
+The value in parentheses is the name of the setting in `config.json`, where present.
+
+* **Default port** (`port`): specifies the port that Server listens on (http).
+ Defaults to `8008`.
+* **Leave Server running after closing IDE**. This is off by default. If set,
+ Keyman Developer Server will start with Keyman Developer, and run until the
+ Windows session is finished by logging off or shutting down the computer. Keyman
+ Developer Server can be manually closed from the system notification area icon.
+* **ngrok Settings**
+ * **Use ngrok to provide public url for Server** (`useNgrok`): Defaults to `false`.
+ Set to `true` if you want to enable use of ngrok tunnels.
+ * **Authentication token** (`ngrokToken`): the token provided by ngrok for setting
+ up a tunnel.
+ * **Region** (`ngrokRegion`): select the region in which you are located in order
+ to minimize latency through the tunnel.
+* **Show Server console window on start**. This is off by default, and would only
+ normally be used for diagnostics or development of Keyman Developer itself.
+
+# Related Topics
+
+* [Keyboard Editor](keyboard-editor)
+* [Model Editor](model-editor)
+* [Package Editor](package-editor)
+* [Options](options)
diff --git a/developer/docs/help/guides/command-line.md b/developer/docs/help/guides/command-line.md
new file mode 100644
index 00000000000..f4820281ce4
--- /dev/null
+++ b/developer/docs/help/guides/command-line.md
@@ -0,0 +1,9 @@
+---
+title: Keyman Developer command line tools guide
+---
+
+Keyman Developer has a set of command line tools for creating keyboards and
+models. For more details see:
+
+* [`kmc` -- the Keyman command line compiler](../reference/kmc/)
+
diff --git a/developer/docs/help/guides/develop/advanced-keyboard-development-example.md b/developer/docs/help/guides/develop/advanced-keyboard-development-example.md
new file mode 100644
index 00000000000..204d031a6a1
--- /dev/null
+++ b/developer/docs/help/guides/develop/advanced-keyboard-development-example.md
@@ -0,0 +1,236 @@
+---
+title: Advanced Keyboard Development Example
+---
+
+## Introduction
+
+Whenever you want to do much more in a keyboard than simple character
+substitution, you will generally need to make use of advanced features
+such as **stores**, **deadkeys**, and **multiple groups**. The [tutorial](tutorial)
+has already shown some basic usage of stores and deadkeys, but has not
+covered other possibilities of their use. In this topic we will examine
+a keyboard that demonstrates some other uses of stores and deadkeys, and
+introduces the use of multiple groups for complex processing.
+
+We recommend that you read the topics on
+[groups](/developer/language/guide/groups),
+[stores](/developer/language/guide/stores), and
+[deadkeys](/developer/language/reference/deadkey) before continuing.
+
+The keyboard we will examine is **IPAMenu.kmn** (found in the Keyman
+Developer Samples folder), which contains the beginnings of a menu-based
+keyboard for using the International Phonetic Alphabet (IPA). You will
+need a Unicode font with IPA characters (such as **Gentium**) to
+properly use this keyboard, but you should be able to follow the code
+even without. For more information about the IPA, see [The International
+Phonetic
+Association](https://www.internationalphoneticassociation.org/).
+
+Most of the IPA glyphs are derived from glyphs in the Latin alphabet,
+used to represent differing sounds used in language. Because of this, it
+seemed reasonable to place all glyphs derived from "a" on the "a" key,
+and so on. We have implemented a few of the vowel symbols in this keyboard:
+
+![](../../images/ipa.gif)
+
+
+## Overview of the Keyboard
+
+The basic operation of the keyboard is the displaying of a menu when a
+key is pressed, followed by the output of a single character when the
+user makes a selection from that menu with a number key.
+
+The first thing to notice is the organisation of most of the input and
+output into stores:
+
+```keyman
+store( choices ) '1234567890'
+store( a_menu ) '[1æ 2a 3ɑ 4ɐ 5ʌ 6ɒ]'
+store( e_menu ) '[1ɛ 2ɜ 3ə 4e 5ɘ 6ɚ 7ɝ 8ɞ]'
+store( o_menu ) '[1o 2ø 3ɔ]'
+store( a_chars ) 'æaɑɐʌɒ' dk(a_err) dk(a_err) dk(a_err) dk(a_err)
+store( e_chars ) 'ɛɜəeɘɚɝɞ' dk(e_err) dk(e_err)
+store( o_chars ) 'oøɔ' dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err)
+```
+
+An important point to notice is the use of deadkeys in these stores:
+we'll explain their purpose here later.
+
+The next thing that stands out is that the file has six separate groups,
+four of which handle keystrokes and two which manipulate context only:
+
+```keyman
+group( first )
+
+group( main ) using keys
+
+group( final )
+
+group( a_group ) using keys
+
+group( e_group ) using keys
+
+group( o_group ) using keys
+```
+
+## Full source
+
+The full keyboard source is shown below. Refer to this when following
+the description after.
+
+```keyman
+c IPAMenu.kmn
+c
+c Copyright ©2002 Tavultesoft.
+c
+c Demonstrates simple use of multiple groups to create a menu-based
+c system for entering IPA characters, based on an example created
+c by Peter E. Hauer's.
+c
+c A font with the IPA unicode characters defined must be used with this
+c keyboard, for example, Code2000 or Lucida Sans Unicode
+c
+c Note that the stores pertaining to each letter could be placed in the
+c group for that letter; the location of stores in the keyboard source
+c has no effect on the final keyboard.
+
+Version 6.0
+Name "IPA Menu Example"
+
+c This keyboard should be independent of the user's
+c system keyboard layout
+store(&MnemonicLayout) '1'
+
+c *******************************************************************
+begin Unicode > use( first )
+
+c keys used to choose menu items
+store( choices ) '1234567890'
+
+c menu stores
+store( a_menu ) '[1æ 2a 3ɑ 4ɐ 5ʌ 6ɒ]'
+store( e_menu ) '[1ɛ 2ɜ 3ə 4e 5ɘ 6ɚ 7ɝ 8ɞ]'
+store( o_menu ) '[1o 2ø 3ɔ]'
+c add more menu stores here
+
+c character choice stores - error deadkeys are used to pad
+c out the stores so they are the same length as the choices store
+store( a_chars ) 'æaɑɐʌɒ' dk(a_err) dk(a_err) dk(a_err) dk(a_err)
+store( e_chars ) 'ɛɜəeɘɚɝɞ' dk(e_err) dk(e_err)
+store( o_chars ) 'oøɔ' dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err) dk(o_err)
+c add more char stores here
+
+c *******************************************************************
+c first matches a menu in the context or passes processing to main
+group( first )
+ c match a menu on the context
+ outs(a_menu) > use(a_group)
+ outs(e_menu) > use(e_group)
+ outs(o_menu) > use(o_group)
+ c add more menus here
+
+ c no menu was in the context, so process keys normally
+ nomatch > use(main)
+
+c *******************************************************************
+c main outputs the menus, and handles any other normal key processing
+group( main ) using keys
+ c output a menu if appropriate
+ + 'a' > outs(a_menu)
+ + 'e' > outs(e_menu)
+ + 'o' > outs(o_menu)
+ c add more menu keys here
+
+c *******************************************************************
+c final matches error markers and finishes processing
+group( final )
+ dk(a_err) > beep outs(a_menu)
+ dk(e_err) > beep outs(e_menu)
+ dk(o_err) > beep outs(o_menu)
+ c add more error-marker handlers here
+
+c *******************************************************************
+c a_group handles the menu for a
+group( a_group ) using keys
+ + any(choices) > index(a_chars, 1) use( final ) c output chosen character
+ + [K_BKSP] > nul c delete menu
+ nomatch > dk(a_err) use( final ) c invalid choice - error
+
+c *******************************************************************
+c e_group handles the menu for e
+group( e_group ) using keys
+ + any(choices) > index(e_chars, 1) use( final ) c output chosen character
+ + [K_BKSP] > nul c delete menu
+ nomatch > dk(e_err) use( final ) c invalid choice - error
+
+c *******************************************************************
+c o_group handles the menu for o
+group( o_group ) using keys
+ + any(choices) > index(o_chars, 1) use( final ) c output chosen character
+ + [K_BKSP] > nul c delete menu
+ nomatch > dk(o_err) use( final ) c invalid choice - error
+
+c add more menu groups here
+
+c End of file
+```
+
+## Primary Operation of the Keyboard
+
+When a key is pressed, execution begins at the group indicated by the
+`begin` statement, in this case the `first` group. Because this group
+does not specify `using keys`, it is limited to context manipulation
+only: the output of this rule is dependent only on what came before the
+current keystroke, and becomes the context for any further groups that
+are called from this one.
+
+Let's suppose the a key has been pressed with no context. The `first` group will have nothing to match on, so the
+`nomatch` rule fires and passes control to the `main` group. Here the
+"a" key is matched, and the `a_menu` store is output, displaying the
+menu of a-like characters.
+
+Now the user is presented with a menu of options to choose from. Suppose
+he types 1 Once again the `first` group gains
+control first, but this time matches the first rule, with the `a_menu`
+string on the context, so control is passed to the `a_menu` group to
+handle the keystroke.
+
+Here the 1 is matched as an entry in the `choices` store, and the corresponding character in the
+`a_chars` store - in this case "æ" - is output. Finally, control goes
+from here to the `final` group, which fails to match anything in the
+context (which now includes the output from the previous group).
+
+## Error Handling
+
+One issue this keyboard has to deal with is if the user tries to select
+an option that's not in the menu - when this happens, it should beep and
+remain at the menu, so the user can try again. Also, if the user wants
+to dismiss the menu, we should allow the use of Backspace to delete it -
+this is simply done with a rule matching `[K_BKSP]` and outputting
+`nul`.
+
+There are two rules which handle the user selecting a nonexistent
+option: if the current menu has fewer than 10 entries, the user can
+press a number key indicating a menu entry that is not there - this
+situation will be matched by the `any(choices)` rule. The other occasion
+is if the user presses any other key which is not valid for selecting a
+menu option. This is handled by the `nomatch` rule in the group. For
+both these cases we want the output to be the same: a `beep`, and remain
+at the menu. To do this we will use deadkeys as error flags, one for
+each menu. By padding the `a_char`, `e_char` and `o_char` stores to 10
+characters with these deadkeys, the output for this first situation will
+be the error flag. Similarly, we can output these deadkeys in the
+`nomatch` rules, to mark an error.
+
+The actual error handling is now done with the `final` group, which
+matches the error flags on the output and outputs the `beep` and the
+appropriate menu again.
+
+## Conclusion
+
+Although this example went nowhere near the limits of what can be done
+with multiple groups, it gave a demonstration of some of the ways
+multiple groups can be made to interact for more powerful processing.
+You should now have some understanding of the use of advanced features
+in Keyman Developer, and be able to begin using them to improve your
+keyboards.
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md
new file mode 100644
index 00000000000..21bf08df14b
--- /dev/null
+++ b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty.md
@@ -0,0 +1,754 @@
+---
+title: Creating a Touch Keyboard Layout Part 2
+---
+
+
+
+
+
+
+This article will continue the guide to creating a touch keyboard layout. In
+particular, we'll look in more detail at how keys are arranged, just what can be
+specified for each key, and lastly, how this all looks in the JSON code used to
+define the long press layout.
+
+## Key and Key Layer Organization
+
+There are two issues that are immediately apparent when considering key layout
+on touch devices.
+
+First, on smaller touch devices, such as phones, if we try to display the same
+arrangement of keys that is used for a typical desktop keyboard, the keys are so
+small that it is difficult to reliably select the wanted key. If used in
+'portrait' view, key widths are too narrow for our 'fat fingers':
+
+![](../../images/touch_amharic_keyboard_4.png)
+
+Or if in 'landscape' view, key heights are too small:
+
+![](../../images/touch_amharic_keyboard_5.png)
+
+The situation is improved markedly if we limit the number of keys per row to ten
+and have no more than four key rows:
+
+![](../../images/touch_amharic_keyboard_6.png)
+
+and, in landscape view:
+
+![](../../images/touch_amharic_keyboard_7.png)
+
+So which keys can be eliminated, and which keys must be on the base (default)
+layer? This brings us to the second point. When using phones and other touch
+layout devices rather than desktop keyboards, text entry is most often
+single-handed, which makes it best to avoid using the 'shift' layer for entering
+normal text. Secondary keyboard layers will, of course, still be usually needed
+for uppercase, numerals and symbols, but will be used much less frequently.
+
+Most desktop keyboards (for European languages, at least) are already laid out
+with no more than ten letter (or digit) keys per row, with the remaining keys
+being used for accented letters, punctuation and other non-letter input. So the
+obvious choice is to move non-letter keys (and accented letters) either to a
+secondary key layer, or to long press ('pop-up') keys. The GFF Amharic desktop
+keyboard is fortunately also arranged with only ten letters per key row (other
+keys being used for punctuation, etc.), as the many different characters in the
+Amharic 'abugida' are generated by multi-letter sequences rather than being
+displayed on separate keys. The Geez script does not have both upper-case and
+lower-case forms, so those initial consonants that require use of the 'shift'
+layer on the desktop keyboard have been added to the corresponding base-layer
+key as long press keys:
+
+![](../../images/touch_amharic_keyboard_8.png)
+
+Arrangement of punctuation and other non-letter keys is more flexible as mobile
+users are generally familiar with using long press keys or a secondary key layer
+for finding and entering digits and special characters. However, some
+punctuation characters are used so frequently that they need to be on the base
+layer. For the GFF Amharic keyboard, the most frequently used punctuation
+characters can be output from the base layer using standard or long press keys.
+The Geez word space character, in particular, is so frequently used that it was
+considered useful to add it to the bottom key, adjacent to the space bar, as is
+sometimes done for other scripts, such as Japanese, on desktop (physical)
+keyboards.
+
+## Arranging keys with the layout editor
+
+The Keyman Developer layout editor really makes it quite easy to try different
+key layouts and choose what is best for your keyboard. The image below
+highlights just how, for any selected key, using the clickable icons circled, a
+key row can be added above (1) or below (2), a key added before (3) or after
+(4), the selected key deleted (5), and how an array of long press keys
+(sometimes called "subkeys") can be added (6).
+
+![](../../images/touch_amharic_keyboard_9.png)
+
+## Key properties
+
+For each visual key, the appearance and behaviour is determined by a number of
+properties:
+
+### Key code
+
+Each key must be given an identifying key code which is unique to the key layer.
+Key codes by and large correspond to the virtual key codes used when creating a
+keyboard program for a desktop keyboard, and should start with `K_`, for keys
+mapped to standard Keyman virtual key names, e.g. `K_HYPHEN`, and `T_` or `U_`
+for user-defined names, e.g. `T_ZZZ`. If keyboard rules exist matching the key
+code in context, then the output from the key will be determined by the
+processing of those rules. It is usually best to include explicit rules to
+manage the output from each key, but if no rules matching the key code are
+included in the keyboard program, and the key code matches the pattern
+`U_xxxx[_yyyy...]` (where `xxxx` and
+`yyyy` are 4 to 6-digit hex strings), then the Unicode characters
+`U+xxxx` and `U+yyyy` will be output. As of Keyman 15, you
+can use more than one Unicode character value in the id (earlier versions
+permitted only one). The key code is always required, and a default code will
+usually be generated automatically by Keyman Developer.
+
+- `K_xxxx` is used for a standard Keyman Desktop key name, e.g.
+ `K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
+ Many of the `K_` ids have overloaded output behaviour, for instance, if no
+ rule is matched for `K_W`, Keyman will output 'w' when it is touched. The
+ standard key names are listed in [Virtual Keys and Virtual Character
+ Keys](/developer/language/guide/virtual-keys "Virtual Keys and Virtual
+Character Keys"). Typically, you would use only the "common" virtual key
+ codes.
+
+- `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`. If you wanted
+ to use it, `T_ENTER` would also be valid. If no rule matches it, the key
+ will have no output behaviour.
+
+- `U_####[_####]` is used as a shortcut for a key that will output those
+ Unicode values, if no rule matches it. This is similar to the overloaded
+ behaviour for `K_` ids. Thus `####` must be valid Unicode characters. E.g.
+ `U_0259` would generate a schwa if no rule matches. It is still valid to
+ have a rule such as `+ [U_0259] > ...`
+
+As noted above, some `K_xxxx` codes emit characters, if no rule is defined.
+There are also some codes which have special functions:
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_ENTER`
+ Submit a form, or add a new line (multi-line); the key action may vary depending on the situation.
+
+
+ `K_BKSP`
+ Delete back a single character. This key, if held down, will repeat. It is the only key code which triggers
+ repeat behavior.
+
+
+ `K_LOPT`
+ Open the language menu (aka Globe key).
+
+
+ `K_ROPT`
+ Hide the on screen keyboard.
+
+
+ `K_TAB`, `K_TABBACK`, `K_TABFWD`
+ Move to next or previous element in a form. Note that these key functions are normally
+ implemented outside the touch layout, so should not typically be used. `K_TAB` will go to previous
+ element if used with the `shift` modifier.
+
+
+
+
+Any key can be used to switch keyboard layers (see
+[`nextlayer`](#toc-nextlayer)), but the following layer-switching key codes have
+been added for switching to some commonly used secondary layers. Note that these
+keys have no specific meaning; you must still set the `nextlayer` property on
+the key.
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_NUMERALS`
+ Switch to a numeric layer
+
+
+ `K_SYMBOLS`
+ Switch to a symbol layer
+
+
+ `K_CURRENCIES`
+ Switch to a currency layer
+
+
+ `K_SHIFTED`
+ Switch to a shift layer
+
+
+ `K_ALTGR`
+ Switch to a right-alt layer (desktop compatibility)
+
+
+
+
+### Key text
+
+The key text is simply the character (or characters) that you want to appear on
+the key cap. This will usually be the same as the characters generated when the
+key is touched, unless contextual rules are used to generate output according to
+a multi-key sequence, as will be true for the GFF Amharic keyboard. Unicode
+characters can be specified either as a string using a target font or using the
+standard hex notation `\uxxxx`. This may be sometimes more convenient, for
+example, for characters from an uninstalled font, or for diacritic characters
+that do not render well alone.
+
+A number of special text labels are recognized as identifying special purpose
+keys, such as Shift, Backspace, Enter, etc., for which icons are more
+appropriately used than a text label. A special font including these icons is
+included with Keyman and automatically embedded and used in any web page using
+Keyman. The list of icons in the font will probably be extended in future, but
+for now the following special labels are recognized:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Shift*`
+
+ Select Shift layer (inactive). Use on the Shift key to indicate that it switches to the shift layer.
+
+
+ `*Shifted*`
+
+ Select Shift layer (active). Use on the Shift key on the shift layer to switch back to the default layer.
+
+
+ `*ShiftLock*`
+
+ Switch to Caps layer (inactive). Not commonly used; generally double-tap on Shift key is used to access the
+ caps layer.
+
+
+ `*ShiftedLock*`
+
+ Switch to Caps layer (active). Use on the Shift key on the caps layer to switch back to the default layer.
+
+
+
+ `*Enter*`
+ or
+ Return or Enter key (shape determined by writing system direction)
+
+
+ `*LTREnter*`
+
+ Return or Enter key (left-to-right script shape)
+
+
+ `*RTLEnter*`
+
+ Return or Enter key (right-to-left script shape)
+
+
+ `*BkSp*`
+ or
+ Backspace key (shape determined by writing system direction)
+
+
+ `*LTRBkSp*`
+
+ Backspace key (left-to-right script shape)
+
+
+ `*RTLBkSp*`
+
+ Backspace key (right-to-left script shape)
+
+
+ `*Menu*`
+
+ Globe key; display the language menu. Use on the `K_LOPT` key.
+
+
+ `*Hide*`
+
+ Hide the on screen keyboard. Use on the `K_ROPT` key.
+
+
+ `*ABC*`
+
+ Select alphabetic layer (Uppercase)
+
+
+ `*abc*`
+
+ Select alphabetic layer (Lowercase)
+
+
+ `*123*`
+
+ Select the numeric layer
+
+
+ `*Symbol*`
+
+ Select the symbol layer
+
+
+ `*Currency*`
+
+ Select the currency symbol layer
+
+
+ `*ZWNJ*`
+ (iOS) or (Android)
+ Zero Width Non Joiner (shape determined by current platform)
+
+
+ `*ZWNJiOS*`
+
+ Zero Width Non Joiner (iOS style shape)
+
+
+ `*ZWNJAndroid*`
+
+ Zero Width Non Joiner (Android style shape)
+
+
+ `*ZWNJGeneric*`
+
+ Zero Width Non Joiner (not platform-specific)
+
+
+ `*Sp*`
+
+ Regular space
+
+
+ `*NBSp*`
+
+ No-Break Space
+
+
+ `*NarNBSp*`
+
+ Narrow No-Break Space
+
+
+ `*EnQ*`
+
+ En Quad
+
+
+ `*EmQ*`
+
+ Em Quad
+
+
+ `*EnSp*`
+
+ En Space
+
+
+ `*EmSp*`
+
+ Em Space
+
+
+ `*PunctSp*`
+
+ Punctuation Space
+
+
+ `*ThSp*`
+
+ Thin Space
+
+
+ `*HSp*`
+
+ Hair Space
+
+
+ `*ZWSp*`
+
+ Zero Width Space
+
+
+ `*ZWJ*`
+
+ Zero Width Joiner
+
+
+ `*WJ*`
+
+ Word Joiner
+
+
+ `*CGJ*`
+
+ Combining Grapheme Joiner
+
+
+ `*LTRM*`
+
+ Left-to-right Mark
+
+
+ `*RTLM*`
+
+ Right-to-left Mark
+
+
+ `*SH*`
+
+ Soft Hyphen
+
+
+ `*HTab*`
+
+ Horizontal Tabulation
+
+
+
+
+
+The following additional symbols are also available, but intended for working
+with legacy desktop layouts, and not recommended for general use:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Tab*`
+
+ Move to next input element in tab order
+
+
+ `*TabLeft*`
+
+ Move to previous input element in tab order
+
+
+ `*Caps*`
+
+ Select caps layer (legacy)
+
+
+ `*AltGr*`
+
+ Select AltGr (Right-Alt) layer (desktop layout compatibility)
+
+
+ `*Alt*`
+
+ Select Alt layer (desktop layout compatibility)
+
+
+ `*Ctrl*`
+
+ Select Ctrl layer (desktop layout compatibility)
+
+
+ `*LAlt*`
+
+ Select Left-Alt layer (desktop layout compatibility)
+
+
+ `*RAlt*`
+
+ Select Right-Alt layer (desktop layout compatibility)
+
+
+ `*LCtrl*`
+
+ Select Left-Ctrl layer (desktop layout compatibility)
+
+
+ `*RCtrl*`
+
+ Select Right-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrl*`
+
+ Select Left-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*RAltCtrl*`
+
+ Select Right-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrlShift*`
+
+ Select Left-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RAltCtrlShift*`
+
+ Select Right-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltShift*`
+
+ Select Alt-Shift layer (desktop layout compatibility)
+
+
+ `*CtrlShift*`
+
+ Select Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltCtrlShift*`
+
+ Select Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*LAltShift*`
+
+ Select Left-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*RAltShift*`
+
+ Select Right-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*LCtrlShift*`
+
+ Select Left-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RCtrlShift*`
+
+ Select Right-Ctrl-Shift layer (desktop layout compatibility)
+
+
+
+
+### Key type
+
+The general appearance of each key is determined by the key type, which is
+selected (in Keyman Developer) from a drop-down list. While generally behavior
+is not impacted by the key type, Spacer keys cannot be selected.
+
+
+
+
+ Key Type
+ Value
+ Meaning
+
+
+
+
+ Default
+ `0`
+ Any normal key that emits a character
+
+
+ Special
+ `1`
+ The frame keys such as Shift, Enter, BkSp.
+
+
+ Special (active)
+ `2`
+ A frame key which is currently active, such as the Shift key on the shift layer.
+
+
+ Deadkey
+ `8`
+ Does not impact behavior, but colors the key differently to indicate it has a special function, such as a
+ desktop-style deadkey.
+
+
+ Blank
+ `9`
+ A blank key, which may be used to maintain a layout shape. Usually colored differently. Does not impact
+ behavior.
+
+
+ Spacer
+ `10`
+ Does not render the key, but leaves a same-sized gap in its place. The key cannot be selected.
+
+
+
+
+The colour, shading and borders of each key type is actually set by a style
+sheet which can be customized by the page developer.
+
+### font-family
+
+If a different font is required for a particular key text, the `font-family`
+name can be specified. The font used to display icons for the special keys (as
+mentioned above) does not need to be specified, as it will be automatically
+applied to a key that uses any of the special key text labels.
+
+### font-size
+
+If a particular key cap text requires a different font size from the default for
+the layout, it should be specified in em units. This can be helpful if a the key
+text is either an unusually large character or, alternatively, a word or string
+of several characters that would not normally fit on the key.
+
+### width
+
+The layout is scaled to fit the widest row of keys in the device width, assuming
+a default key width of 100 units. Keys that are to be wider or narrower than the
+default width should have width specified as a percentage of the default width.
+For any key row that is narrower than the widest row, the width of the last key
+in the row will be automatically increased to align the right hand side of the
+key with the key with the right edge of the keyboard. However, where this is not
+wanted, a "spacer" key can be inserted to leave a visible space instead. As
+shown in the above layouts, where the spacer key appears on the designer screen
+as a narrow key, but will not be visible in actual use.
+
+### pad
+
+Padding to the left of each key can be adjusted, and specified as a percentage
+of the default key width. If not specified, a standard padding of 5% of the key
+width is used between adjacent keys.
+
+### layer
+
+To simplify correspondence with desktop keyboards and avoid the need for using a
+separate keyboard mapping program, touch layout keys can specify a desktop
+keyboard layer that the keystroke should be interpreted as coming from. Layer
+names of `shift`, `ctrl`, `alt`, `ctrlshift`, `altshift`, `ctrlalt` and
+`ctrlaltshift` can be used to simulate use of the appropriate modifier keys when
+processing rules.
+
+### nextlayer
+
+The virtual keys `K_SHIFT`, `K_CONTROL`, `K_MENU`, etc. are normally used to
+switch to another key layer, which is implied by the key code. The left and
+right variants of those key codes, and also additional layer-switching keys
+mentioned above (`K_NUMERALS`, `K_SYMBOLS`, `K_CURRENCIES`, `K_ALTGR`) can also
+be used to automatically switch to the appropriate key layer instead of
+outputting a character. However, it is sometimes useful for a key to output a
+character first, then switch to a new layer, for example, switching back to the
+default keyboard layer after a punctuation key on a secondary layer had been
+used. Specifying the `nextlayer` for a key allows a different key layer to be
+selected automatically following the output of the key. Of course, that can be
+manually overridden by switching to a different layer if preferred.
+
+Another way the `nextlayer` property can be used is for a non-standard layer
+switching key. So, for example, for the GFF Amharic keyboard phone layout,
+switching back to the base layer uses a `T_ALPHA` key code, in which `nextlayer`
+is set as default. In this case, it is also necessary to add a rule to the
+keyboard program:
+
+```keyman
++ [T_ALPHA] > nul
+```
+
+to ensure that the key's scan code is ignored by the keyboard mapping.
+
+When a key in a touch layout definition includes a **Next Layer** control, this
+takes precedence over setting layer via the
+[`layer`](/developer/language/reference/layer) store (as the **Next Layer**
+control is applied once the rule has finished processing).
+
+### subkey
+
+Arrays of longpress 'subkeys' or pop-up keys can be defined for any key, and
+will appear momentarily after the key is touched if not immediately released.
+This provides a major advantage over physical desktop keyboards in that many
+more keys can be made available from a single layer, without cluttering up the
+basic appearance of the layout. For the GFF Amharic keyboard, we have already
+noted how such subkey arrays are used to manage the extra keys that, on the
+desktop keyboard, would appear in the shift layer. But they are also used to
+provide another way to enter the two different types of each syllable-initial
+vowels (glottal or pharyngeal), as a visual alternative to pressing the key
+twice.
+
+The same properties that are defined for standard keys can also be specified for
+each subkey except that the width of each key in a subkey array will always be
+the same as the width of the key that causes the subkeys to be shown, and key
+spacing always uses the default padding value.
+
+The GFF Amharic keyboard, like many others, is mnemonic, so it is useful to also
+display the standard key cap letter that would appear on the key of a desktop
+keyboard. This is enabled globally in the On-Screen layout editor and applies to
+both the On-Screen keyboard and touch layouts.
+
+## Representing (and editing) the visual layout with JSON code
+
+In case you are wondering, 'Why do I need to know that?', the reason is that,
+just as with keyboard mapping code, it is sometimes easier to edit a text
+specification than to use the GUI layout design tool. Keyman Developer switches
+seamlessly between the visual layout tool and the code editor, unless, of
+course, careless editing of the code results in invalid JSON syntax!
+
+The GFF Amharic phone layout code starts as:
+
+```javascript
+ {
+ "phone": {
+ "font": "Tahoma",
+ "layer": [{
+ "id": "default",
+ "row": [{
+ "id": 1,
+ "key": [{
+ "id": "K_Q",
+ "text": "ቅ",
+ "pad": "0"
+ }, {
+ "id": "K_W",
+ "text": "ው"
+ },
+
+ . . .
+```
+
+As long as standard JSON syntax is remembered - nested braces {…}, quoted
+strings "…" for both element names and values, element or object arrays in
+square brackets […], and no trailing comma after the last element in an array -
+it is quite easy to understand a layout, which will usually comprise a list of
+two separate JSON objects for tablet and phone.
+
+Now you're ready to create a great touch layout for your own Keyman keyboard!
+
+Other articles on developing touch layouts:
+
+- [Creating a touch keyboard layout for Amharic - part 1](creating-a-touch-keyboard-layout-for-amharic)
+- [How to test your keyboard layout — touch and desktop](../test/keyboard-touch-and-desktop)
+
+You can distribute your keyboard to other users by following the instructions in
+this article:
+
+- [Distribute keyboards to Keyman applications](../distribute/packages)
diff --git a/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic.md b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic.md
new file mode 100644
index 00000000000..3dc96dfc950
--- /dev/null
+++ b/developer/docs/help/guides/develop/creating-a-touch-keyboard-layout-for-amharic.md
@@ -0,0 +1,124 @@
+---
+title: Creating a Touch Keyboard Layout Part 1
+---
+
+This article describes the approach taken and some of the issues encountered when adapting the existing GFF‑AMH‑7 Amharic keyboard for easier use on phone and tablet touch screen devices.
+
+A widely used Keyman Desktop phonetic keyboard for Amharic (developed by Geez Frontier Foundation) was also compiled and made available for use with KeymanWeb. When used with web browsers on desktop computers, the behaviour of the keyboard was identical to use with Keyman Desktop, but use of the same Amharic keyboard on smart phones or other touch screen devices revealed a number of weaknesses in the layout.
+
+### Background
+
+The desktop layout has a default layer and a shift layer which are fairly similar:
+
+![](../../images/touch_amharic_keyboard.png)
+
+
+Several keycaps have more than one Geez script letter, indicating that pressing the key twice changes the character output. Geez script is better understood as an Abugida, rather than an alphabet, since each consonant-vowel syllable is a modification of the original letter rather than having separate letters for consonant and vowel. So when entering Amharic, the form of a consonant changes from the default syllable form to the appropriate syllable character for the consonant and vowel pressed. However, as each vowel can also occur in stand-alone syllables as either pharyngeal or glottal forms, the two separate syllables for each vowel are shown on the "a", "e", "i", "o" and "u" keys.
+
+But what about the extra characters on the "s" and "h" keys? Amharic (like most Semitic languages) is rich in fricative sounds, and Geez has four separate, distinct phonemes that are roughly similar to the English
+"s", and three similar to "h". The desktop layout handles this by using
+repeated keystrokes in combination with shift to select the alternate
+consonants (before typing the appropriate vowel). That works for a
+desktop keyboard, but not for a touch screen device, for which it is
+often impractical to hold a shift key while touching another key.
+
+Small format touch-screen devices such as phones have a further
+difficulty in that if they display the full number of key rows and keys
+in each row that appear in the desktop layout, the keys are too small
+for convenient use.
+
+On the other hand, touch screen devices offer one very significant
+advantage over layouts based on a physical keyboard - it is quite easy
+to configure dynamic touch screen layouts to display contextually
+appropriate character alternates in separate key windows as each key is
+touched.
+
+### Design goals
+
+With the above background, the following design goals were set for
+developing a touch screen layout for Amharic:
+- Maintain the same basic phonetic layout functionality that users are familiar with.
+- Instead of having "default" and "shift" layers, design the layout so that all alphabetic text and frequently used punctuation can be entered from a single layer, moving less commonly used punctuation and symbols to the second "symbol" layer.
+- Use a dynamic window rather than the shift key to select the initial "s" or "h" consonant for a syllable.
+- Use a separate key (adjacent to the space bar) for the Ethiopic word-space character, instead of requiring shift + space to be pressed.
+- For tablet devices, increase key size slightly by removing most non-letter keys from each row, moving them either to dynamic ("pop-up") key windows, or to the symbol layer.
+- For phones and other small-format devices, further increase the key size by not displaying the digits in the default (alphabetic) layer, and moving many of the less commonly used characters to dynamic windows, reducing the number of key rows by one for each layer.
+
+### Implementation
+
+Achieving the design goals required making a number of decisions for
+each format (tablet and phone), in particular:
+
+- Which keys can be removed from the default layer?
+- Which keys can be removed from the symbol layer?
+- Which "pop-up" keys should be grouped with each base character?
+
+Keyman Developer 17's Touch Layout editor greatly simplifies the process
+of developing the JSON layout file that specifies the touch layout for
+each device, making it easy to move keys around, create pop-up key
+arrays, and adjust key widths, labels and other parameters. While the
+graphical interface does not allow moving entire key rows between layers
+or device types, that is easily managed using the code window.
+
+To keep a keyboard compatible with the desktop version, it is important
+to use the same key identifiers and "shift states". So, no matter
+whether a particular character occurs in a visible row or in a pop-up
+array, it should usually use the same code as on the original desktop
+layout keyboard.
+
+Where a key did not occur in the original layout and does not correspond
+to a desktop layout key, identifiers will usually be something like
+T\_<name> where <name> will typically be a Unicode character
+name, or an obvious contraction of it. Note that this is just a rule of
+thumb and key identifiers can be anything that makes sense. Such keys
+are distinct from the K\_<name> identifiers that are associated
+with physical keys, and should normally have a mapping rule added
+explicitly to the mapping program. If no rule is added, output will
+default to the keycap text, but this is not recommended.
+
+All key "rotations" - pressing the same key repeatedly to select from
+alternate output characters - in the original desktop keyboard program
+are still supported, but have been supplemented by pop-up key arrays,
+allowing a user to visually select the wanted character instead of
+stepping through alternates. Similarly, Ethiopian numerals can be
+selected from a pop-up key array attached to each numeral key, as well
+as being output by pressing the single quote key before a digit key.
+
+The final tablet default keyboard layer, as displayed in the Touch
+Layout editor in the previous release of Keyman Developer 10, now appears
+as:
+
+![](../../images/touch_amharic_keyboard_2.png)
+
+Here the editor is shown with the "s" key selected, displaying the
+pop-up key array with the four different Geez characters that can be
+produced, each of which will combine as necessary with any subsequently
+typed vowel.
+
+For the small format (e.g. iPhone) layout, the two keyboard layers are:
+
+![](../../images/touch_amharic_keyboard_3.png)
+
+For these layouts, the row length has been reduced to the minimum number
+of ten keys, moving backspace to another row. The numerals only appear
+on the second layer, as is usual with phone layouts. All less-frequently
+used keys are now available only in pop-up key windows.
+
+### Summary
+
+With these principles guiding all the changes made, the Amharic keyboard
+now adapts to various device form factors, making use of device-specific
+usability factors, while retaining the same conceptual input model as
+the original keyboard. This means that users can use the keyboard layout
+on different devices with a minimum of difficulty. These methods should
+be applicable to other keyboard layouts.
+
+Other articles on developing touch layouts:
+
+- [Creating a touch keyboard layout for Amharic - the nitty gritty](creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+- [How to test your keyboard layout — touch and desktop](../test/keyboard-touch-and-desktop)
+
+You can distribute your keyboard to other users by following the
+instructions in this article:
+
+- [Distribute keyboards to Keyman applications](../distribute/packages)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/imxdll.md b/developer/docs/help/guides/develop/imx/imxdll.md
new file mode 100644
index 00000000000..8d4fe029bcd
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/imxdll.md
@@ -0,0 +1,115 @@
+---
+title: DLL Exports
+---
+
+The DLL is called from Keyman with `LoadLibrary()`. All functions are
+then found with `GetProcAddress()`. You must ensure that the function
+exports do not have ordinals encoded in the names. The best way to
+accomplish this in C/C++ is to use a .def file.
+
+## DLL group function exports
+
+The function declaration for the DLL group function is:
+
+```c
+BOOL WINAPI KeyEvent(HWND hwndFocus, WORD KeyStroke, WCHAR KeyChar, DWORD ShiftFlags);
+```
+
+Note that `KeyEvent()` is a placeholder for any name that you wish to
+use. You can have multiple exports for Keyman use in a single DLL.
+
+| | |
+|------------|-------|
+| hwndFocus | The currently focused window. You will probably never have a need to use this. |
+| KeyStroke | The virtual key code for the current key. |
+| KeyChar | The character code for the current key (based on US English layout). This will be `0` if _KeyStroke_ does not generate a character (e.g. function keys). |
+| ShiftFlags |The shift state for the current key. The following shift states are possible: (see the table below) |
+
+| Flag | Value | Description |
+|--------------|----------|----------------------------------------|
+| LCTRLFLAG | `0x0001` | Left Control Flag |
+| RCTRLFLAG | `0x0002` | Right Control Flag |
+| LALTFLAG | `0x0005` | Left Alt Flag |
+| RALTFLAG | `0x0008` | Right Alt Flag |
+| K_SHIFTFLAG | `0x0010` | Shift flag |
+| K_CTRLFLAG | `0x0020` | Ctrl flag (unused here; see l/r flags) |
+| K_ALTFLAG | `0x0040` | Alt flag (unused here; see l/r flags) |
+| CAPITALFLAG | `0x0100` | Caps lock on |
+| NUMLOCKFLAG | `0x0400` | Num lock on |
+| SCROLLFLAG | `0x1000` | Scroll lock on |
+| ISVIRTUALKEY | `0x4000` | It is a Virtual Key Sequence |
+
+## Optional DLL Exports
+
+Keyman recognises a number of other exports, if they are defined in the
+DLL. None of these are required. These functions will be called when a
+keyboard that references the DLL is manipulated. They will not be called
+for keyboards that do not reference the DLL.
+
+The following exports are available:
+
+### KeymanIMInit
+
+```c
+BOOL WINAPI KeymanIMInit(PSTR keyboardname);
+```
+
+`KeymanIMInit()` is called once when the keyboard identified by
+`keyboardname` is loaded for a given process. It is called for each
+process in which the keyboard is loaded.
+
+### KeymanIMDestroy
+
+```c
+BOOL WINAPI KeymanIMDestroy(PSTR keyboardname);
+```
+
+This is called once when the keyboard identified by `keyboardname` is
+unloaded in a given process. It is called when the process exits
+normally, or when Keyman refreshes its keyboard list after keyboards are
+added or removed. If the keyboard is subsequently reloaded,
+`KeymanIMInit()` will be called again.
+
+### KeymanIMActivate
+
+```c
+BOOL WINAPI KeymanIMActivate(PSTR keyboardname);
+```
+
+This function is called whenever the user or a program activates the
+keyboard. It is never called before `KeymanIMInit()` (unless
+`KeymanIMInit()` is not exported). This is an appropriate place to
+switch on permanently-visible IMC windows. `KeymanIMActivate()` can also
+be called when switching processes and the target process has a related
+keyboard already active.
+
+### KeymanIMDeactivate
+
+```c
+BOOL WINAPI KeymanIMDeactivate(PSTR keyboardname);
+```
+
+This function is called when the user or a program switches off a
+related keyboard. It is always called before `KeymanIMActivate()` for
+the next keyboard. It will also be called when the user activates
+another process, to give the DLL a chance to hide top-most IMC windows.
+
+### KeymanIMConfigure
+
+```c
+BOOL WINAPI KeymanIMConfigure(PSTR keyboardname, HWND hwndParent);
+```
+
+`KeymanIMConfigure()` is called when the user clicks the Configure
+button in Keyman Configuration to configure the DLL-specific
+functionality for the keyboard. The appropriate behaviour is to display
+a dialog box, and save the settings in the registry.
+
+This function is separate from all the other functions. It can be called
+when there are no keyboards loaded, or even if Keyman itself is not
+loaded. You should not attempt to call Keyman32.dll from this function.
+
+## See also
+
+- [DLL Interface for Keyman - Introduction](index)
+- [The imlib.cpp library module](imxlib)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/imxlib.md b/developer/docs/help/guides/develop/imx/imxlib.md
new file mode 100644
index 00000000000..178ae0f3eff
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/imxlib.md
@@ -0,0 +1,208 @@
+---
+title: The imlib.cpp library module
+---
+
+imlib.cpp, included in the development kit, contains a set of useful
+functions for interfacing to Keyman.
+
+## PrepIM
+
+```c
+BOOL PrepIM(void);
+```
+
+`PrepIM()` initialises the Keyman32 imports. You should not call any of
+the Keyman imports without calling `PrepIM()` first. If `PrepIM()`
+fails, you should exit without doing any processing.
+
+## IMDefWindowProc
+
+```c
+BOOL IMDefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *lResult);
+```
+
+`IMDefWindowProc()` should be called from an IMC window procedure (see
+section titled Input Method Composition windows). If it returns `TRUE`
+you should return the value stored in `lResult` without any further
+processing. `IMDefWindowProc()` mostly manages window activation and
+movement.
+
+## Keyman Imports
+
+The DLL can call Keyman functions to interact with Keyman and the target
+application. It should not attempt to directly control the application
+as Keyman will be doing this. You should never call any of the functions
+here from the `KeymanIMConfigure()` callback.
+
+You can use `PrepIM()`, declared in imlib.cpp to get access to the the
+Keyman functions. When using imlib.cpp, the functions are declared as
+pointers, so you need to dereference them to call them in C (e.g. for
+KMGetContext, call `(*KMGetcontext)(buf,len);`)
+
+## KMGetContext
+
+```c
+BOOL WINAPI KMGetContext(PWSTR buf, DWORD len);
+```
+
+`KMGetContext()` returns the last `len-1` UTF-16 codepoints of the
+context stack. If there are not enough characters in the context stack,
+it will return as many as it can. On success, the `buf` variable will be
+null terminated.
+
+The context stack can contain a special code for deadkeys. See
+`KMQueueAction()` for a way to output a deadkey. The code sequence for a
+deadkey is (3 words):
+
+```
+UC_SENTINEL, CODE_DEADKEY, deadkeyID
+```
+
+`UC_SENTINEL` is `0xFFFF`; `CODE_DEADKEY` is `0x0008`; `deadkeyID` can
+be any value from `0x0001` to `0xFFFE`.
+
+> ### Note
+Changes in 8.0.333.0: a potential buffer overflow has been corrected.
+The size of the buffer pointed to by `buf` should be `WCHAR[len+1]`
+to allow for terminating null. Keyman now counts supplementary plane
+characters as 2 UTF-16 codepoints rather than as a single codepoint.
+Keyman no longer returns partial deadkey code sequences.
+
+## KMSetOutput
+
+```c
+BOOL WINAPI KMSetOutput(PWSTR buf, DWORD backlen);
+```
+
+`KMSetOutput()` is a wrapper for `KMQueueAction()`. It simplifies the
+process of deleting contextual characters and outputting a new string.
+The results will not be output to the screen until the current function
+returns. If called within the context of an IMC window, the results will
+not be output to the screen until the window posts the
+`wm_keymanim_close` message.
+
+`buf` is a pointer to a null-terminated string of characters to output.
+`backlen` is the number of characters to backspace from the current
+context before displaying `buf`.
+
+This function modifies the context returned from `KMGetContext()`, even
+if the output is not yet on the screen.
+
+Internally, this function does the following:
+
+```c
+while(backlen-- > 0) KMQueueAction(QIT_BACK, 0);
+while(*buf) KMQueueAction(QIT_CHAR, *buf++);
+```
+
+## KMQueueAction
+
+```c
+BOOL WINAPI KMQueueAction(int itemType, DWORD dwData);
+```
+
+`KMQueueAction()` lets you send any Keyman action to a target
+application. This can be virtual keys, characters, shift keys up and
+down, deadkeys, beeps, or backspaces (a special case of virtual keys).
+
+| | |
+|------------------|----------------|
+| itemType code | Description |
+| `QIT_VKEYDOWN` | Simulate any key press on the keyboard; `dwData` is the virtual key code |
+| `QIT_VKEYUP` | Simulate any key release on the keyboard; `dwData` is the virtual key code |
+| `QIT_VSHIFTDOWN` | Simulate pressing a set of shift keys. `dwData` can be a combination of the following flags: `LCTRLFLAG, RCTRLFLAG, LALTFLAG, RALTFLAG, K_SHIFTFLAG, K_CTRLFLAG, K_ALTFLAG` |
+| `QIT_VSHIFTUP` | Release the shift state, `dwData` is the same as the previous flags. |
+| `QIT_CHAR` | `dwdata` is any `WCHAR`. |
+| `QIT_DEADKEY` | `dwData` is any value from `0x0001` to `0xFFFE`. This can be matched in the context with `KMGetContext()`. |
+| `QIT_BELL` | `dwData` should be zero (`0`). |
+| `QIT_BACK` | `dwData` should be zero (`0`). |
+
+## KMHideIM
+
+```c
+BOOL WINAPI KMHideIM(HWND hwndIM);
+```
+
+`KMHideIM()` hides the IMC window referred to by `hwndIM` and ensures
+that Keyman processes input from the keyboard through the correct
+method. You should call this rather than hiding the window manually with
+`ShowWindow(hwnd, SW_HIDE);` or post the message `wm_keymanim_close` to
+hide the window.
+
+## KMDisplayIM
+
+```c
+BOOL WINAPI KMDisplayIM(HWND hwndIM, BOOL FCaptureAll);
+```
+
+`KMDisplayIM()` displays the IMC window referred to by `hwndIM`. It does
+not do any movement of the window. If the `FCaptureAll` flag is set, all
+keyboard input (character-generating keys only) will be redirected to
+the IMC window until the message `wm_keymanim_close` is posted,
+`KMHideIM()` is called, or `KMDisplayIM()` with `FCaptureAll` set to
+`FALSE`.
+
+## KMGetKeyboardPath
+
+```c
+BOOL WINAPI KMGetKeyboardPath(PSTR keyboardname, PWSTR dir, DWORD length);
+```
+
+This function returns the full path to the keyboard referred to by
+`keyboardname`. The buffer `dir` should be 260 characters long.
+
+## KMGetActiveKeyboard
+
+```c
+BOOL WINAPI KMGetActiveKeyboard(PSTR keyboardname, DWORD length);
+```
+
+This function can be called while processing to determine which is the
+active keyboard. Alternatively, use the callbacks `KeymanIMActivate()`
+and `KeymanIMDeactivate()`.
+
+## KMSendDebugString
+
+```c
+BOOL WINAPI KMSendDebugString(PSTR str);
+```
+
+This function outputs the string `str` to the Keyman debug window or
+debug log file (usually %USERPROFILE%\Desktop\keymanlog\system\*.log).
+
+## The Input Method Composition window
+
+The IMC window can be shown or hidden at any time that the associated
+keyboard is active. This means that you can have an IMC window
+permanently open or open at appropriate times.
+
+The keyboard IMSample included with Keyman is a good example of
+manipulating the IMC display.
+
+The window should be created invisible, most probaly as a popup window.
+The window can use `KMGetContext()`, `KMSetOutput()` at any time, but
+output will not be put to the screen until it has posted (not sent)
+`wm_keymanim_close` to itself.
+
+```c
+PostMessage(hwnd, wm_keymanim_close, (WPARAM) FSuccess, (LPARAM) FActuallyClose);
+```
+
+Keyman will manage the window display, focus, and message loop. The
+window procedure should set the position and size appropriately.
+
+Keyman will recognise this window and any child windows to be part of
+the IM and will not attempt to process any input that goes through the
+window.
+
+The IMC window must not take focus at any time.
+
+## Limitations
+
+- Clicks outside the window will cancel the IM and lose context.
+- Switching applications will cancel the IM and lose context.
+
+## See also
+
+- [DLL Interface for Keyman - Introduction](index)
+- [DLL Exports](imxdll)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/index.md b/developer/docs/help/guides/develop/imx/index.md
new file mode 100644
index 00000000000..6bc606b8f91
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/index.md
@@ -0,0 +1,101 @@
+---
+title: Input Method Extensions
+---
+
+Keyman's multiple group processing is powerful, but sometimes you need
+to be able to do something a bit more complex, such as a dictionary
+lookup. Keyman's DLL interface let you do this. You can call a function
+in a DLL in the same way as you call another group. The function can
+read the context, deadkeys and the current keystroke, and output
+characters, deadkeys, virtual keys, beeps and other items.
+
+The DLL interface also allows you to create a popup Input Method
+Composition (IMC) window. This window allows the user to select visually
+the characters they are wishing to input. The window can be set to be
+visible when the keyboard is active, or after an appropriate key
+sequence. When the window is visible, it can be set to capture all
+keyboard input, or be passive.
+
+## File locations
+
+The DLL should be placed in any of the following locations:
+
+- The same directory as the .kmx file (e.g. use a package to install it)
+- The Keyman program directory (same place as keyman32.dll)
+- Anywhere on the path (such as the Windows directory)
+
+The best option is the first, as you can then include the DLL in a
+Keyman package for easy installation and uninstallation.
+
+Full x64 support for IMX DLLs was introduced in Keyman Desktop
+8.0.333.0. An x64 version of the DLL may be included simply by giving it
+the file extension .x64.dll, with the same base name as the x86 DLL.
+Keyman will call the appropriate DLL.
+
+## General usage information
+
+DLL functions used in place of groups are called DLL group functions.
+
+All strings, apart from keyboard names, are passed as WCHAR, regardless
+of whether the active window is a Unicode window or not. In Keyman 8 and
+earlier, ANSI characters are represented as 16-bit WCHAR, with high bits
+zeroed out. In Keyman 9, ANSI keyboards are translated to Unicode at
+install time, through the CP-1252 code page.
+
+The DLL will be loaded for each process in which the keyboard is
+activated. Remember that the DLL will not share memory between these
+processes by default, so if you have large memory requirements, you
+should use memory mapped files or possibly SHARDATA segments to minimize
+the memory consumption.
+
+DLL group functions are called in a fairly time-critical environment. It
+is important that you minimise the processing time in these functions.
+It is essential that you avoid any window focus or activation – message
+boxes are definitely out of the question. For debugging purposes, there
+is a Keyman32.dll function exported for writing to the logfile (see the
+section titled Keyman32 imports).
+
+DLLs can handle multiple keyboards at once. The keyboards are identified
+by a name which is the filename of the keyboard, minus path and
+extension. For example, given c:\keyman\imsample\imsample.kmx, the
+keyboard name is imsample. These are the same names that Keyman uses
+internally, for example in the registry and directory names.
+
+## Registry settings
+
+Parameters for the DLL can be stored in two locations in the registry.
+They should always be stored under HKEY_CURRENT_USER, as the user will
+not have permission to change machine-wide settings, and the settings
+should not affect other users. The following locations are recommended:
+
+> HKEY_CURRENT_USER\Software\Tavultesoft\Keyman Engine\9.0\IMX\\[DLLName]
+
+> HKEY_CURRENT_USER\Software\Tavultesoft\Keyman Engine\9.0\Installed Keyboards\\[kbdname]
+
+The first key should be used for settings that pertain to any keyboard
+associated with the DLL. The second key should be used for any
+keyboard-specific settings. Values stored under the second key should be
+prefixed with the name of the dll, so that they will not conflict with
+Keyman or other dll values.
+
+## The .kmn interface
+
+Inside a .kmn file, you define the DLL group function interface as follows:
+
+```keyman
+store(DLLFunction) "myfile.dll:KeyEvent"
+```
+
+You can use this anywhere where you would place the use statement
+(except in the begin statement), with the call statement. For example,
+
+```keyman
++ 'a' > call(DLLFunction)
+```
+
+A single .kmn file can reference multiple DLL group functions, in a single or multiple DLLs.
+
+## See also
+
+- [DLL Exports](imxdll)
+- [The imlib.cpp library module](imxlib)
diff --git a/developer/docs/help/guides/develop/imx/web.md b/developer/docs/help/guides/develop/imx/web.md
new file mode 100644
index 00000000000..fa98ba40321
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/web.md
@@ -0,0 +1,4 @@
+---
+title: IMX Interface for KeymanWeb
+---
+
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/imx/web/index.md b/developer/docs/help/guides/develop/imx/web/index.md
new file mode 100644
index 00000000000..9453c5a01de
--- /dev/null
+++ b/developer/docs/help/guides/develop/imx/web/index.md
@@ -0,0 +1,5 @@
+---
+title: Guidelines for building IMX for KeymanWeb
+---
+
+* TODO: Write to fill requirements for .call_js, see call() statement for details.
diff --git a/developer/docs/help/guides/develop/index.md b/developer/docs/help/guides/develop/index.md
new file mode 100644
index 00000000000..8d8490c42c1
--- /dev/null
+++ b/developer/docs/help/guides/develop/index.md
@@ -0,0 +1,19 @@
+---
+title: Developing Keyboards
+---
+
+[Keyman keyboard tutorial](tutorial)
+
+[An advanced keyboard development example](advanced-keyboard-development-example)
+
+[Creating a simple touch keyboard layout](touch-keyboard-tutorial)
+
+[Creating a touch keyboard layout for Amharic - part 1](creating-a-touch-keyboard-layout-for-amharic)
+
+[Creating a touch keyboard layout for Amharic - part 2](creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+
+[Input Method Extensions (IMX)](imx)
+
+------------------------------------------------------------------------
+
+[Keyman Developer 16 Tutorial](https://lingtran.net/Keyman-Developer-16-Tutorial) (external on Lingtran.net)
\ 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
new file mode 100644
index 00000000000..bc7c8e792f2
--- /dev/null
+++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md
@@ -0,0 +1,54 @@
+---
+title: Creating a Simple Touch keyboard
+---
+
+## Introduction and design
+
+If you have experience in designing Keyman keyboards for computers, the
+interface for designing mobile keyboards looks very similar, but it
+hides the reality of additional steps necessary for mobile keyboards.
+This tutorial attempts to walk you through the process.
+
+The [Quick French tutorial](../tutorial/) shows how to create a simple
+desktop keyboard for typing accented characters used in French and other
+European languages. It is possible to create a touch screen format of
+that keyboard, but it is probably unnecessary. The default keyboards for
+both Android and iOS devices have a way to type these accented
+characters. Press and hold on a vowel, and a popup menu will give you a
+choice of accents to put on that letter.
+
+For this tutorial we will demonstrate something usable for the Fulfulde
+language cluster spoken across many Sahelian countries of Africa.
+Fulfulde is usually written using the Latin alphabet, but there are a
+few characters added to the usual list of alphabetical characters. So
+this is a useful example for any language wanting to add a few
+characters to the standard Latin keyboard. Here are the characters we
+are going to add, together with their Unicode values.
+
+| | | | | | | | | |
+|-----|----------|-----|----------|-----|-----|----------|-----|----------|
+| `ɓ` | `U+0253` | `Ɓ` | `U+0181` | | `ɗ` | `U+0257` | `Ɗ` | `U+018A` |
+| `ŋ` | `U+014B` | `Ŋ` | `U+014A` | | `ƴ` | `U+01B4` | `Ƴ` | `U+01B3` |
+
+Here we will likely want to proceed differently than with the desktop
+keyboard. The usual approach for a desktop keyboard is to create a
+deadkey. The keyboards I have used for Fulfulde (going back to decades
+ago) used the / key as the deadkey.
+/ b gave ɓ,
+/ 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.
+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
+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.
+
+[Next: Making, testing and distributing the touch
+keyboard](making-touch-keyboard)
diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/making-touch-keyboard.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/making-touch-keyboard.md
new file mode 100644
index 00000000000..5039acb2624
--- /dev/null
+++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/making-touch-keyboard.md
@@ -0,0 +1,361 @@
+---
+title: Making a touchscreen keyboard
+---
+
+## Setting up your Keyman project
+
+This is a useful convention for keeping track of your files:
+
+1. Create a folder on your hard drive for all your Keyman developer
+ files
+2. In that folder, create a folder for this project, named after the
+ language. Use all lowercase letters for compatibility with other
+ platforms.
+3. Now create a project in Keyman Developer with the same name as the
+ new folder, and save it inside the new folder
+
+Now you will be able to use this project to organize and create any
+files for desktop, mobile, web or other. It also facilitates uploading
+the files to the Keyman website later on.
+
+## Add touch to an existing keyboard or create a new keyboard?
+
+You can add a mobile touchscreen keyboard to an existing desktop
+keyboard, or you can create a new touchscreen keyboard from scratch. You
+should be aware that there will be cross-over between mobile and desktop
+keyboards. (In Keyman terms, "desktop" includes laptop computers or any
+device with a physical keyboard). Your mobile device can have an
+external keyboard, which will use the desktop Keyman keyboard. So the
+Keyman strategy is to always bundle touch keyboards with desktop
+keyboards to make them work on multiple platforms.
+
+If creating from scratch, you click the New icon in the Keyman Developer
+toolbar (or select File > New from the menu), and specify the file
+name for your keyboard. We recommend a name using only lowercase
+letters, numbers and underscore. I’ll choose “mobile_fulfulde.kmn” .
+
+![choose new file
+type](../../../images/simpleTouchKeyboard_1.png)
+
+## Adding the required metadata
+
+After creating the file, Keyman Developer will ask you to name the
+keyboard. This name can include spaces and upper case letters if
+desired. I’ll name this one “Fulfulde for Mobile”.
+
+![keyboard
+metadata](../../../images/simpleTouchKeyboard_2.png)
+
+The first thing you need to do is to tell Keyman Developer this keyboard
+will include some kind of mobile device. You do this in the Targets
+list just below the keyboard name. I’ll select “mobile ” in the list,
+(towards the end). You see in the picture above that Keyman Developer
+checked the “windows” box by default, I can uncheck that or leave it
+checked if I am going to add a Windows keyboard to this set.
+
+![mobile target](../../../images/simpleTouchKeyboard_3.png )
+
+This will include all mobile devices, both Android and iOS, both phones
+and tablets. It is possible if needed to select Android only or iOS only
+if needed, and to develop a different keyboard layout for tablets than
+for phones, but for this example, with only a few characters needed to
+be added to the default keyboards, there is no need for that kind of
+complexity.
+
+(If you are adding a mobile layout to an existing keyboard file, you
+would open that file in Keyman Developer, go to the Targets box and
+check “mobile” or whatever mobile targets you wanted to specify, then
+follow along from here).
+
+## Adding the touch optimized feature
+
+The next step is to add the touch-optimised feature to our keyboard
+setup. If you scroll to the bottom of the details tab, there is the list
+of features to add. I’ll click the Add button and choose “touch
+optimised keyboard.”
+
+![touch-optimised](../../../images/simpleTouchKeyboard_4.png)
+
+Keyman developer asks me to choose one of three templates for the mobile
+layout.
+
+![three templates](../../../images/simpleTouchKeyboard_5.png)
+
+The “template-latin” layout includes many accented characters, like the
+default keyboards for Android and iOS do. The “template-basic” layout
+has no extra characters, and the template-traditional has a few extra
+characters (mostly punctuation symbols if you long-press period or full
+stop). For this example, I’ll choose “template-basic” because accented
+characters are not needed for Fulfulde. But if I knew that many eventual
+users might want to be able to type French or other European languages
+as well as Fulfulde, and wouldn’t like to have to switch the keyboard to
+do so, I could choose “template-latin”.
+
+## Defining one or several touch layouts
+
+I select the template and click OK. Keyman Developer now adds a new tab
+in the left column, the Touch Layout tab.
+
+![touch layout](../../../images/simpleTouchKeyboard_6.png)
+
+That is where we need to go now to add characters to our touch layout.
+But before doing that, there is one important thing to take note of in
+the touch layout. By default, Keyman Developer has created a layout for
+phones and another layout for tablets. They all start out the same, but
+as I add characters, I will have to make sure I add all the characters
+to both layouts, or I’ll discover that my keyboard does not work for all
+device sizes.
+
+In the image, you see in the Platform box that Keyman Developer is currently showing the
+tablet layout. This box is where I’d choose which layout I’d design in.
+
+But I’m going to make this keyboard simple, and just have one layout for
+both tablets and phones. So in the platform box, I am going to click
+the “minus” button, to delete the tablet layout. When there is only one
+mobile layout, Keyman Developer will compile that layout for all mobile
+devices. (I could also have chosen to delete the phone layout and done
+my layout design in the tablet layout).
+
+![platform list](../../../images/simpleTouchKeyboard_7.png)
+
+## Adding longpress characters
+
+Now I can start adding characters. I’ll click on the “y” key in the
+layout, then I’ll look at the “longpress keys” area at the lower
+left.
+
+![add longpress](../../../images/simpleTouchKeyboard_8.png)
+
+I click that, and Keyman Developer adds a new key below the keyboard
+layout.
+
+![longpress added](../../../images/simpleTouchKeyboard_9.png)
+
+I'm going to use this key for the y with hook. I click inside it to make it active, then I’ll go look for my y
+with hook character in the character map to the right. If I type “y
+hook” in the search box, Keyman Developer shows the character I’m
+looking for.
+
+![adding keycap](../../../images/simpleTouchKeyboard_10.png)
+
+If I double click on the lower case y with hook, that copies the character
+to the keycap box of my new key. (I could also click and drag the character from the character map to the key).
+
+![keycap added](../../../images/simpleTouchKeyboard_11.png)
+
+## New key info
+
+There are several important pieces of information that got copied over. If we
+look at the properties of the new key to the right, we see that this new key has
+the “text” ƴ (what appears on the keycap), the “Unicode value” U+01B4 and the
+“ID” U_01B4. All of these were copied from the character map.
+
+NOTE: If you copy one character from the character map to a new key, then
+realize you copied the wrong character, dragging a second character may not
+replace all the needed information into the key. If a key has a character in it
+already, it works better to hold Ctrl as you drag the new character
+into it.
+
+I could add the other three characters; hook b, hook d, and eng, as longpress
+characters as well. I could select the regular letter I want to longpress,
+click plus in the longpress keys area and copy the needed character. Here for
+example is the b with hook:
+
+![keycap added](../../../images/simpleTouchKeyboard_12.png)
+
+After I have added my four lowercase characters as longpress keys, the keyboard
+looks like this. Even when I don't have that letter selected, I can see which
+keys have longpress keys by the dot beside the letter.
+
+![keycap added](../../../images/simpleTouchKeyboard_12-2.png)
+
+[See the Keyman Language guide on virtual keys](../../../../language/guide/virtual-keys#toc-virtual-keys-and-touch-layouts)
+
+## Adding uppercase characters
+
+I could do two different things for the upper case letters. I could add them as
+longpress keys beside their lowercase equivalents, by pressing the plus key
+again to add a second longpress character to each letter.
+
+This would mean users would not have to press Shift to be able to type the upper
+case hook y. But since a user needs to press Shift for all the regular Latin
+upper case letters, it might make more sense to put the uppercase letters into
+the Shift layer.
+
+I can change to the shift layer by finding the layer box at lower left, and
+choosing `shift` instead of `default`.
+
+![changing to shift layer](../../../images/simpleTouchKeyboard_13.png)
+
+Now I see the upper case letters on the keyboard, and I can add longpress
+characters to these in the same way I did on the default layer. I click the
+letter, then click `+` to add a key, then copy the character info from the
+character map. Adding my upper case y hook to Y would look like this:
+
+![new key added to row](../../../images/simpleTouchKeyboard_14.png)
+
+(If I had clicked on the green triangle to the left of my first key, the
+new key would be added to the left of that key).
+
+For this new key, I go through the same steps. I click in the empty box
+on the new key, then double click the upper case y with hook in the
+character map to put that in the keycap. I also change the code of this
+new key to “U_01B3”
+
+![keycap and code added to new
+key](../../../images/simpleTouchKeyboard_15.png)
+
+## Another option for upper case characters
+
+I might also add the upper case character to a longpress character on
+the upper-case Y key, because some users will press the Shift key then
+look for the letter to type. To do this, I change the layer at the top
+of the tablet layout to "shift" from "default".
+
+![changing
+layer](../../../images/simpleTouchKeyboard_15b.png)
+
+Now I click the Y key, and add a longpress character, then add the upper
+case y with hook to the keycap and add the keycode as shown before.
+
+![key added to longpress
+Y](../../../images/simpleTouchKeyboard_16b.png)
+
+You can design your keyboard either way, with the upper case characters
+in the same longpress row as the lower case, or in a longpress row off
+of the shift layer. The rest of this tutorial shows the upper case
+characters in the same row as the lower case, but I don't mean to
+present this as the best way. If you do put upper case characters in the
+shift layer, you'll have to remember to change the layer back to
+"default" when you want to add a lower case letter, then back to "shift"
+when adding an upper case letter. You could put the upper case
+characters in both places, if you think that best.
+
+Now I’ll add the two d hook characters as a longpress popup for the d
+key. I click the d key in the layout, click “Add longpress popup”, add
+the lower case hook d to the keycap and change the code to “U_0257”;
+then click the plus sign to add a second key, add the upper case hook D
+to the keycap and change its code to “U_018A”. The end result should
+look like this:
+
+![longpress row on
+d](../../../images/simpleTouchKeyboard_16.png)
+
+I’ll do the similar step to add two characters as a longpress popup for
+“b”
+
+![longpress row on
+b](../../../images/simpleTouchKeyboard_17.png)
+
+And two characters as a longpress popup for “n”. (Tip, to search for ŋ
+in the character map, type “eng”).
+
+![longpress row on
+n](../../../images/simpleTouchKeyboard_18.png)
+
+When no keys are selected in the keyboard layout, I can still see which
+ones I have set up longpress popups for, because Keyman Developer shows
+a faint gray line at the top right corner of the key. So you should have
+a faint diagonal line on your y, d, b and n keys.
+
+![lines on longpress
+keys](../../../images/simpleTouchKeyboard_19.png)
+
+## Replacing the Unicode and Code automatically
+
+1. Hold the Ctrl key while dragging and
+ dropping a character from the Character Map to the touch layout
+ editor, and it will replace the Code as well as the character.
+2. Hold the Shift key while dropping, and it
+ will only add the character to the key.
+3. Finally, hold
+ Ctrl +Shift , this
+ will add the character to the key AND update the Code to match the
+ full set of characters represented on the key.
+
+## Compiling your keyboard
+
+Now click Save to save your work, and compile the keyboard. (Keyboard
+> Compile Keyboard, or press F7). Hopefully you’ll see it compiled
+with no error messages:
+
+![successful
+compile](../../../images/simpleTouchKeyboard_20.png)
+
+If you see a warning like:
+
+```
+Warning: line 0 warning 2092: Key " T_new_579" on layer "default", platform "phone", is a custom key but has no corresponding rule in the source.
+```
+
+This shows you forgot to enter the correct keycode for a new key you
+added. Double check your longpress popup keys for one with a “T_new_NNN”
+code, and add in the desired “U_NNNN” code.
+
+You might also see a message like
+
+```
+Error: line 0 error 405A: Key " U+0181" on "phone", layer "default" has an invalid identifier.
+```
+
+This shows you attempted to change the keycode but mistyped the code.
+I’ve gotten this more than once, because I am used to typing `U+0181`to
+refer to a Unicode value, but the correct syntax for a keycode in Keyman
+Developer is `U_0181`
+
+# Testing your keyboard
+
+Other Keyman help pages describe how you can test your keyboard:
+
+- [On a virtual device in the Chrome web browser on your
+ computer](../../test/keyboard-touch-mobile-emulator)
+- [On your own device accessing the keyboard over the
+ network](../../test/keyboard-touch-and-desktop)
+
+If your device cannot connect to your computer on the local network (for
+instance if your computer is on a wired office network and your device
+is on the wireless office network), the above technique for sharing may
+not work. You can put your keyboard in a package and share it over the
+Internet with your device, as described below.
+
+# Packaging and distributing your keyboard
+
+Another Keyman help file describes [how to build a package
+file.](../../distribute/tutorial)
+
+## BCP 47 tags for Fulfulde
+
+[Step 3](../../distribute/tutorial/step-3) of the package building
+tutorial describes adding the BCP 47 language tag to your package. When
+I came to the step of adding a tag for Fulfulde, I had to do some
+research. I naively thought "Fulfulde" was a clear enough label for the
+language. It is not, since it is a language chain rather than one
+language. The BCP 47 tag in many cases is the same as the [SIL
+Ethnologue code](https://www.ethnologue.com), so that is where I had to
+go to find the tags I needed. In the end I added four tags for four
+Fulfulde languages:
+
+![BCP 47 tags](../../../images/simpleTouchKeyboard_36.png)
+
+I also added the script designator `Latn` for Latin script. This is
+recommended for Windows, which sometimes fails to install the desktop
+keyboard if there is not a script designator on the BCP 47 tag. We
+recommend that you always put in the script tag unless there is a
+specific reason not to.
+
+Some languages have only two letters in their BCP 47 tag. For instance,
+the Hausa language has an Ethnologue code `hau` but the BCP 47 tag is
+`ha`. Keyman Developer knows about this, and will change the tag to `ha`
+if I put in the Ethnologue tag.
+
+# Next steps
+
+The two following articles on Amharic describe a more complex
+touchscreen example where quite frequently two keys are pressed to get
+one distinct character. (Amharic is a Abugida or syllabary script, where
+one character can represent a consonant and following vowel).
+
+- [Creating a touch keyboard layout for Amharic - part
+ 1](../creating-a-touch-keyboard-layout-for-amharic)
+- [Creating a touch keyboard layout for Amharic - part
+ 2](../creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/index.md b/developer/docs/help/guides/develop/tutorial/index.md
new file mode 100644
index 00000000000..3642ddc1ed5
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/index.md
@@ -0,0 +1,56 @@
+---
+title: Keyboard Tutorial
+---
+
+[Step 1: Planning the Keyboard](step-1)
+
+[Step 2: Writing the Header](step-2)
+
+[Step 3: The Keyboard Header](step-3)
+
+[Step 4: The Keyboard Body](step-4)
+
+[Step 5: Rules with Context](step-5)
+
+[Step 6: Stores, 'any', and 'index'](step-6)
+
+[Step 7: Testing the Keyboard](step-7)
+
+[Step 8: Deadkeys](step-8)
+
+[Step 9: The Finished Keyboard](step-9)
+
+## Overview
+
+Welcome! In this tutorial, you will learn the basics of Keyman keyboards
+and create a simple French keyboard which can be used with any physical
+keyboard layout.
+
+The keyboard will use a basic English layout, and add some deadkeys to
+define vowel diacritics and a few other characters.
+
+While this tutorial is based on a physical keyboard such as used on a
+desktop or laptop computer, the principles of the keyboard language that
+you will learn are applicable to developing touch layouts as well.
+
+This keyboard will use Unicode. Unicode is a character encoding standard
+that supports most of the world's scripts, and includes support for
+user-defined scripts. Unicode is the accepted standard for text encoding
+in modern applications and operating systems.
+
+In Keyman Developer keyboard source files, Unicode characters are
+specified with `U+xxxx`, where `xxxx` is a four-digit hexadecimal
+number.
+
+At the bottom of each page in the tutorial, will be a link to both the
+previous page and the next page. You can use these links to work your
+way through the tutorial. You may also find links to reference
+information, which you can select to learn more about a particular
+aspect of creating Keyman keyboards.
+
+## Let's begin
+
+Let's get started! Move on to the next topic to begin the first step,
+planning the keyboard.
+
+- [Continue with Step 1: Planning the Keyboard](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-1.md b/developer/docs/help/guides/develop/tutorial/step-1.md
new file mode 100644
index 00000000000..642fa916bba
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-1.md
@@ -0,0 +1,71 @@
+---
+title: Step 1: Planning the Keyboard
+---
+
+## Choosing the characters
+
+First of all, we need to decide which characters we want the keyboard to
+produce. Next, we must find out the codes used to represent them, using
+a program such as Character Map, or with the Character Map in Keyman
+Developer (go to **View**, **Character Map**).
+
+French uses the same 26 letters as English, with some additions. As our
+keyboard is based on English, we only need to work with these additional
+letters. Note that for completeness, we will design our keyboard to
+produce a few other accented vowels that are not used in French. Also,
+we want our keyboard to include the angled quotes `«` and `»`.
+
+These characters, with both uppercase and lowercase forms, are listed in
+the table below along with their Unicode codes.
+
+| | | | | | | | | |
+|-----|----------|-----|----------|-----|-----|----------|-----|----------|
+| `À` | `U+00C0` | `à` | `U+00E0` | | `Á` | `U+00C1` | `á` | `U+00E1` |
+| `È` | `U+00C8` | `è` | `U+00E8` | | `É` | `U+00C9` | `é` | `U+00E9` |
+| `Ì` | `U+00CC` | `ì` | `U+00EC` | | `Í` | `U+00CD` | `í` | `U+00ED` |
+| `Ò` | `U+00D2` | `ò` | `U+00F2` | | `Ó` | `U+00D3` | `ó` | `U+00F3` |
+| `Ù` | `U+00D9` | `ù` | `U+00F9` | | `Ú` | `U+00DA` | `ú` | `U+00FA` |
+| `Â` | `U+00C2` | `â` | `U+00E2` | | `Ä` | `U+00C4` | `ä` | `U+00E4` |
+| `Ê` | `U+00CA` | `ê` | `U+00EA` | | `Ë` | `U+00CB` | `ë` | `U+00EB` |
+| `Î` | `U+00CE` | `î` | `U+00EE` | | `Ï` | `U+00CF` | `ï` | `U+00EF` |
+| `Ô` | `U+00D4` | `ô` | `U+00F4` | | `Ö` | `U+00D6` | `ö` | `U+00F6` |
+| `Û` | `U+00DB` | `û` | `U+00FB` | | `Ü` | `U+00DC` | `ü` | `U+00FC` |
+| `Ý` | `U+00DD` | `ý` | `U+00FD` | | `Ç` | `U+00C7` | `ç` | `U+00E7` |
+| `«` | `U+00AB` | `»` | `U+00BB` | | | | | |
+
+If you are not familiar with the hexadecimal (base-16) numbering system,
+don't worry: you can use the Character Map in Keyman Developer to find
+the character you want, and then drag-and-drop or copy-and-paste its
+character code into your keyboard.
+
+Note that you must be careful to use the right character: The Unicode
+standard has many characters with the same shape as another, but a
+different meaning; an example of this is the Greek capital letter Sigma
+(`U+03A3` Σ) and the mathematical summation symbol (`U+2211`, ∑). An application
+supporting Unicode would treat these two characters differently. If in
+doubt whether a character is the right one, you can look up the
+reference tables at [www.unicode.org](http://www.unicode.org/).
+
+## Designing the layout
+
+After choosing the characters we want our keyboard to use, we must
+decide how we want the user to be able to enter them. For some
+languages, you might replace each letter on the English keyboard with a
+letter from the language. In this case, however, most of the letters are
+accented vowels, we will use two keystrokes for each: one for the
+accent, and one for the vowel.
+
+| Character | Keystrokes |
+|-------------|-------------------------------|
+| `À, à, ...` | back-quote (` ), then the vowel key. |
+| `Á, á, ...` | quote (' ), then the vowel key. |
+| `Â, â, ...` | caret (^ ), then the vowel key. |
+| `Ä, ä, ...` | double-quote (" ), then the vowel key. |
+| `Ç, ç` | quote (' ), then lower- or uppercase C. |
+| `«, »` | two less-than (<< ) or greater-than (>> ) symbols. |
+
+Now that we have decided which character to use and how the user can
+enter them, we can start to write the keyboard.
+
+- [Continue with Step 2: Writing the Header](step-2)
+- [Back to the Introduction](index)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-2.md b/developer/docs/help/guides/develop/tutorial/step-2.md
new file mode 100644
index 00000000000..5867973fb39
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-2.md
@@ -0,0 +1,53 @@
+---
+title: Step 2: Writing the Header
+---
+
+## Overview of a keyboard file
+
+A keyboard file is divided into two sections: the **header** and the
+**rules** section. The header section defines the name of the keyboard,
+its bitmap, and other general settings. The rules are used to define how
+the keyboard responds to keystrokes from the user, and are divided into
+groups. We will start by writing the header.
+
+## The keyboard header
+
+The keyboard header is the first part of a keyboard; it consists of
+statements that help Keyman identify the keyboard and set default
+options for it. Each statement in the header must be on a separate line.
+While there is no technical requirement to put header statements at the
+start of a keyboard source file, keeping them there helps you identify
+them easily, and keeps them consistent with keyboard programs other
+people might write.
+
+We will begin to write the keyboard. If Keyman Developer is not already
+running, start it now. Create a new keyboard file, navigate to the
+**Layout** tab, and click on the **Code** tab at the bottom of the
+screen to switch to the Code view.
+
+Type or paste the following code into the keyboard file. This is the
+header of our keyboard file.
+
+```keyman
+c Simplified French Keyboard for Keyman 9.0
+c
+c This keyboard program uses a simplified set of keys
+c for typing French, especially for those who don't know the
+c standard French keyboard.
+c
+c NOTE: This keyboard was created from the Keyman keyboard
+c programming tutorial.
+
+store(&Version) "9.0" c This keyboard is for use with Keyman 9.0
+store(&Name) "Quick French"
+store(&Bitmap) "qfrench.ico"
+store(&MnemonicLayout) "1" c This keyboard uses a mnemonic layout.
+
+begin Unicode > use(Main)
+```
+
+An explanation of the various parts of the header follows on the next
+page.
+
+- [Continue with Step 3: The Keyboard Header](step-3)
+- [Back to Step 1: Planning the Keyboard](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-3.md b/developer/docs/help/guides/develop/tutorial/step-3.md
new file mode 100644
index 00000000000..81777aec6a0
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-3.md
@@ -0,0 +1,99 @@
+---
+title: Step 3: The Keyboard Header
+---
+
+## Comments
+
+```keyman
+c Simplified French Keyboard for Keyman 9.0
+```
+
+Most of the header in this example is made up of
+[comments](/developer/language/guide/comments). A comment is used
+to make notes about the keyboard, or to provide information on the
+workings of the keyboard. The comments are readable by anyone looking at
+the source code of the keyboard.
+
+A comment always starts with a lowercase `c`, followed by one or more
+spaces, and continues to the end of the line. Keyman Developer will
+ignore comments when compiling a keyboard.
+
+Comments can take up a whole line, or can start in the middle of the
+line. The latter is useful for making short notes about individual
+lines. As you can see we have used both kinds of comments in the header.
+
+## The `&Version` store
+
+```keyman
+store(&Version) "9.0" c This keyboard is for use with Keyman 9.0
+```
+
+The [`&Version` store](/developer/language/reference/version)
+identifies the Keyman version for which this keyboard was written; this
+keyboard is for use with Keyman 9.0. The `&Version` store is an optional
+part of the keyboard header, but if present, it should be the first
+store in the file.
+
+## The `&Name` store
+
+```keyman
+store(&Name) "Quick French"
+```
+
+The [`&Name` store](/developer/language/reference/name) specifies
+a descriptive name for the keyboard, which can be up to eighty
+characters long. The name we have given to this keyboard is
+`"Quick French"`. The `&Name` store is not required but is highly
+recommended!
+
+## The `&Bitmap` store
+
+```keyman
+store(&Bitmap) "qfrench.ico"
+```
+
+The optional [`&Bitmap` store](/developer/language/reference/bitmap)
+tells Keyman which image to use for the keyboard's icon. The picture
+should be in the standard Windows .ico format, and should contain at
+least a single 16x16 pixel image. It can also contain higher resolution
+images for high resolution "High DPI" displays. If you use a modern icon
+editor, the icon can use alpha transparency.
+For this keyboard we will be using the following bitmap:
+![](../../../images/tutorial_keyboard_qfrench.gif); it is
+found in the Keyman Developer folder, under
+`Samples\Examples\qfrench.ico` - you should copy it into the same folder
+in which you will save your keyboard.
+
+## The `&MnemonicLayout` store
+
+```keyman
+store(&MnemonicLayout) "1"
+```
+
+The [`&MnemonicLayout` store](/developer/language/reference/mnemoniclayout) tells Keyman that
+the layout is meant to conform to the user's keyboard layout; for
+example, if the user presses the quote key ' on
+their keyboard (whether they are using a US English, UK English, French,
+German, Swedish, or other keyboard) it should work in the same way. The
+opposite of this is a positional layout (which is the default if this
+store omitted), which is intended for keyboards for which there is not
+necessarily a correspondence between what is printed on the physical
+keyboard and what is output when that key is pressed.
+
+## The `begin` statement
+
+```keyman
+begin Unicode > use(Main)
+```
+
+The [`begin` statement](/developer/language/reference/begin) tells
+Keyman which group of rules to process first when it receives a
+keystroke. The use of multiple groups is an advanced feature, and
+unnecessary for this tutorial, so we will use a single group, called
+`Main`. The `begin` statement is required in every keyboard, and marks the
+start of the keyboard body. The `begin` statement also tells Keyman
+which encoding to use for the keyboard. Nearly all keyboards will use
+`Unicode`, today.
+
+- [Continue with Step 4: The Keyboard Body](step-4)
+- [Back to Step 2: Writing the Header](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-4.md b/developer/docs/help/guides/develop/tutorial/step-4.md
new file mode 100644
index 00000000000..e8a861ae1cd
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-4.md
@@ -0,0 +1,102 @@
+---
+title: Step 4: The Keyboard Body
+---
+
+The body of the keyboard is the most important part: it determines the
+behaviour of the keyboard. The body consists of groups, which in turn
+contain one or more rules which define the responses of the keyboard to
+certain keystrokes.
+
+## Groups
+
+There are two types of groups:
+* groups that process the keys pressed and the context.
+* groups that process only the context.
+
+For simple keyboards, the latter type of group will not be required. A group begins
+with a [`group` statement](/developer/language/reference/group), and
+ends either at the start of another group, or at the end of the keyboard
+file.
+
+We will only use one group in the Quick French keyboard, called `Main`.
+We mark the start of it with the `group` statement below. Add this line
+to the keyboard if it's not already there.
+
+```keyman
+group(Main) using keys
+```
+
+The `using keys` clause tells Keyman that this group will process
+keystrokes.
+
+## Basic Rules
+
+A rule tells Keyman the output to produce for a certain input. A rule
+consists of three parts: the **context**, the **key**, and the
+**output**.
+
+* The **context** specifies the conditions under which a rule will act. If
+what is shown in the document to the left of the cursor matches the
+context of a rule, the rule will be processed.
+* The **key** specifies which keystroke the rule will act upon.
+* The **output** determines the characters that are produced by a rule.
+The output replaces the matched context in the document.
+
+## Simple rules
+
+The simplest rules in Keyman consist of just a key and output, as below.
+(The examples in this section are just for illustration, and do not form
+part of the Quick French keyboard).
+
+```keyman
++ "a" > "ä"
+```
+
+In this rule, the key is a , and the output is `"ä"`. A simple rule
+begins with a plus sign, followed by the key, a greater-than symbol
+(suggesting "becomes"), and finally the output. As you might guess, this
+rule will change a lowercase a key typed by the
+user into **ä**.
+
+The key and output can be written as a character in single or double
+quotes (as above), or as its Unicode character code, or using a named
+constant. The rule above could have also been written any of the
+following ways, among others:
+
+```keyman
++ 'a' > 'ä'
+
++ U+0061 > U+00E4
+
+store(ADIERESIS) 'ä'
++ 'a' > $ADIERESIS
+```
+
+You can also write the key in one form and the context in another.
+
+## Rules with longer output
+
+The output of a rule is not limited to a single character. You could,
+for example, write a rule such as the following:
+
+```keyman
++ "f" > "ph"
+```
+
+This would change any f keys typed into **ph**.
+If the output of a rule consists of more than one character, you can
+write the characters in different ways if necessary, with a space
+separating each part. You can specify multiple characters in quotes, but
+if you use the Unicode codes to write the characters, you must separate
+each with a space:
+
+```keyman
++ "f" > U+0070 U+0068
+
++ "f" > U+0070 "h"
+```
+
+These rules are functionally identical to the one [further above](#toc-simple-rules).
+
+- [Continue with Step 5: Rules with Context](step-5)
+- [Back to Step 3: The Keyboard Header](step-3)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-5.md b/developer/docs/help/guides/develop/tutorial/step-5.md
new file mode 100644
index 00000000000..88a76bce340
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-5.md
@@ -0,0 +1,76 @@
+---
+title: Step 5: Rules with Context
+---
+
+## Rules with Context
+
+Very often we want a keyboard to produce different output based on more
+than just the last keystroke. For example, in the Quick French keyboard,
+we want the key e to produce one of è, é,
+ë, ê, or just e, depending on what was typed immediately before
+it. To do this we must make our rules look at the context.
+
+The context is the output from previous rules; that is, the characters
+that are displayed on the screen. We can make a rule work with only
+certain context by putting this before the plus sign in the rule:
+
+```keyman
+"^" + "e" > "ê"
+```
+
+With this rule, whenever an e is typed, if it
+was preceded by a caret (^), the output will be ê. It is important
+to remember that the context consists of **output
+from previous rules**, not the previous keystrokes. To emphasize
+this point, consider the four rules below:
+
+```keyman
++ "a" > "b"
++ "b" > "c"
+"b" + "c" > "d"
+"c" + "d" > "e"
+```
+
+With these rules, typing b c would produce the
+output `cc`, and not `d`, as you might initially expect. This is because
+the key b is converted by the second rule into
+the output `c`, while the third rule expects a context of b, and not
+c; we would have to type a c to get `d`.
+
+However, if a key has no matching rule, the output will be the same as
+the key: so the output `e` will be produced for either of the inputs c d (because c by itself has no rule), and b d .
+
+## Continuing the Quick French keyboard
+
+Now we know how to create context-dependent rules, we can continue
+making the Quick French keyboard. Let's start with the rules for
+acute-accented characters, using the ANSI codes from the table we
+prepared earlier:
+
+```keyman
+c lowercase characters with acute accent
+"'" + "a" > U+00E1
+"'" + "e" > U+00E9
+"'" + "i" > U+00ED
+"'" + "o" > U+00F3
+"'" + "u" > U+00FA
+"'" + "y" > U+00FD
+
+c uppercase characters with acute accent
+"'" + "A" > U+00C1
+"'" + "E" > U+00C9
+"'" + "I" > U+00CD
+"'" + "O" > U+00D3
+"'" + "U" > U+00DA
+"'" + "Y" > U+00DD
+```
+
+We can also create similar rules for the other thirty-odd accented
+characters.
+
+As you can see, even for a simple keyboard like this we quickly end up
+with a large number of rules, which makes for clumsiness. We can make
+things simpler using stores, and the `any()` and `index()` statements.
+
+- [Continue with Step 6: Stores, `any()`, and `index()`](step-6)
+- [Back to Step 4: The Keyboard Body](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-6.md b/developer/docs/help/guides/develop/tutorial/step-6.md
new file mode 100644
index 00000000000..95ade8767c6
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-6.md
@@ -0,0 +1,116 @@
+---
+title: Step 6: Stores, any(), and index()
+---
+
+When we have many similar rules, as in the last example on the previous
+page, we can group them together into one rule by using stores. A store
+is a set of characters that are grouped under a single name. Stores are
+used in rules with the [`any()`](/developer/language/reference/any) and
+[`index()`](/developer/language/reference/index) statements. We create a
+store with a [`store()` statement](/developer/language/reference/store):
+
+```keyman
+store(vowels) "aeiou"
+```
+
+This creates a store called `vowels`, which contains the five lowercase
+vowels.
+> #### Note:
+We could also have written the content of the store
+using ANSI or Unicode character codes, in the same way as the output.
+
+The `any()` statement is used to match a character from a specific
+store. For example, the following rule will replace any vowel with a
+period, when used with the store above:
+
+```keyman
++ any(vowels) > "."
+```
+
+The `any()` statement can be used in the context or in the key part of a
+rule. It cannot be used in the output.
+
+The second statement that is used with stores is the `index()`
+statement. It is usually used in the output of a rule, and will output
+the character from a particular store at the same position as the
+character matched by a specified `any()` statement. This is best shown
+with an example; this rule will convert all input to uppercase:
+
+```keyman
+store(lowercase) "abcdefghijklmnopqrstuvwxyz"
+store(uppercase) "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
++ any(lowercase) > index(uppercase,1)
+```
+
+When a letter, such as j is typed, the `any()`
+statement finds its position in the `lowercase` store; the `index()`
+statement then gets this index from the `any()` statement, and outputs
+the character at the same position in the `uppercase` store, in this
+case `J`.
+
+The `index()` statement has two parts: the store from which it takes the
+output character, and the offset of the `any()` statement that it gets
+the character position from. This offset is found by counting the
+characters and statements in the context and key parts of the rule up to
+the `any()` statement. Again, a few examples may help to illustrate
+this:
+
+```keyman
+"a" + any(somestore) > index(otherstore,2) c The 'any' statement
+ c is character #2
+"ab" any(somestore) + "c" > index(otherstore,3) c The 'any' statement
+ c is character #3
+
+c Here the 'index' statement references the second 'any' statement used,
+c which is character #4
+U+0041 any(somestore) "B" + any(otherstore) > index(thirdstore,4)
+
+c You can have multiple 'index' statements in the output, which can
+c reference the same or different 'any's
+any(store1) + any(store2) > index(store1,2) index(store2,1) index(store3,2)
+```
+
+## Using stores in the Quick French keyboard
+
+We can now reduce the number of rules needed for the Quick French
+keyboard by using stores. We will make five stores: one for the
+unaccented vowels, and one each for vowels with acute accents, grave
+accents, circumflexes, and diereses. For clarity, the `group()`
+statement is repeated below:
+
+```keyman
+group(Main) using keys
+
+store( plainvowels ) 'a' 'e' 'i' 'o' 'u' 'A' 'E' 'I' 'O' 'U'
+store( acutevowels ) U+00E1 U+00E9 U+00ED U+00F3 U+00FA U+00C1 U+00C9 U+00CD U+00D3 U+00DA
+store( gravevowels ) U+00E0 U+00E8 U+00EC U+00F2 U+00F9 U+00C0 U+00C8 U+00CC U+00D2 U+00D9
+store( circumvowels ) U+00E2 U+00EA U+00EE U+00F4 U+00FB U+00C2 U+00CA U+00CE U+00D4 U+00DB
+store( dresisvowels ) U+00E4 U+00EB U+00EF U+00F6 U+00FC U+00C4 U+00CB U+00CF U+00D6 U+00DC
+
+"'" + any( plainvowels ) > index( acutevowels, 2 )
+"`" + any( plainvowels ) > index( gravevowels, 2 )
+"^" + any( plainvowels ) > index( circumvowels, 2 )
+'"' + any( plainvowels ) > index( dresisvowels, 2 )
+```
+
+This is far clearer than the long list of rules that we used earlier.
+Obviously we should add one or two more ordinary rules to produce uppercase
+and lower-case ç, ý, and also the angled quotes « and ». Then we
+will have almost finished the keyboard:
+
+```keyman
+"'" + "y" > U+00FD c Acute-accented Y
+"'" + "Y" > U+00DD
+
+"'" + "c" > U+00E7 c C-cedilla
+"'" + "C" > U+00C7
+
+"<" + "<" > U+00AB c Angled quotes
+">" + ">" > U+00BB
+```
+
+All we need to do now is to test the keyboard.
+
+- [Continue with Step 7: Testing the Keyboard](step-7)
+- [Back to Step 5: Rules with Context](step-5)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-7.md b/developer/docs/help/guides/develop/tutorial/step-7.md
new file mode 100644
index 00000000000..2432bb76f4a
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-7.md
@@ -0,0 +1,52 @@
+---
+title: Step 7: Testing the Keyboard
+---
+
+# Compiling the Keyboard
+
+Before we can test the keyboard, we must compile it. Choose
+Keyboard, Compile
+Keyboard or press F7 to compile the
+keyboard. The Message window will display the results of the
+compilation; if you have no typing errors, the keyboard should compile
+successfully.
+
+If there are any mistakes, an error message will be displayed in the
+Message window, which will tell you the line on which the error
+occurred.
+
+## Compiling the Keyboard
+
+After compiling the keyboard successfully, we can start testing it.
+Choose Debug,
+Start Debugging or press
+F5 to begin testing. The Keyboard debug window
+will appear.
+
+Now we test that all the rules operate as expected. To test the rules,
+we type the keystrokes that will give us the output; for example, we can
+type a quote ' followed by one of
+A , E ,
+I , O ,
+U , or Y to test the
+uppercase acute-accented vowels. Similarly we can test the other
+accents, and C-cedilla (`Ç`) and the angled quotes. If the rules are
+correct, all this should work as we wanted.
+
+Testing the rules in isolation like this will show if the rules are
+correct or not, but won't show other possible errors that might occur in
+everyday usage of the keyboard. For example, look at what happens if you
+type the following quote:
+
+> `'Alors Alice demande, "Où est mon chat ?"'`
+
+As you can see, it comes out incorrectly as:
+
+> `Álors Alice demande, Öù est mon chat ?"'`
+
+The problem occurs when we have a word in quotes that begins with a
+vowel: the keyboard will convert it to an accented vowel. We will need
+to come up with a solution to this problem.
+
+- [Continue with Step 8: Deadkeys](step-8)
+- [Back to Step 6: Stores, `any()`, and `index()`](step-6)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-8.md b/developer/docs/help/guides/develop/tutorial/step-8.md
new file mode 100644
index 00000000000..52f532ce76d
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-8.md
@@ -0,0 +1,78 @@
+---
+title: Step 8: Deadkeys
+---
+
+## Solving the Problem
+
+Probably the easiest solution to the problem which we encountered on the
+last page is to design the keyboard so that the user types a quote key
+twice when they want to produce a quote, but once when they want to use
+it as an accent. However, we cannot simply use the rule:
+
+```keyman
+"'" + "'" > "'"
+```
+
+This will certainly produce a single quote after it's typed twice;
+however it will still produce the same error, because the following
+keystroke will continue to swallow the quote character from the context.
+We need to distinguish between the output of this rule, when the user
+wants a quote, and that of a single quote press, when the user wants to
+place an accent on a vowel. To implement this behaviour, we use
+[deadkeys](/developer/language/reference/deadkey).
+
+## Deadkeys
+
+A deadkey is like a character that is used in the context or output but
+never appears on the screen. We use deadkeys like this:
+
+```keyman
++ "'" > deadkey(quote)
+
+c Handle acute accents
+deadkey(quote) + any( plainvowels ) > index( acutevowels, 2 )
+
+c Handle a single quote
+deadkey(quote) + "'" > "'"
+```
+
+Note that for the sake of convenience, a deadkey can also be written in
+a short form:
+
+```keyman
+dk(quote) c This is identical to deadkey(quote)
+```
+
+Type the three rules above in place of the existing rule for acute
+accents in the keyboard so far. If you test the keyboard now, you will
+find that the error no longer occurs. This is because accented vowels
+are only produced after the deadkey, and no deadkey is output if the
+user types a quote key twice.
+
+But we've introduced another difference to the keyboard now: the quote
+is no longer displayed before you type the vowel. This is because we are
+converting the quote to a deadkey. If we prefer, we can still
+distinguish between rules in the above manner and display the quote, if
+we just add a quote before the deadkey, like this:
+
+```keyman
++ "'" > "'" dk(quote)
+
+c Handle acute accents
+"'" dk(quote) + any( plainvowels ) > index( acutevowels, 3 )
+
+c Handle a single quote
+"'" dk(quote) + "'" > "'"
+```
+
+However, we will not use this technique for the Quick French keyboard.
+
+Now we can make changes so that all the other accents use deadkeys as
+well, for consistency, and add rules to output the accent character by
+typing the key twice.
+
+The Quick French keyboard is now complete. The full source is on the
+next page.
+
+- [Continue with Step 9: The Finished Keyboard](step-9)
+- [Back to Step 7: Testing the Keyboard](step-7)
\ No newline at end of file
diff --git a/developer/docs/help/guides/develop/tutorial/step-9.md b/developer/docs/help/guides/develop/tutorial/step-9.md
new file mode 100644
index 00000000000..03e00a27870
--- /dev/null
+++ b/developer/docs/help/guides/develop/tutorial/step-9.md
@@ -0,0 +1,66 @@
+---
+title: Step 9: The Finished Keyboard
+---
+
+## The Quick French Keyboard
+
+Here is the completed keyboard:
+
+```keyman
+c Simplified French Keyboard for Keyman 9.0
+c
+c This keyboard program uses a simplified set of keys
+c for typing French, especially for those who don't know the
+c standard French keyboard.
+c
+c NOTE: This keyboard was created from the Keyman keyboard
+c programming tutorial.
+
+store(&Version) "9.0" c This keyboard is for use with Keyman 9.0
+store(&Name) "Quick French"
+store(&Bitmap) "qfrench.ico"
+store(&MnemonicLayout) "1" c This keyboard uses a mnemonic layout.
+
+begin Unicode > use(Main)
+
+group( Main ) using keys
+
+c Store the upper and lowercase vowels with different accents
+store( plainvowels ) 'a' 'e' 'i' 'o' 'u' 'A' 'E' 'I' 'O' 'U'
+store( acutevowels ) U+00E1 U+00E9 U+00ED U+00F3 U+00FA U+00C1 U+00C9 U+00CD U+00D3 U+00DA
+store( gravevowels ) U+00E0 U+00E8 U+00EC U+00F2 U+00F9 U+00C0 U+00C8 U+00CC U+00D2 U+00D9
+store( circumvowels ) U+00E2 U+00EA U+00EE U+00F4 U+00FB U+00C2 U+00CA U+00CE U+00D4 U+00DB
+store( dresisvowels ) U+00E4 U+00EB U+00EF U+00F6 U+00FC U+00C4 U+00CB U+00CF U+00D6 U+00DC
+
+c Output deadkeys only for the accent keys pressed
++ "'" > dk(quote) c Quote for acute accent
++ "`" > dk(bkquote) c Backquote for grave accent
++ "^" > dk(caret) c Caret for circumflex
++ '"' > dk(dbquote) c Double-quote for dieresis
+
+c Rules for accented vowels
+dk(quote) + any( plainvowels ) > index( acutevowels, 2 )
+dk(bkquote) + any( plainvowels ) > index( gravevowels, 2 )
+dk(caret) + any( plainvowels ) > index( circumvowels, 2 )
+dk(dbquote) + any( plainvowels ) > index( dresisvowels, 2 )
+
+c Rules for other characters
+dk(quote) + "y" > U+00FD c Acute-accented Y
+dk(quote) + "Y" > U+00DD
+
+dk(quote) + "c" > U+00E7 c C-cedilla
+dk(quote) + "C" > U+00C7
+
+"<" + "<" > U+00AB c Angled quotes
+">" + ">" > U+00BB
+
+c Rules for the accent character itself (type it twice)
+dk(quote) + "'" > "'" c Quote
+dk(bkquote) + "`" > "`" c Backquote
+dk(caret) + "^" > "^" c Caret
+dk(dbquote) + '"' > '"' c Double-quote
+
+c End of keyboard
+```
+
+- [Back to Step 8: Deadkeys](step-8)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/index.md b/developer/docs/help/guides/distribute/index.md
new file mode 100644
index 00000000000..7b84bf472fd
--- /dev/null
+++ b/developer/docs/help/guides/distribute/index.md
@@ -0,0 +1,10 @@
+---
+title: Distributing keyboards
+---
+
+- [Package development tutorial](tutorial) (Keyman Desktop, Keyman for macOS, Keyman for mobile apps)
+- [Distribute keyboards to Keyman applications](packages)
+
+## See also
+
+- [Keyman Engine](/developer/engine/)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/install-kmp-android.md b/developer/docs/help/guides/distribute/install-kmp-android.md
new file mode 100644
index 00000000000..bdba196c908
--- /dev/null
+++ b/developer/docs/help/guides/distribute/install-kmp-android.md
@@ -0,0 +1,78 @@
+---
+title: Installing Custom Keyboards to your Android Device
+---
+
+## Overview
+
+In [Keyman for Android 10.0](/products/android/10.0/), we added the ability to
+easily download/share custom keyboards to your Android device.
+
+## Installing a custom keyboard
+
+To install a custom keyboard, you will need a link to download the
+keyboard package. If you have already copied the kmp file onto the
+Android device, skip to Step 2.
+
+If you want to build and dev your own keyboard, you will need some
+technical skills for this, and can read our full step-by-step
+instructional on how to do this [here](packages).
+
+Once you have a website or email with a link to the keyboard package,
+follow these steps on your Android device to download and install the
+keyboard package into the Keyman for Android application:
+
+## Step 1) Click the link to save your custom keyboard package file
+
+The link in this example is for Khmer Angkor keyboard.
+
+![](../../images/dist-url-screen-ap.png)
+
+## Step 2) Add Keyboard from Device
+
+Once the KMP file is on your device, you will need to browse to the KMP
+file and select it. From the Keyman menu, select "Settings". From the
+Keyman Settings menu, select "Add Keyboard from Device".
+
+![](../../images/settings-language-ap.png)
+
+The device will launch a file browser where you'll browse to the
+directory of your KMP file. A common places to look is the "Downloads"
+folder.
+
+![](../../images/dist-file-browser-ap.png)
+
+Selecting the KMP file should bring you to Step 3)
+
+## Step 3) Grant Keyman for Android access to storage (Android 6.0+ only)
+
+On Android 6.0 (Marshmallow) and higher, mobile apps need to request
+permission to access storage. Keyman for Android needs access to read
+storage for installing the KMP file. At the dialog, select "ALLOW". Once
+authorized, Keyman for Android won't need to ask for storage permission
+again, unless the user revokes or uninstalls the app.
+
+![](../../images/dist-storage-permission-ap.png)
+
+Older versions of Android grant Storage permissions at app installation
+time, so those users can skip this step.
+
+## Step 4) Keyboard Package welcome screen
+
+Keyman for Android will parse the metadata in the package. If the
+keyboard package includes a "welcome.htm" file, this will be displayed
+at the confirmation to install the keyboard package
+
+![](../../images/dist-welcome-ap.png)
+
+If "welcome.htm" is not included, a generic page with the package ID and
+package version will be shown.
+
+Click the left "Install" button to install the entire keyboard package
+
+## Step 5) The keyboard is successfully installed!
+
+All the keyboards in the package are installed as a group. In this
+example, the package only has the "Khmer Angkor" keyboard, so it becomes
+the active keyboard
+
+![](../../images/dist-install1-ap.png)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/install-kmp-ios.md b/developer/docs/help/guides/distribute/install-kmp-ios.md
new file mode 100644
index 00000000000..de4d3a64cb5
--- /dev/null
+++ b/developer/docs/help/guides/distribute/install-kmp-ios.md
@@ -0,0 +1,53 @@
+---
+title: Installing Custom Keyboards to your iPhone or iPad
+---
+
+## Overview
+
+In version 10 of Keyman for iPhone and iPad, we added the ability to easily download custom keyboards to your iOS device.
+
+## Installing a custom keyboard
+
+To install a custom keyboard, you will first need a link to the keyboard
+package on a website or in your email.
+
+If you want to build and dev your own keyboard, you will need some
+technical skills for this, and can read our full step-by-step
+instructional on how to do this [here](packages).
+
+Once you have a website or email with a link to the keyboard package,
+follow these steps on your iPhone or iPad to download and install the
+keyboard package into the Keyman for iPhone and iPad application:
+
+## Step 1) Click the link to your custom keyboard package file
+
+The link in this example is for GFF Amharic 7 keyboard.
+
+![](../../images/dist-url-screen-i.png)
+
+Safari will display an option to open the KMP file with Keyman
+
+![](../../images/dist-kmp-open-i.png)
+
+## Step 2) Click Open in "Keyman"
+
+Keyman for iPhone and iPad will parse the metadata in the package. If
+the keyboard package includes a "welcome.htm" file, this will be
+displayed at the confirmation to install the keyboard package
+
+![](../../images/dist-welcome-i.png)
+
+If "welcome.htm" is not included, a generic page with the package ID and
+package version will be shown.
+
+## Step 3) Click the top right "Install" button
+
+The keyboard from the keyboard package is successfully installed!
+
+![](../../images/dist-kmp-success-i.png)
+
+All the keyboards in the package are installed as a group. In this
+example, the package only has the "GFF Amharic 7" keyboard, so it
+becomes the active keyboard
+
+![](../../images/dist-install1-i.png)
diff --git a/developer/docs/help/guides/distribute/packages.md b/developer/docs/help/guides/distribute/packages.md
new file mode 100644
index 00000000000..e3382220a30
--- /dev/null
+++ b/developer/docs/help/guides/distribute/packages.md
@@ -0,0 +1,131 @@
+---
+title: Distribute keyboards to Keyman Applications
+---
+
+## Overview
+
+Keyboard package files contain one or more keyboards, along with readme
+files, fonts (if your keyboard requires a custom font), and any other
+files you wish to include. You should create a package file to bundle
+your keyboard with fonts and help into a simple, single file that is
+easy for an end-user to install.
+
+Internally, as well as your own files, the package file will contain
+metadata files "kmp.inf" and "kmp.json", which list the details Keyman
+needs to install the package. The package file is a ZIP compatible
+archive.
+
+> ### Tip
+You can distribute keyboards and lexical models in package files, but
+you can't include both in the same package file.
+
+#### Keyman Desktop
+
+Keyman Desktop can install package files, including installing fonts,
+creating Start Menu shortcuts, and adding appropriate registry entries
+for uninstallation.
+
+On Windows, the context menu for a package file has one additional
+entry: "Install".
+
+#### Keyman for macOS
+
+Keyman for macOS can install package files with fonts and keyboards, and
+shortcuts in the package will be available through the keyboard's entry
+in Keyman Configuration.
+
+#### Keyman for Android and Keyman for iPhone and iPad
+
+Keyman mobile applications can install the same package files as Keyman
+Desktop, as long as the package includes keyboards for touch layouts.
+
+## Package file contents
+
+A package can have a variety of different files contained within. The
+following files and file types are recognized by the package installer:
+
+\*.kmx (Desktop and macOS only)
+: Keyboard files. Each of these will be installed. Keyman
+ Configuration will not allow installation or uninstallation of a
+ single keyboard from a package. They will always be treated as a
+ group for installation and uninstallation.
+
+\*.kvk (Desktop and macOS only)
+: On Screen Keyboard files, associated with each keyboard file.
+
+\*.js (mobile only)
+: Touch layout Keyboard files. When Keyman mobile applications install
+ a keyboard package, all included keyboards will be installed as a
+ group. With Keyman Developer 10, the keyboard version information is
+ in kmp.json, and no longer within the JS file names.
+
+welcome.htm
+: Introductory help for the keyboard, HTML format. This will normally
+ be displayed when the package is installed by the user, and is also
+ the entry point for help when accessed via Keyman's help system or
+ Keyman Configuration.
+
+readme.htm
+: Displayed before a package is installed, together with brief
+ metadata about the package, to allow the user to determine if they
+ wish to continue installation of the package.
+
+kmp.inf (Legacy versions of Keyman)
+: A Windows .ini format file that lists each of the files in the
+ package, together with metadata.
+
+kmp.json
+: A JSON format file contains metadata for the keyboard package such
+ as package version, keyboard versions, and lists each of the files
+ in the package. For more explanation of the structure of the JSON
+ file, please read the
+ [metadata](../../reference/file-types/metadata) documentation.
+
+\*.ttf, \*.otf, \*.ttc
+: Truetype font files that will be installed with the package, and
+ uninstalled when the package is removed. On mobile, these fonts will
+ be available only within the Keyman app and the on-screen-keyboard,
+ not in other apps.
+
+## Share the keyboard package file
+
+> ### Tip
+Read [Step 2: Organizing the Keyboard Files](../../../keyboards/github/step-2)
+to know which files to include or exclude in the keybord folder.
+
+Once the keyboard package .kmp file is created, you can share them via
+external storage devices (USB drive, SD card, etc). You can also share
+the package file via a cloud storage system (Google Drive, Dropbox or
+similar service), then share the link to your device via text, note or
+email. Alternatively, you can upload the .kmp file to a public facing
+website. For this example, the keyboard package for Khmer Angkor is
+being uploaded:
+
+1. khmer_angkor.kmp (the keyboard package .kmp file)
+
+## Putting the keyboard package on a website
+
+Once all the files have been uploaded, you will need to provide a link
+to the keyboard package .kmp file for your device to download and
+install. This can either be a link on a web page, or a link in an email.
+In this tutorial, a very simple .html web page with a link to the
+khmer_angkor.kmp file is created:
+
+``` markup
+
+
+
+
+ Custom Keyboard URL
+
+
+```
+
+The link must be in the format `http://` or `https://`
+
+Upload the web page to your public facing website. Once done, you can
+install the keyboard package onto your mobile devices by following these
+steps:
+
+- [Installing custom keyboards to your iPhone or iPad](install-kmp-ios)
+- [Installing custom keyboards to your Android device](install-kmp-android)
diff --git a/developer/docs/help/guides/distribute/tutorial/index.md b/developer/docs/help/guides/distribute/tutorial/index.md
new file mode 100644
index 00000000000..ec57e57e100
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/index.md
@@ -0,0 +1,36 @@
+---
+title: Package Tutorial
+---
+
+[Step 1: What do we include?](step-1)
+
+[Step 2: Creating Additional Files for the Package](step-2)
+
+[Step 3: Creating a package and adding files](step-3)
+
+[Step 4: Filling in package details](step-4)
+
+[Step 5: Shortcuts](step-5)
+
+[Step 6: Compiling, testing and distributing a Package](step-6)
+
+## Overview
+
+Welcome! In this tutorial, you will learn how to create a
+[package](../../../reference/file-types/kmp). A package is a collection
+of files, compressed into a single file, just like a ZIP file. It is
+designed to make installation of keyboards, fonts and related
+documentation straightforward for the end user. When done, the package
+can be installed into Keyman Desktop through a simple dialog. Keyman for
+mobile apps can also download and install keyboards through the keyboard
+package.
+
+We will be creating a Khmer Angkor package. You can make your own
+package based on the Quick French keyboard from the [keyboard tutorial](../../develop/tutorial).
+
+### Let's begin
+
+Let's get started! Move on to the next topic to begin the first step,
+choosing what to include in the package.
+
+[Step 1: What do we include?](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-1.md b/developer/docs/help/guides/distribute/tutorial/step-1.md
new file mode 100644
index 00000000000..a18fce49ac9
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-1.md
@@ -0,0 +1,35 @@
+---
+title: Step 1: What do we include?
+---
+
+What is the purpose of a package? To make installation of a keyboard,
+fonts, documentation, and on screen keyboard as straightforward as
+possible for the end user.
+
+We need to keep this goal in mind as we work on all the aspects of a
+package.
+
+A good package will:
+
+1. Collect the keyboard and font files together for simple installation
+2. Include Start Menu items for documentation
+
+A great package will also:
+
+1. Include a 'readme' visible before install.
+2. Include a 'welcome.htm' file which is displayed after install.
+3. Include an On Screen Keyboard if relevant.
+4. Include documentation on use of the keyboard.
+
+Packages uploaded to the Tavultesoft website will get basic icon ratings
+reflecting their ease of install/use:
+
+- ![](../../../images/tutorial_package_includesfonts.gif) includes fonts
+- ![](../../../images/tutorial_package_includesdocs.gif) includes documentation
+- ![](../../../images/tutorial_package_includesosk.gif) includes on screen keyboards
+- ![](../../../images/tutorial_package_includeswelcome.gif) includes welcome.htm
+
+In general, the more icons that a package earns, the easier that end
+users will find it to start using.
+
+[Step 2: Creating Additional Files for the Package](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-2.md b/developer/docs/help/guides/distribute/tutorial/step-2.md
new file mode 100644
index 00000000000..17dc613ba8d
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-2.md
@@ -0,0 +1,127 @@
+---
+title: Step 2: Creating Additional Files for the Package
+---
+
+You should create or select some additional files for a package. Below
+are a list of the typical files that you would create for or include in
+a package.
+
+readme.htm
+
+: A short description of the package, its use restrictions, and what
+ it includes. Try to keep the readme under 10 lines long. The readme
+ is displayed in the package install dialog and should be an html
+ file for optimal formatting.
+
+ > ### Tip
+ As the package has not been installed at this stage, and you are using fonts that may not yet be available on the target device, you should use CSS @font-face declarations to ensure that the text is readable on all devices.
+
+ Create the HTML file in any HTML editor.
+
+ > ### Tip
+ If using Microsoft Word, choose HTML (clean) when saving to create a smaller file.
+
+ Note, if your HTML editor puts images into a subfolder, you will need to edit the HTML source so that all files are in the same folder -- the package builder will not maintain subfolders. You can easily edit the HTML source in Keyman Developer.
+
+ Also, if your welcome or readme files use embedded external images, stylesheets, javascript or other files, you will need to add these files to your package as well.
+
+welcome.htm
+
+: When including an introductory help file in your package, you must
+ name the file welcome.htm. This file will be detected during install
+ and displayed (in a window roughly two thirds of the user's screen
+ width) after the package install completes successfully. Make sure
+ that you design your HTML file so that it can be resized to fit the
+ user's screen - avoid extra wide tables or wide fixed width
+ elements.
+
+ At this stage of the installation, fonts in the package have been installed, so you can include text that uses those fonts.
+
+ Welcome.htm will also be accessible after installation from Keyman Configuration under the Package Options menu, and from the Keyboard Help item and On Screen Keyboard toolbar button.
+
+ The intention of welcome.htm is to provide instructions on getting started with your keyboards.
+
+ > ### Tip
+ Useful information to include in welcome.htm is:
+ - key sequences - especially for characters that may not be immediately obvious.
+ - names of fonts in the package.
+ - names of keyboards in the package.
+ - links to additional help on your website or more extensive documentation files in the package.
+ - a link to an official distribution site for your package (even
+ if it is the Keyman website) - so that users know where to find
+ the latest version of your package.
+
+ You should avoid including instructions for the use of Keyman
+ itself - although a basic "click the Keyman icon and choose Quick
+ French" would be helpful.
+
+ > ### Tip
+ If you want links to your website to open in the user's preferred
+ browser, preface the href link with `link:`, e.g.
+ `website `
+ The `link:` sceheme will open the referred file in the default
+ application - that is, a web browser for URLs and links, Notepad for
+ .txt files, Adobe Reader for PDFs. You can use `link:` to open any
+ of the files in the package, e.g. `link:docs.pdf` will open the file
+ docs.pdf in Adobe Reader or the default PDF reader on the system.
+
+ To save you the effort of writing a welcome and readme file for the
+ Quick French example, we have placed some in the Samples/QFrench
+ folder.
+
+ If you create documents in other formats, for example PDF or
+ printable documentation, you should link to that in the welcome.htm.
+
+ > ### Tip
+ You can include multiple "welcome.htm" files for different languages
+ by appending a hyphen and the BCP 47 language code to the filename,
+ for example welcome-fr.htm for French.
+
+Fonts
+
+: A font is the single most important item to include with a keyboard
+ – if the characters of the language you are supporting are not in
+ fonts included with target operating systems. Installing fonts is
+ tedious, so make sure that your users don't have to locate and
+ install fonts themselves!
+
+ .TTF, .OTF, and .TTC fonts will be installed by the package
+ installer, and uninstalled when the package is uninstalled. A list
+ of the fonts installed is displayed in the Install Package dialog.
+
+Keyboards
+
+: Add the .kmx compiled Keyman keyboards to the package. You can add
+ multiple keyboards to the package, but be judicious.
+
+ > ### Tip
+ Don't forget to add the On Screen keyboard .KVK file associated with
+ your keyboard to the package. KVK files are not compiled into the
+ .KMX file -- although the .ICO/.BMP is.
+
+ Add the .js compiled Keyman touch layout keyboards to the package.
+
+Documentation
+
+: The two preferred documentation formats are HTML and PDF. You should
+ avoid .DOC, .RTF, and other formats -- .DOC files in particular are
+ not recommended due to the possibility of macro viruses.
+
+ Remember that HTML files can be displayed on any computer without
+ additional software. PDF files require Adobe Reader or a compatible
+ PDF viewer application. You may choose to include both and HTML
+ documentation - PDF documents often print better than HTML
+ documents, but HTML documentation is more accessible and translates
+ better to on-screen or web use.
+
+ > ### Tip
+ If you use HTML, don't forget to also add all the included files
+ such as images and stylesheets!
+
+Splash Image
+: The splash image is a 140x250 pixel image that is displayed when the
+ package is installed, at the left of the Package Install dialog.
+ Including a splash image makes your package look more professional
+ and polished, so a splash image is recommended!
+
+[Step 3: Creating a package and adding files](step-3)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-3.md b/developer/docs/help/guides/distribute/tutorial/step-3.md
new file mode 100644
index 00000000000..657f2cd02b5
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-3.md
@@ -0,0 +1,31 @@
+---
+title: Step 3: Creating a package and adding files
+---
+
+We are finally ready to open up the Package Editor and create the new
+package.
+
+- In the Project Window in Keyman Developer, click the **Packages** tab, and click **New Package...**. Enter the path and filename of the package you are creating.
+
+> ### Tip
+This tutorial makes a package named `khmer_angkor.kps`, but you could call yours `qfrench.kps`.
+
+A package source file will have the extension .kps, and will be compiled in a file with extension .kmp.
+
+After you click OK, the Package Editor will open with the Files tab visible.
+
+![](../../../images/tutorial_distribute_keyboard_3_files.png)
+
+- In the Files tab, click **Add...** to add
+ all the files we discussed in the previous step to the package. You
+ can add multiple files at once, and from multiple folders. When the
+ package is compiled, all the files will be placed in the same folder
+ within the package.
+- If your package includes .js touch layout keyboards, you can
+ associate fonts and languages in the Keyboards tab. A valid [BCP 47 language tag](../../../reference/bcp-47) must be set or the keyboard will not install on your mobile device.
+
+![](../../../images/tutorial_distribute_keyboard_3.png)
+
+You could stop here. This would be a completely valid package, but it would not be as good as it could be. So let's continue on to the next step, and fill in some descriptions of the package.
+
+[Step 4: Filling in package details](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-4.md b/developer/docs/help/guides/distribute/tutorial/step-4.md
new file mode 100644
index 00000000000..67c55fe66b8
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-4.md
@@ -0,0 +1,63 @@
+---
+title: Step 4: Filling in package details
+---
+
+Click on the Details tab in the Package Editor. You should fill in as
+many details as you can on this page.
+
+![](../../../images/tutorial_distribute_keyboard_details.png)
+
+Package Name
+
+: The Package Name will be displayed in the package install dialog,
+ and within Keyman Desktop Configuration, wherever the package is
+ referred to.
+
+Readme File
+
+: Select the readme file from the list of files you added in the
+ previous step. Remember that it should be a HTML file for optimum
+ clarity.
+
+Version
+
+: A version number for the package is important - it helps the your
+ users know that they are using the most recent update of your
+ package. The version format you should use is `1.0`. When making a
+ major change to your package or keyboards in your package, increment
+ the first part and set the second part to, e.g. `2.0`; when making a
+ bug fix or a minor update, increment the second part, e.g. `1.1`.
+
+Version numbers should be in the form `major.minor[.subversion]`.
+ Subversion is optional but is helpful for small bug fix releases.
+ Each of the sections of the version should be an integer. Keyman
+ Desktop does integer comparisons on the version numbers, so, for
+ example, version `2.01` is regarded as older than version `2.2`.
+ Alphabetic or date formats should be avoided as the installer for
+ the keyboard cannot determine which version is older reliably.
+
+Copyright
+
+: Enter copyright details for your package and keyboards. Keep this
+ reasonably short or it won't be clear for end users.
+
+Author
+
+: Enter your name or the name of your company.
+
+Email
+
+: Enter a contact email address where package users can contact you.
+ If you don't want to be contacted via email, leave this field empty
+
+Website
+
+: Enter the name of the website where you will have information about
+ this keyboard. If you want to host it on keyman.com, you could enter
+ `http://www.keyman.com/`
+
+Image file
+: Select the splash image file that you created in Step 2 from the
+ list of files in your package.
+
+[Step 5: Shortcuts](step-5)
diff --git a/developer/docs/help/guides/distribute/tutorial/step-5.md b/developer/docs/help/guides/distribute/tutorial/step-5.md
new file mode 100644
index 00000000000..eb24d70f133
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-5.md
@@ -0,0 +1,49 @@
+---
+title: Step 5: Shortcuts
+---
+
+Click on the Shortcuts tab in the Package Editor.
+
+![](../../../images/tutorial_distribute_keyboard_shortcuts.png)
+
+While it may be a good idea to include Start Menu shortcuts for your
+package, on recent versions of Windows these are not very visible to end
+users. This means that you should ensure that all instructions are
+available through the **welcome.htm** file as well. The Start Menu
+shortcuts were designed originally for Windows-based keyboard packages
+and work slightly differently in macOS.
+
+You should consider adding the following items to the Start Menu:
+
+- Documentation shortcuts
+- Welcome.htm shortcut
+
+> ### Note
+While Keyman Developer 8.0 and earlier versions of the documentation
+advised including an uninstall shortcut to the package, this is no
+longer recommended. Users should uninstall your package from Keyman
+Configuration. Adding an uninstall shortcut to the package adds
+confusion to the Start Menu search on Windows, and is not compatible
+with macOS keyboards.
+
+Click **New** to add a new shortcut to the
+package. Enter a description for the shortcut, and select the program or
+file to start from the Program list. Four predefined program entries
+(Start Product), (Product Configuration), (Product Help) and (About
+Product) will be translated into the appropriate shortcuts to start
+Keyman Desktop tasks as described.
+
+> ### Note
+In Keyman 6 and earlier, the predefined targets $KEYMAN\keyman.exe and
+$KEYMAN\kmshell.exe were available. These are translated to (Start
+Product) and (Product Configuration), respectively, in Keyman Desktop
+7.0 and later versions.
+
+
+
+> ### Tip
+Packages are not listed in the Control Panel Add/Remove Programs applet
+with Keyman Desktop 7.0 and later versions. Packages can be uninstalled
+through the Start Menu shortcut or from Keyman Desktop Configuration.
+
+[Step 6: Compiling, testing and distributing a Package](step-6)
\ No newline at end of file
diff --git a/developer/docs/help/guides/distribute/tutorial/step-6.md b/developer/docs/help/guides/distribute/tutorial/step-6.md
new file mode 100644
index 00000000000..374584c3e1c
--- /dev/null
+++ b/developer/docs/help/guides/distribute/tutorial/step-6.md
@@ -0,0 +1,68 @@
+---
+title: Step 6: Compiling, testing and distributing a Package
+---
+
+In the Package Editor, click on the **Compile** tab.
+
+![](../../../images/tutorial_distribute_keyboard_compile.png)
+
+Click **Compile Package** to compile the
+package into a .kmp file. Compiling takes all the files listed for the
+package, compresses them (using a .ZIP-compatible format) and adds the
+package information, all into a single file. If any files are not
+available, an error will be listed in the Messages window.
+
+After compiling, you can test the package installation in Keyman
+Desktop, by clicking **Install Package**. You
+should test that all the keyboards and fonts install successfully, that
+the Readme and Welcome files are displayed during the install, and that
+the documentation is accessible to the end user. Make sure that the On
+Screen Keyboard installs with your keyboards, and that the shortcuts are
+correctly listed and working in the Start Menu.
+
+> ### Advice
+The distribution model for Keyman keyboards is changing. We now
+recommend distributing source for keyboards through the [Keyman Cloud Keyboard Repository](/developer/keyboards/). The instructions here
+remain, but the binary distribution model is deprecated.
+
+## Distributing a package on the Keyman Cloud Keyboard Repository
+
+Once you have tested the package to your satisfaction, it is time to
+distribute it. We recommend uploading your keyboard package to the
+[Keyman Cloud Keyboard Repository](/developer/keyboards/)
+
+## Distributing a package on your own website
+
+If you distribute a package on your own site, we have the following
+recommendations:
+
+1. Ensure the MIME type on the web server or folder for .KMX and .KMP
+ files is set up to application/octet-stream. Without this, .KMP
+ files may be recognised as .ZIP files -- this is not helpful to the
+ end user as it will be opened in the wrong application. If you can't
+ make this change, consider hosting the keyboard package on the
+ Tavultesoft website to make things easier for your end user.
+2. Avoid putting the .KMP file in an archive (e.g. .ZIP) or
+ self-expanding archive (.EXE) - this makes it harder for end users
+ to install. A .KMP file is already compressed (it is actually just a
+ ZIP archive file!) and you won't save much space by recompressing
+ it.
+3. Include a link to the Keyman download page:
+ `http://keyman.com/desktop/`
+
+## Distributing a package by email
+
+Attaching the KMP file directly to an email may be blocked for security
+reasons. As mentioned above, a KMP file is basically a ZIP file and
+mobile keyboard data is in JavaScript, this is a combination that looks
+suspicious to many email servers. You can upload it to a Google drive,
+and email a link for downloading the file
+
+You may need to do some exploring to get your mobile device to load the
+keyboard from the KMP file into Keyman. The device may not open the file
+directly, claiming it is an unrecognized type. If you look for other
+options to open the file, hopefully you can get it to Keyman. For
+example, in iOS, I had to tap on the three dots to the right, then
+choose "Open in" and then I could choose "Copy to Keyman." In Android, I
+had to download the file to my device, then open the KMP file in a file
+manager before I could install it in Keyman.
\ No newline at end of file
diff --git a/developer/docs/help/guides/index.md b/developer/docs/help/guides/index.md
new file mode 100644
index 00000000000..55604780ad2
--- /dev/null
+++ b/developer/docs/help/guides/index.md
@@ -0,0 +1,13 @@
+---
+title: Keyman Developer Guides
+---
+
+Need help using Keyman Developer? You'll find everything you need here, including product documentation, frequently asked questions and tutorials.
+
+- [Introduction to Keyman Developer](intro)
+- [Developing Keyman keyboards](develop/)
+- [Testing Keyman keyboards](test/)
+- [Distributing Keyman keyboards](distribute/)
+- [Developing Keyman lexical models (for predictive text and autocorrect)](lexical-models/)
+- [Distributing Keyman lexical models](lexical-models/distribute/)
+- [Keyman Developer command line tools guide](command-line)
\ No newline at end of file
diff --git a/developer/docs/help/guides/intro.md b/developer/docs/help/guides/intro.md
new file mode 100644
index 00000000000..fd72c898f91
--- /dev/null
+++ b/developer/docs/help/guides/intro.md
@@ -0,0 +1,43 @@
+---
+title: Introducing Keyman Developer
+---
+
+## Welcome to Keyman Developer
+
+Keyman Developer allows you to create your own keyboard layouts for use
+on the Keyman platform. Keyman is a multi-platform input method solution
+that runs on Windows, macOS, the web, mobile web, iOS and Android and
+Linux.
+
+There are two core applications included in Keyman Developer: the Keyman
+Developer IDE (formerly called TIKE), and the command line compiler kmc.
+
+[Watch a video](https://youtu.be/kwhgx_eX4Es) that highlights some of
+the Keyman Developer features.
+
+## Keyman Developer IDE
+
+The Keyman Developer IDE is a complete environment for designing,
+developing, testing, and packaging your keyboards for distribution. Some
+of the major features of Keyman Developer IDE are:
+
+- Keyboard Editor, for creating keyboards visually for all platforms
+- Touch Layout editor, for creating fluid touch layouts
+- Package Editor, for creating a package of keyboard, fonts and files for distribution (used on all platforms except web)
+- Lexical Model Editor, for creating predictive text models
+- Integrated compiler, to compile keyboards, packages and models
+- Fully interactive debugger for testing keyboards and models
+- Character Map, which supports viewing Unicode fonts under all Windows versions
+- On Screen Keyboard editor, for creating visual representations of keyboards
+- Projects, for managing keyboard and lexical model development projects large and small
+
+Source files (except images!) are normally written in UTF-8 format, so
+they can be opened with any text editor. However, the Keyman Developer
+IDE provides much tooling to simplify editing each of the types of
+files. The Keyman Developer IDE runs on Windows.
+
+## kmc, the command line compiler
+
+
+kmc, the command-line compiler, is a comprehensive tool that lets you
+compile keyboards, packages, models and projects from the command-line.
diff --git a/developer/docs/help/guides/lexical-models/advanced/index.md b/developer/docs/help/guides/lexical-models/advanced/index.md
new file mode 100644
index 00000000000..951df2fc48e
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/index.md
@@ -0,0 +1,19 @@
+---
+title: Advanced Lexical Models Topics
+---
+
+> For the following topics, **knowledge of TypeScript or JavaScript is
+required**.
+
+The Keyman lexical model engine is capable of much more sophisticated
+lexical models than the one described in the [tutorial](../tutorial). To
+make advanced models, you will need to modify the [model definition file](model-definition-file), also known as the [`.model.ts` file](../../../reference/file-types/model-ts).
+
+Once you understand how to change the model.ts file, you can attempt
+these customizations to an existing wordlist project:
+
+[Changing the default punctuation](punctuation)
+
+[Creating a custom search key function](search-term-to-key)
+
+[Customizing the word breaker](word-breaker)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/model-definition-file.md b/developer/docs/help/guides/lexical-models/advanced/model-definition-file.md
new file mode 100644
index 00000000000..97b207d8f32
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/model-definition-file.md
@@ -0,0 +1,102 @@
+---
+title: The model definition file
+---
+
+This is a small [TypeScript](https://www.typescriptlang.org/) source
+code file that tells us how to define our model.
+
+In the case of the **wordlist lexical models**, the model definition
+file indicates where to find the [TSV source files](../../../reference/file-types/tsv), as well as gives us the option to tell the compiler a little bit more about our language’s spelling system or *orthography*.
+
+## The model definition template
+
+**Keyman Developer** provides a default model definition similar to the
+following. If you want to create the file yourself, copy-paste the
+following template, and save it as `model.ts`. Place this file in the
+same folder as `wordlist.tsv`.
+
+```typescript
+/*
+ sencoten 1.0 generated from template.
+
+ This is a minimal lexical model source that uses a tab delimited wordlist.
+ See documentation online at https://help.keyman.com/developer/ for
+ additional parameters.
+*/
+
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ wordBreaker: {
+ use: 'default',
+ },
+ sources: ['wordlist.tsv'],
+};
+export default source;
+```
+
+Let's step through this file, line-by-line.
+
+On the first line, we're declaring the source code of a new lexical
+model.
+
+```typescript
+const source: LexicalModelSource = {
+```
+
+On the second line, we're saying the lexical model will use the
+`trie-1.0` format. The `trie` format creates a lexical model from one or
+more word lists; the `trie` structures the lexical model such that it
+can predict through thousands of words very quickly.
+
+```typescript
+ format: 'trie-1.0',
+```
+
+On lines 3–5, we're specifying the word breaking algorithm that we want
+to use. Keyman supplies a default algorithm that conforms to the rules
+expected for many languages.
+
+```typescript
+ wordBreaker: {
+ use: 'default',
+},
+```
+
+On the sixth line, we're telling the `trie` where to find our wordlist.
+
+```typescript
+ sources: ['wordlist.tsv'],
+```
+
+The seventh line marks the termination of the lexical model source code.
+If we specify any customizations, they **must** be declared above this
+line:
+
+```typescript
+};
+```
+
+The eighth line is necessary to allow external applications to read the
+lexical model source code.
+
+```typescript
+export default source;
+```
+
+## Customizing our lexical model
+
+The template, as described in the previous section, is a good starting
+point, and may be all you need for your language. However, most language
+require a few customizations. The `trie-1.0` wordlist model supports the
+following customizations:
+
+[Punctuation](punctuation)
+: How to define certain punctuation in your language.
+
+[Word breaker](word-breaker)
+: How to determine when words start and end in the writing system.
+
+[Search term to key](search-term-to-key)
+: How and when to ignore accents and letter case.
+
+To see all of the things possible in a model definition file, see the [`LexicalModelSource` interface](https://github.com/keymanapp/keyman/blob/stable-15.0/developer/js/source/lexical-model-compiler/lexical-model.ts#L95-L146).
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/punctuation.md b/developer/docs/help/guides/lexical-models/advanced/punctuation.md
new file mode 100644
index 00000000000..a665f08e4d8
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/punctuation.md
@@ -0,0 +1,73 @@
+---
+title: Punctuation
+---
+
+The lexical models use two different kinds of punctuation:
+
+- Quotation marks around the “keep” suggestion.
+- What kind of space to insert after words, if any.
+
+Both of these can be customized by adding the punctuation to [model definition file](./model-definition-file). Here is a full example of a model definition file using default punctuation:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // CUSTOMIZE THIS:
+ punctuation: {
+ quotesForKeepSuggestion: {
+ open: "“", close: "”"
+ },
+ insertAfterWord: " ",
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+## Customizing `quotesForKeepSuggestion`
+
+These are the quotation marks that surrond the “keep” suggestion when
+it's displayed in the suggestion bar. By defaut, the quotations used are
+“smart” quotation marks used in English typography. Namely, the **open
+quote** is `“` U+201C LEFT DOUBLE QUOTATION MARK, and the **close
+quote** is `”` U+201D RIGHT DOUBLE QUOTATION MARK.
+
+Let's customize this to use `«` and `»` for the open and close quote, respectively. To do this, change the part labeled
+`quotesForKeepSuggestion`:
+
+```typescript
+punctuation: {
+ quotesForKeepSuggestion: {
+ open: "«", close: "»"
+ },
+},
+```
+
+## Customizing `insertAfterWord`
+
+Many languages insert a space after a word. Some languages, like Thai or Khmer, do not use spaces. To suppress the space, you may set `insertAfterWord` to the empty string:
+
+```typescript
+punctuation: {
+ insertAfterWord: "",
+},
+```
+
+You can even use an alternate spacing character, if required by your language:
+
+```typescript
+punctuation: {
+ insertAfterWord: "\u200B", // U+200B ZERO WIDTH SPACE
+},
+```
+
+## See also
+
+[The TypeScript definition of
+`LexicalModelPunctuation`](https://github.com/keymanapp/keyman/blob/4211b468949860b8fb4a4707710472ab9e33c581/common/lexical-model-types/index.d.ts#L328-L371)
+
+------------------------------------------------------------------------
+
+[Return to “Advanced Lexical Model Topics”](./)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/search-term-to-key.md b/developer/docs/help/guides/lexical-models/advanced/search-term-to-key.md
new file mode 100644
index 00000000000..086a9ca01cf
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/search-term-to-key.md
@@ -0,0 +1,93 @@
+---
+title: Search term to key
+---
+
+To look up words quickly, the `trie` model creates a _search key_
+that takes the latest word (as determined by the
+[word breaker](word-breaker)) and converts it into an internal form. The
+purpose of this internal form is to make searching for a word work, as
+expected, regardless of things such as **accents**, **diacritics**,
+**letter case**, and minor **spelling variations**. The internal form is
+called the _key_. Typically, the key is always in
+lowercase, and lacks all accents and diacritics. For example, the key
+for “naïve" is `naive` and the key for “Canada” is `canada`.
+
+The form of the word that is stored is “regularized” through the use of a _key function_, which you can define in TypeScript code.
+
+> ### Note:
+This function runs both **on every word when the wordlist is compiled** and **on the input, whenever a suggestion is requested**. This way, whatever a user types is *matched* to something stored in the lexical model, without the user having to type things in a specific way.
+
+The key function takes a string which is the raw search term, and
+returns a new string, being the “regularized” key. As an example,
+consider the **default key function**; that is, the key function that is
+used if you do not specify one:
+
+```typescript
+searchTermToKey: function (term: string): string {
+ // Use this pattern to remove common diacritical marks (accents).
+ // See: https://www.compart.com/en/unicode/block/U+0300
+ const COMBINING_DIACRITICAL_MARKS = /[\u0300-\u036f]/g;
+
+ // Lowercase each letter in the string INDIVIDUALLY.
+ // Why individually? Some languages have context-sensitive lowercasing
+ // rules (e.g., Greek), which we would like to avoid.
+ // So we convert the string into an array of code points (Array.from(term)),
+ // convert each individual code point to lowercase (.map(c => c.toLowerCase())),
+ // and join the pieces back together again (.join(''))
+ let lowercasedTerm = Array.from(term).map(c => c.toLowerCase()).join('');
+
+ // Once it's lowercased, we convert it to NFKD normalization form
+ // This does many things, such as:
+ //
+ // - separating characters from their accents/diacritics
+ // e.g., "ï" -> "i" + "◌̈"
+ // - converting lookalike characters to a canonical ("regular") form
+ // e.g., ";" -> ";" (yes, those are two completely different characters!)
+ // - converting "compatible" characters to their canonical ("regular") form
+ // e.g., "𝔥𝔢𝔩𝔩𝔬" -> "hello"
+ let normalizedTerm = lowercasedTerm.normalize('NFKD');
+
+ // Now, using the pattern defined above, replace each accent and diacritic with the
+ // empty string. This effectively removes all accents and diacritics!
+ //
+ // e.g., "i" + "◌̈" -> "i"
+ let termWithoutDiacritics = normalizedTerm.replace(COMBINING_DIACRITICAL_MARKS, '');
+
+ // The resultant key is lowercased, and has no accents or diacritics.
+ return termWithoutDiacritics;
+},
+```
+
+This should be sufficient for most Latin-based writing systems. However,
+there are cases, such as with SENĆOŦEN, where some characters do not
+decompose into a base letter and a diacritic. In this case, it is
+necessary to write your own key function.
+
+## Use in your model definition file
+
+To use this in your model definition file, provide a function as the `searchTermToKey` property of the lexical model source:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ searchTermToKey: function (wordform: string): string {
+ // Your searchTermToKey function goes here!
+ let key = wordform.toLowerCase();
+ return key;
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+## Suggested customizations
+
+- For all writing systems, **normalize into NFKD** or **NFKC** form using `wordform = wordform.normalize('NFKD')`.
+- For Latin-based scripts, **lowercase** the word, and **remove diacritics**.
+- For scripts that use the U+200C zero-width joiner (ZWJ) and/or the U+200D zero-width non-joiner (ZWNJ) (e.g., Brahmic scripts), **remove the ZWJ or ZWNJ** from the **end** of the input with `wordform = wordform.replace(/[\u200C\u200D]+$/`
+
+------------------------------------------------------------------------
+
+[Return to “Advanced Lexical Model Topics”](./)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/advanced/unicode-breaker-extension.md b/developer/docs/help/guides/lexical-models/advanced/unicode-breaker-extension.md
new file mode 100644
index 00000000000..a9e4d3776cc
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/unicode-breaker-extension.md
@@ -0,0 +1,539 @@
+---
+title: Customization and extension of the Unicode word breaker
+---
+
+> ### Note:
+While this offers a great amount of flexibility and customization that may assist predictions for your language, note that this approach may get quite technical!
+
+As mentioned on the [main word breaker page](./word-breaker),
+our currently-supported lexical models need to know what a word
+is in running text. After all, it is quite difficult to look up a word in a
+dictionary when unclear about what the start and end of that word even is.
+
+In languages using the Latin script—like, English, French,
+and SENĆOŦEN—finding words is easy. Words are separated by spaces or
+punctuation. The actual rules for where to find words can get quite tricky to
+describe, but Keyman implements the [Unicode Standard Annex #29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundaries) which works well for most languages.
+
+This guide is about techniques that may be used to customize and extend
+the behaviors of that specification to be better tailored to your language.
+This is done by using similar patterns and structures to the rules found in
+the specification itself.
+
+There are three ways - all of them optional - to extend and customize
+the word-breaking rules themselves:
+
+* If you need to prevent splits in very specific scenarios and/or add splits in other specific scenarios, you may specify [context-based rules](#rules) to obtain the desired behavior.
+* If certain characters are not handled appropriately for their role in
+ your language, you may [map characters](#map) to different
+ word-breaking character classes - including custom ones. This will
+ override the default property they are assigned by the default
+ implementation, with the new property applying for all word-breaking
+ rules.
+* If the default word-breaking classes from the specification are
+ too general for certain aspects of your language, it is possible to
+ [define custom character classes](#define) for use in custom
+ rules.
+
+## A first example
+
+This example was designed to address the needs of a minority language in the country
+of Cambodia. The majority language does not use spaces for wordbreaking, while the
+minority language in question does use them. In addition, hyphens sometimes occur
+within words.
+
+The word breaker function can be specified in the
+[model definition file](./model-definition-file) as follows:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // CUSTOMIZE THIS:
+ wordBreaker: (text) => {
+ let customization = {
+
+ /*** Definition of extra word-breaking rules ***/
+ rules: [{
+ match: (context) => {
+ if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["ALetter"])) {
+ return true;
+ } else if(context.propertyMatch(["ALetter"], ["Hyphen"], ["ALetter"], null)) {
+ return true;
+ } else if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["eot"])) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ }],
+
+ /*** Character class overrides for specific characters ***/
+ propertyMapping: (char) => {
+ let hyphens = ['\u002d', '\u2010', '\u058a', '\u30a0'];
+ // treats Khmer consonants & independent vowels in the same manner
+ // as the basic latin-script based alphabet
+ if(char >= '\u1780' && char <= '\u17b3') {
+ return "ALetter";
+ } else if(hyphens.includes(char)) {
+ return "Hyphen";
+ } else {
+ // The other Khmer characters already have useful word-breaking
+ // property assignments.
+ return null;
+ }
+ },
+
+ /*** Declares any new, custom character classes to be recognized by the word-breaker ***/
+ customProperties: ["Hyphen"]
+ };
+
+ /*** Connects all the pieces together for actual use ***/
+ return wordBreakers['default'](text, customization);
+ },
+ // ...
+};
+
+export default source;
+```
+
+This example's customization is designed to accomplish two goals:
+
+1. Unicode's wordbreaker does not map base Khmer consonants to any of the
+ [relevant wordbreaking character properties](https://unicode.org/reports/tr29/#Table_Word_Break_Property_Values), causing it to be treated as `"Other"`.
+ The minority language in question instead wishes for Latin-script-like word-breaking,
+ so mapping the consonants to the same property as Latin-script consonants allows
+ them to be treated similarly - in the manner they expect.
+
+2. By default, the wordbreaker will automatically insert word boundaries before
+ and after a hyphen. To allow mid-word hyphens, we need to remap them to
+ a different character property.
+
+ While the specification itself mentions that we could
+ just map hyphens to `"MidLetter"`, this example opts to define
+ a custom-tailored property that ensures only hyphens are affected in order to add
+ a special, end-of-context rule that may be useful when typing - the rule above
+ referencing `"eot"`.
+
+More on the wordbreaking character properties will be covered later.
+
+## Custom word-breaking rules
+
+When defining additional rules for use in word-breaking, it is advisable to reference
+the [rules of the Unicode Standard Annex #29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundary_Rules).
+
+Rules WB1 through WB4 of the specification will always apply first, before any
+custom rules. Custom rules will then be applied in order of their definition within
+the model, with them all being applied before the specification's rules labeled WB5
+and onward.
+
+Each rule should be of the following form:
+
+```typescript
+{
+ // A function that returns 'true' whenever the rule should apply
+ match: (context) => {
+ // ...
+ },
+ // Whether to prevent (false) or to enforce (true) a boundary when the rule applies.
+ breakIfMatch: false
+}
+```
+
+Toward this end, the `context` object received by match provides a function called
+`propertyMatch` in order to define rules like those of the wordbreaker specification.
+Let's take WB6 - "do not break letters across certain punctuation" - as an example.
+
+As written in the spec, WB6 reads **AHLetter x (MidLetter | MidNumLetQ) AHLetter**.
+
+This is simply a series of characters, up to two characters before and after a potential break point. To break that down:
+- (implicit) **Any** -- accept any character in this position
+- **AHLetter**: `ALetter` or `Hebrew_Letter`
+- **x** - "do not break"
+- (either) **MidLetter** or **MidNumLetQ**: one of `MidLetter`, `MidNumLet`, or `Single_Quote`.
+- **AHLetter**: `ALetter` or `Hebrew_Letter`
+
+The expansions `MidNumLetQ` and `AHLetter` are defined at https://unicode.org/reports/tr29/#WB_Rule_Macros.
+
+If written as a custom rule, rule WB6 takes the following form. Note the use of the function `context.propertyMatch`,
+which takes 4 parameters - two to match characters before and two to match characters after a potential boundary:
+
+```typescript
+// Rule WB6 from the Unicode spec, as a custom rule:
+{
+ match: (context) => {
+ return context.propertyMatch(null, // no requirements set, so "Any" character may match
+ ["ALetter", "Hebrew_Letter"],
+ // x
+ ["MidLetter", "MidNumLet", "Single_Quote"],
+ ["ALetter", "Hebrew_Letter"]);
+ },
+ breakIfMatch: false // do not break
+}
+```
+
+Note that an empty array `[]` in any slot is not treated the same as `null` -
+use of an empty `[]` will prevent the rule from matching.
+
+Almost all of the specification's rules are of this form. Again, up to two characters
+before a potential boundary may be considered alongside up to two characters after
+the potential boundary.
+
+### Word-breaking property names
+The names used in each array must be defined in one of the following places:
+* https://unicode.org/reports/tr29/#Table_Word_Break_Property_Values
+* `customProperties` - your [declaration of any custom property types](#define)
+* One of the special property types `"Other"`, `"sot"`, or `"eot"`:
+ * `Other`: a character without an associated word-breaking property value
+ * `sot`: "start of text" - a marker indicating the beginning of the string being word-broken
+ * `eot`: "end of text" - a marker indicating the end of the string being word-broken
+
+All rules use case-insensitive matching, so capitalization differences will not affect operation.
+
+### Rule-matching examples
+
+#### A successful rule application
+As an example, when determining whether or not to break the English word `don't` when
+applying WB6 as written above, this is what happens near the apostrophe:
+
+```typescript
+{
+ match: (context) => {
+ return context.propertyMatch(null, /* match any character */ // "o" - ALetter
+ ["ALetter", "Hebrew_Letter"], // "n" - ALetter
+ // x
+ ["MidLetter", "MidNumLet", "Single_Quote"], // "'" - Single_Quote
+ ["ALetter", "Hebrew_Letter"]); // "t" - ALetter
+ },
+ breakIfMatch: false // do not wordbreak at any position where the rule matches.
+}
+```
+
+The rule applies at the position between the `n` and the `'` of `don't`, telling the
+word-breaker not to word-break if the rule matches at that location.
+
+#### An unsuccessful rule match
+
+Given the input `n't|` (where `|` is the caret), we can see that the rule will not match:
+```typescript
+{
+ match: (context) => {
+ return context.propertyMatch(null, /* automatic match */ // "n" - ALetter
+ ["ALetter", "Hebrew_Letter"], // "'" - Single_Quote
+ // x
+ ["MidLetter", "MidNumLet", "Single_Quote"], // "t" - Single_Quote
+ ["ALetter", "Hebrew_Letter"]); // - eot
+ },
+ breakIfMatch: false // do not break
+}
+```
+
+The same rule does not apply between the `'` and the `t`, so it does not apply at this position in
+the text. This is why the wordbreaking spec includes rule WB7 in addition to WB6 - some scenarios
+require multiple context property-matching attempts.
+
+You may define any number of these rule objects in any order for use within the wordbreaker within
+the `rules` array.
+
+> **Important note**: When no other rule successfully matches a potential word-boundary position, the spec's rule WB999 applies as a catch-all and will enforce a split. Preventing a word-break always requires a successfully-matching rule, though this is covered for most cases by the Unicode specification's ruleset.
+
+Defining custom rules is a powerful tool, but it is detailed work and may be somewhat tedious
+to get right. Feel free to [ask for help at our Community Site](https://community.software.sil.org/c/keyman/19) for assistance.
+
+## Character property remapping
+
+Many writing systems in the world are shared by multiple languages, using most of the same
+characters in common. However, sometimes there may be notable differences in how specific
+languages wish to treat certain characters. In order to address these cases, we allow
+overriding the "standard" word-breaking property that the Unicode specification gives the
+character with one set by the lexical model.
+
+### Default character properties
+
+For reference, [this text file](https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt)
+provides the standard word-breaking properties for all characters. This is one of many
+files Unicode provides publicly here: https://www.unicode.org/reports/tr41/#Props0.
+
+That text file contains many lines of the following form:
+
+```
+0041..005A ; ALetter # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z
+```
+
+This says that `0041` through `005A` - or rather, `\u0041` (the code for 'A') through
+`\u005A` (the code for 'Z') - are assigned word-breaking property `ALetter`. That
+range covers 26 (`[26]`) characters. (For our purposes here, the `L&` part is
+irrelevant.)
+
+As noted at the top of the file:
+
+```
+# All code points not explicitly listed for Word_Break
+# have the value Other (XX).
+
+# @missing: 0000..10FFFF; Other
+```
+
+### Redefining character properties
+
+Of note from [our first example](#example):
+
+```typescript
+/*** Character class overrides for specific characters ***/
+propertyMapping: (char) => {
+ // ...
+ // treats Khmer consonants & independent vowels in the same manner
+ // as the basic latin-script based alphabet
+ if(char >= '\u1780' && char <= '\u17b3') {
+ return "ALetter";
+ } // ...
+},
+```
+
+If you search the [property definition text file](https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt)
+for `1780` or `17b3`, you will find neither. These correspond to many letters
+from the Khmer character set - notably, the 'base' characters used in Khmer's
+grapheme clusters. The other Khmer characters tend to attach at various positions
+around these base characters. The majority language for the script - Khmer -
+does not follow conventional word-breaking rules; most notably, they do not add
+whitespace between each word. (There are other strategies that get utilized for
+such scripts.)
+
+As breaks sometimes occur between the base characters while other times do not,
+properties for these base characters were not explicitly defined and are thus
+treated as class `Other`.
+- As other Khmer characters tend to attach around the base characters, they do
+ have specified word-breaking properties - they `Extend` the grapheme cluster.
+- Searching `17b4` in [the text file mentioned above](https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt) will show the closest results to the characters under discussion.
+
+However, there are minority languages that prefer to use whitespaces between words,
+meaning that there should never be wordbreaks applied directly between neighboring
+characters for their words. In such cases, we can map them to a pre-existing property
+with the desired behavior - `ALetter`.
+
+```typescript
+if(char >= '\u1780' && char <= '\u17b3') {
+ return "ALetter"; // Maps Khmer-script base characters to ALetter
+ // to allow Latin-script-like word-breaking.
+}
+```
+
+For another example of character property remapping, consider the use of hyphens with
+words (and/or names) in some languages. Default word-breaking behavior will split
+hyphenated words and names apart, but by changing the property of hyphens, it is
+possible to disable this behavior.
+
+Noting [rule WB6](#WB6) and WB7, the `MidLetter` class is designed to prevent
+word-breaks from occurring when its characters lie directly between letters -
+hence the property name. Assigning hyphens to this class can provide the
+desired behavior.
+
+```typescript
+let hyphens = ['\u002d', '\u2010', '\u058a', '\u30a0'];
+// ...
+if(hyphens.includes(char)) {
+ return "MidLetter";
+} // ...
+```
+
+## Defining and using new word-breaking properties
+
+There may be some cases in which none of the default character word-breaking
+properties provide the exact behavior that you're wanting, or perhaps you
+want only specific characters from that class to match custom rules. For such
+cases, wordbreaker customization also allows definition of new word-breaking
+properties.
+
+For one example, note how word-breaking operations affect predictions when
+typing new words:
+
+- `can'`, with the intent to type `can't`
+- `full-`, with the intent to type `full-scale`
+
+For the first example above, while `'` (property `Single_Quote`) is included within
+WB6 and WB7, those rules only apply _between letters_. If there is no letter
+on the right-hand side, `can'` will be interpreted as `can` + `'` by the
+word-breaking algorithm. Similarly, even when remapping `-` to the `MidLetter`
+property, `full-` will be remapped to `full` + `-` before additional text
+is typed.
+
+Of course, this problem does alleviate itself once another `ALetter`-property
+letter is typed, but suppose we wanted a rule to prevent word-breaking for
+the second example above. (After all, `can'` could be the end of a quoted
+phrase in English - `'sure you can'` - in which case we might want the split
+to occur.)
+
+Revisiting [an earlier example](#example) and simplifying a little bit:
+
+```typescript
+/*** Definition of extra word-breaking rules ***/
+{
+ rules: [{
+ match: (context) => {
+ if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["eot"])) {
+ return true;
+ } else {
+ /* ... */
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ }],
+
+ /*** Character class overrides for specific characters ***/
+ propertyMapping: (char) => {
+ let hyphens = ['\u002d', '\u2010', '\u058a', '\u30a0'];
+ if(hyphens.includes(char)) {
+ return "Hyphen";
+ } else {
+ // Use the default properties for anything else.
+ return null;
+ }
+ },
+
+ /*** Declares any new, custom character classes to be recognized by the word-breaker ***/
+ customProperties: ["Hyphen"]
+}
+```
+
+Let's walk through what this simplification is trying to achieve:
+
+1. Hyphens are mapped to their own distinct word-breaking property.
+2. The custom rule prevents wordbreaking between a letter and a hyphen at the end of text.
+ - It does not include any of the `MidLetter` characters.
+
+Matching the rule against the end of text suggests that what follows the `Hyphen` character
+is the point of text insertion. For this example, assuming that a user has just typed
+`full-`, there will be no word-break on the hyphen until either more input is received or
+the user changes the site of text entry.
+
+**Important note**: You must declare any custom properties within the `customProperties` array.
+If any are missing, the missing custom properties will fail to match against any word-breaking rule.
+Make sure you don't misspell it anywhere in your customization code!
+
+### Default rules + custom properties
+
+There is one notable issue with this example, though - whenever you remap a character to
+a new property, it is no longer considered to have its old property, and so it will no
+longer match rules based on its default property. This is why the original example
+included a couple of extra rules:
+
+```typescript
+{
+ match: (context) => {
+ // Extend WB6 - allow "Hyphen" in the same place as "MidLetter"
+ if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["ALetter"])) {
+ return true;
+ // Extend WB7 - allow "Hyphen" in the same place as "MidLetter"
+ } else if(context.propertyMatch(["ALetter"], ["Hyphen"], ["ALetter"], null)) {
+ return true;
+ // The same rule from above.
+ } else if(context.propertyMatch(null, ["ALetter"], ["Hyphen"], ["eot"])) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+}
+```
+
+By replicating [WB6](#WB6) and WB7's structure and allowing `Hyphen` to match in the same
+position as `MidLetter` in the original rules, we can prevent word-breaking splits
+after additional text has been typed after a `Hyphen`-property character. This does not
+_replace_ the behavior of WB6 and WB7 - it merely _extends_ it to include the new property.
+
+### A more complex case
+
+A meatier example may be found as [the specification's hypothetical rule WB5a](https://unicode.org/reports/tr29/#WB999):
+
+> "Break between apostrophe and vowels (French, Italian)"
+>
+> WB5a: **Apostrophe ÷ Vowels**.
+
+The idea of this rule is to allow words such as the French `l'objectif` to be split
+into the article - `l'` and its following word - `objectif` while preserving other
+cases that should still be treated as single words, such as `aujourd'hui`.
+
+To simplify the code needed for customization here somewhat, we will use `Single_Quote`
+in place of `Apostrophe`, as well as a few extra simplifications. The new `Vowels`
+property offers enough complexity as it is.
+
+```typescript
+let customization = {
+ rules: [
+ // WB5 extension - ensure `AVowel` is handled like `ALetter`.
+ {
+ match: (context) => {
+ if(context.propertyMatch(null, ["ALetter", "AVowel"], ["ALetter", "AVowel"], null)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ },
+ // Our main goal WB5a
+ {
+ match: (context) => {
+ if(context.propertyMatch(null, ["Single_Quote"], ["AVowel"], null)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: true
+ },
+ // WB6, 7 extension - ensure `AVowel` is handled like `ALetter`
+ {
+ match: (context) => {
+ if(context.propertyMatch(null,
+ ["ALetter", "AVowel"],
+ ["MidLetter", "MidNumLet", "Single_Quote"],
+ ["ALetter", "AVowel"])) {
+ return true;
+ } else if(context.propertyMatch(["ALetter", "AVowel"],
+ ["MidLetter", "MidNumLet", "Single_Quote"],
+ ["ALetter", "AVowel"],
+ null)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ breakIfMatch: false
+ }
+ // Similar extensions to WB9, 10, 13a, and 13b would also be needed for robustness.
+ // Note: we have left "Hebrew_Letter" out of the WB5, 6, and 7 rewrites to help
+ // simplify this example.
+ ],
+ propertyMapping: (char) => {
+ const vowels = ['a', 'e', 'i', 'o', 'u'];
+ // French and Italian allow accented vowels; this will strip off the accent and
+ // leave us with the base vowel.
+ const baseChar = char.normalize('NFD').charAt(0);
+ if(vowels.includes(baseChar)) {
+ return "AVowel";
+ }
+
+ return null;
+ },
+ customProperties: ["AVowel"]
+}
+```
+
+Note that we have left out some of the rule extensions that would help cover certain
+less-frequently encountered cases. These may matter to some parts of a
+language community, especially for models targeting a majority language.
+Computer programmers in particular tend to care about the un-extended
+rules (WB9, 10, 13a, and 13b) more than most.
+
+Remember, remapping characters to a new word-breaking property prevents any default
+rule from handling them unless you add custom rules to re-include them as their
+new property.
+
+------
+
+[Return to “Advanced Lexical Model Topics”](./)
diff --git a/developer/docs/help/guides/lexical-models/advanced/word-breaker.md b/developer/docs/help/guides/lexical-models/advanced/word-breaker.md
new file mode 100644
index 00000000000..f8f321327ac
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/advanced/word-breaker.md
@@ -0,0 +1,218 @@
+---
+title: Word breaker
+---
+
+The `trie` family of lexical models needs to know what a word is in
+running text. In languages using the Latin script—like, English, French,
+and SENĆOŦEN—finding words is easy. Words are separated by spaces or
+punctuation. The actual rules for where to find words can get quite
+tricky to describe, but Keyman implements the [Unicode Standard Annex #29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundaries)
+which works well for most languages.
+
+However, in languages written in other scripts — especially East Asian
+scripts like Chinese, Japanese, Khmer, Lao, and Thai — there are no obvious break in between words. For these languages, there must be special rules for determining when words start and stop. This is what a _word breaker function_ is responsible for. It is a little bit of code that looks at some text to determine where the words are.
+
+You can customize the word breaker in three ways:
+- If your language uses its writing system in an unconventional way (e.g., use spaces to separate words in Thai, Lao, Burmese, or Khmer), you can [override the script's default behaviour](#overrides)
+- If the default word breaker creates **too many splits**, you can [choose which strings join words together](#join).
+- If the default word breaker creates **not enough splits**, you must [create your own word breaker function](#custom).
+- Alternatively, you may choose to [customize and extend the wordbreaker's behavior](./unicode-breaker-extension) by adding extra rules and changing how it treats specific characters.
+
+## Overriding script defaults
+
+The default word breaker makes assumptions about how each
+_script_ (alphabet, syllabary, or writing system)
+works. You can override the defaults by specifying the
+`overrideScriptDefaults` option.
+
+There is currently only one override:
+
+`'break-words-at-spaces'`
+: Only breaks words at spaces for scripts that otherwise do not use spaces in between words.
+
+### Break words at spaces
+
+This applies only to languages that borrow the **Burmese**, **Khmer**,
+**Lao**, or **Thai** scripts. The majority languages for these scripts
+do *not* use spaces in between words; hence, the default word breaker
+will produce undesired results when breaking words in these scripts.
+However, if your language is written in one of these scripts and *does*
+use spaces in between words, then you can set
+`overrideScriptDefaults: 'break-words-at-spaces',` to ensure word breaks
+do not occur in the middle of words, but instead, at spaces.
+
+Your model definition file should look like this:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ wordBreaker: {
+ use: 'default', // we want to use the default word breaker, BUT!
+ // Override the default for Burmese, Khmer, Lao, or Thai:
+ overrideScriptDefaults: 'break-words-at-spaces',
+ }
+};
+
+export default source;
+```
+
+## Customize joining rules
+
+The default word breaker is very liberal in what it considers is a word.
+
+For instance, the default word breaker will split words at hyphens.
+Consider the following Plains Cree example; this is a single word:
+
+: amiskwaciy-wâskahikan
+
+However, the default word breaker will produce three words: `amiskwaciy`, `-`, and `wâskahikan`.
+
+To **join words at hyphens and any other punctuation**, provide the
+`joinWordsAt` option in the [model definition file](./model-definition-file):
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ wordBreaker: {
+ use: 'default', // we want to use the default word breaker, BUT!
+ // CUSTOMIZE THIS:
+ joinWordsAt: ['-'], // join words that contain hyphens
+ }
+};
+
+export default source;
+```
+
+You can specify one or more strings to join words at:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ wordBreaker: {
+ use: 'default',
+ // CUSTOMIZE THIS:
+ joinWordsAt: ['-', ':', '@'], // join words at hyphens, colons, at-signs
+ }
+};
+
+export default source;
+```
+
+## Writing a custom word breaker function
+
+> **Note**:
+If your language uses spaces to denote word breaks, the
+default word breaker is probably sufficient. Only customize this if you
+know the default word breaker really does not work for your language!
+
+The word breaker function can be specified in the [model definition file](./model-definition-file) as follows:
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // CUSTOMIZE THIS:
+ wordBreaker: {
+ use: function(text: string): Span[] {
+ // Return zero or more **spans** of text:
+ return [];
+ },
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+The function must return zero or more `Span` objects. The spans,
+representing an indivisible span of text, must be in ascending order of
+their start point, and they must be non-overlapping.
+
+### A `Span` object
+
+A _span_ is an indivisible piece of a sentence.
+This is typically a word, but it can also be a series of spaces, an
+emoji, or a series of punctuation characters. **A span that looks like a word is treated like a word in the `trie-1.0` model**.
+
+A `span` has the following properties:
+
+```typescript
+{
+ start: number;
+ end: number;
+ length: number;
+ text: string;
+}
+```
+
+The `start` and `end` properties are indices into the original string at
+which the span begins, and the index at which the *next* span begins.
+
+`length` is `end - start`.
+
+`text` is the actual text of the string contained within the span.
+
+## Example for English
+
+Here is a full example of word breaker function that returns an array of
+spans in an ASCII (English) string.
+
+> **Note**
+This is just an example—please
+use the default word breaker for English text!
+
+```typescript
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+ // EXAMPLE BEGINS HERE:
+ wordBreaker: function(text: string): Span[] {
+ // A span derived from a JavaScript RegExp match array:
+ class RegExpDerivedSpan implements Span {
+ readonly text: string;
+ readonly start: number;
+
+ constructor(text: string, start: number) {
+ this.text = text;
+ this.start = start;
+ }
+
+ get length(): number {
+ return this.text.length;
+ }
+
+ get end(): number {
+ return this.start + this.text.length;
+ }
+ }
+
+ let matchWord = /[A-Za-z0-9']+/g;
+ let words: Span[] = [];
+ let match: RegExpExecArray;
+ while ((match = matchWord.exec(phrase)) !== null) {
+ words.push(new RegExpDerivedSpan(match[0], match.index));
+ }
+
+ return words;
+ },
+ // other customizations go here:
+};
+
+export default source;
+```
+
+## See also
+
+- [The TypeScript definition of `WordBreakingFunction` and
+`Span`](https://github.com/keymanapp/keyman/blob/4211b468949860b8fb4a4707710472ab9e33c581/common/lexical-model-types/index.d.ts#L286-L323)
+
+- [Extension and customization of the Unicode word-breaker](./unicode-breaker-extension)
+
+- [The Unicode Standard Annex \#29 §4.1 Default Word Boundary Specification](https://unicode.org/reports/tr29/#Word_Boundaries)
+
+------------------------------------------------------------------------
+
+[Return to “Advanced Lexical Model Topics”](./)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/index.md b/developer/docs/help/guides/lexical-models/distribute/index.md
new file mode 100644
index 00000000000..42f3741af17
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/index.md
@@ -0,0 +1,6 @@
+---
+title: Distributing lexical models
+---
+
+- [Lexical model package development tutorial](tutorial/) (Keyman for mobile apps)
+- [Distribute lexical models to Keyman applications](packages)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/packages.md b/developer/docs/help/guides/lexical-models/distribute/packages.md
new file mode 100644
index 00000000000..92e61650308
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/packages.md
@@ -0,0 +1,70 @@
+---
+title: Distribute lexical models to Keyman Applications
+---
+
+## Overview
+
+Lexical model package files contain one lexical model, along with readme
+files, and any other files you wish to include. You must create a
+package file to bundle your lexical model and help documentation into a
+simple, single file that is easy for an end-user to install.
+
+The package file is a ZIP compatible archive.
+
+> ### Tip
+You can distribute keyboards and lexical models in package files, but
+you can't include both in the same package file.
+
+#### Keyman for Android and Keyman for iPhone and iPad
+
+Keyman mobile applications can install the same lexical model package
+files.
+
+## Package file contents
+
+A package can have a variety of different files contained within. The
+following files and file types are recognized by the package installer:
+
+\*.model.js
+: Lexical model file. When Keyman mobile applications install a
+ lexical model package, the included model will be installed and
+ associated with the specified languages.
+
+welcome.htm
+: Introductory help for the lexical model, HTML format. This will
+ normally be displayed when the package is installed by the user, and
+ is also the entry point for help when accessed via Keyman's help
+ system or Keyman Configuration.
+
+## Step 1) Share the lexical model package file
+
+Once the lexical model package .kmp file is created, you can share them
+via external storage devices (USB drive, SD card, etc). If that is not
+an option, you can upload the .kmp file to a public facing website. For
+this example, the lexical model package for SENĆOŦEN is being uploaded:
+
+1. nrc.str.sencoten.model.kmp (the lexical model package .kmp file)
+
+## Step 2) Create a link to the KMP file
+
+Once all the files have been uploaded, you will need to provide a link
+to the lexical model package .kmp file for your device to download and
+install. This can either be a link on a web page, or a link in an email.
+In this tutorial, a very simple .html web page with a link to the
+nrc.str.sencoten.kmp file is created:
+
+```html
+
+
+
+
+ Sencoten Lexical Model Package
+
+
+```
+
+The link must be in the format `http://` or `https://`
+
+Upload the web page to your public facing website. Once done, you can
+install the lexical model package onto your mobile devices by
+downloading the .kmp from your device's internet browser.
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/index.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/index.md
new file mode 100644
index 00000000000..fe4e0d039ec
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/index.md
@@ -0,0 +1,32 @@
+---
+title: Lexical Model Package Tutorial
+---
+
+[Step 1: What do we include?](step-1)
+
+[Step 2: Editing .htm files for the Package](step-2)
+
+[Step 3: Checking a package and adding files](step-3)
+
+[Step 4: Filling in package details](step-4)
+
+[Step 5: Compiling, testing and distributing a Package](step-5)
+
+## Overview
+
+Welcome! In this tutorial, you will learn how to create a lexical model
+[package](../../../../reference/file-types/kmp). A package is a
+collection of files, compressed into a single file, just like a ZIP
+file. It is designed to make installation of a lexical model
+straightforward for the end user. When done, Keyman for mobile apps can
+download and install lexical models through the package.
+
+We will be creating a package for the SENĆOŦEN lexical model that was
+made in [Developing a lexical model from a word list](../../tutorial/).
+
+### Let's begin
+
+Let's get started! Move on to the next topic to begin the first step,
+choosing what to include in the package.
+
+[Step 1: What do we include?](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-1.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-1.md
new file mode 100644
index 00000000000..d27970b9e43
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-1.md
@@ -0,0 +1,15 @@
+---
+title: Step 1: What do we include?
+---
+
+What is the purpose of a lexical model package? To make installation of
+a lexical model as straightforward as possible for the end user. We need
+to keep this goal in mind as we work on all the aspects of a package.
+
+A great package will:
+
+1. Include one lexical model to install.
+2. Include a 'readme.htm' file which describes why the user would install this package.
+3. Include a 'welcome.htm' file which is displayed before/after install.
+
+[Step 2: Editing .htm files for the package](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-2.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-2.md
new file mode 100644
index 00000000000..10a560fd194
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-2.md
@@ -0,0 +1,72 @@
+---
+title: Step 2: Editing .htm files for the package
+---
+
+When Keyman Developer created your lexical model project, it will have
+created some of these files to go in the package. You will still need to
+edit some of these templated files for the package.
+
+readme.htm
+
+: A short description of the package, its use restrictions, and what
+ it includes. Try to keep the readme under 10 lines long. The readme
+ should be an html file for optimal formatting.
+
+ The intention of readme.htm is to describe why a user would want to
+ install the lexical model.
+
+ Create the HTML file in any HTML editor.
+
+ > ### Tip
+ If using Microsoft Word, choose HTML (clean) when saving to create a
+ smaller file.
+
+ Note, if your HTML editor puts images into a subfolder, you will
+ need to edit the HTML source so that all files are in the same
+ folder -- the package builder will not maintain subfolders. You can
+ easily edit the HTML source in Keyman Developer.
+
+ Also, if your welcome or readme files use embedded external images,
+ stylesheets, javascript or other files, you will need to add these
+ files to your package as well.
+
+welcome.htm
+
+: When including an introductory help file in your package, you must
+ keep the name of the file "welcome.htm". This file will be displayed
+ before the lexical model is installed. Make sure that you design
+ your HTML file so that it can be viewed on a mobile device - avoid
+ extra wide tables or wide fixed width elements.
+
+ After package installation, the welcome.htm will also be accessible
+ from the lexical model info pages.
+
+ The intention of welcome.htm is to provide instructions on getting
+ started with your lexical model.
+
+ > ### Tip
+ Useful information to include in welcome.htm is:
+ - names of languages associated with the lexical model.
+ - name of the lexical model in the package.
+ - links to additional help on your website or more extensive
+ documentation files in the package.
+ - a link to an official distribution site for your package (even
+ if it is the Keyman website) - so that users know where to find
+ the latest version of your package.
+
+
+
+ > ### Tip
+ If you want links to your website to open in the user's preferred
+ browser, preface the href link with `link:`, e.g.
+ `website `
+ The `link:` sceheme will open the referred file in the default
+ application - that is, a web browser for URLs and links, Notepad for
+ .txt files, Adobe Reader for PDFs. You can use `link:` to open any
+ of the files in the package, e.g. `link:docs.pdf` will open the file
+ docs.pdf in Adobe Reader or the default PDF reader on the system.
+
+ If you create documents in other formats, for example PDF or
+ printable documentation, you should link to that in the welcome.htm.
+
+[Step 3: Checking a package and adding files](step-3)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-3.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-3.md
new file mode 100644
index 00000000000..9d1ea260002
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-3.md
@@ -0,0 +1,51 @@
+---
+title: Step 3: Checking a package and adding files
+---
+
+We are finally ready to open up the Package Editor and check the package
+contents. In the Project Window in Keyman Developer, click the
+**Packaging** tab.
+
+> ### Tip
+This tutorial makes a package named `nrc.str.sencoten.model.kps`, but
+you should substitute the name with your own model.
+
+A lexical model package source file will have the extension .model.kps,
+and will be compiled in a file with extension .kmp.
+
+## Files
+
+In the Package Editor, click on the **Files**
+tab.
+
+![Lexical Model Package Files](../../../../images/lm/tutorial_distribute_model_3_files.png)
+
+Keyman Developer already included your lexical model, welcome.htm, and
+readme.htm files in your package. In the Files tab, click
+**Add** to add all the additional files we
+discussed in the previous step to the package. For example, if your
+welcome or readme files use embedded files, include them in your
+package. You can add multiple files at once, and from multiple folders.
+When the package is compiled, all the files will be placed in the same
+folder within the package.
+
+While keyboards can also be distributed in packages, do not include them
+in a lexical model package.
+
+## Lexical Models
+
+In the Package Editor, click on the **Lexical Models** tab.
+
+![Lexical Model Info](../../../../images/lm/tutorial_distribute_model_3.png)
+
+Language Tag
+
+: A valid BCP 47 language tag must be set or the lexical model will
+ not install on your mobile device. Update the list of languages you
+ want to associate with your lexical model.
+
+You could stop here. This would be a completely valid package, but it
+would not be as good as it could be. So let's continue on to the next
+step, and fill in some descriptions of the package.
+
+[Step 4: Filling in package details](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-4.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-4.md
new file mode 100644
index 00000000000..bae0be8c676
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-4.md
@@ -0,0 +1,55 @@
+---
+title: Step 4: Filling in package details
+---
+
+In the Package Editor, click on the **Details** tab. You should fill in as many details as you can on this page.
+
+![](../../../../images/lm/tutorial_distribute_model_details.png)
+
+Package Name
+
+: The Package Name will be displayed in the package install dialog and
+ wherever the package is referred to.
+
+Model Version
+
+: Update the version (intial version can default to 1.0). A version
+ number for the model and package is important - it helps your users
+ know that they are using the most recent update of your package. The
+ version format you should use is `1.0`.
+
+ When making a major change to your lexical model package, increment
+ the first part and set the second part to `0`, e.g. from `1.0` to
+ `2.0`.
+
+ When making a bug fix or a minor update, increment the second part,
+ e.g. from `1.0` to `1.1`.
+
+ Version numbers should be in the form `major.minor[.subversion]`.
+ Subversion is optional but is helpful for small bug fix releases.
+ Each of the sections of the version should be an integer. Keyman
+ does integer comparisons on the version numbers, so, for example,
+ version `2.01` is regarded as older than version `2.2`. Alphabetic
+ or date formats should be avoided as the installer for the model
+ cannot determine which version is older reliably.
+
+Copyright
+
+: Enter copyright details for your lexical model package. Keep this
+ reasonably short or it won't be clear for end users.
+
+Author
+
+: Enter your name or the name of your company.
+
+Email
+
+: Enter a contact email address where package users can contact you.
+ If you don't want to be contacted via email, leave this field empty
+
+Website
+
+: Enter the name of the website where you will have information about
+ this lexical model.
+
+[Step 5: Compiling, testing and distributing a Package](step-5)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/distribute/tutorial/step-5.md b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-5.md
new file mode 100644
index 00000000000..ed873c69cab
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/distribute/tutorial/step-5.md
@@ -0,0 +1,55 @@
+---
+title: Step 5: Compiling, testing and distributing a Package
+---
+
+In the Package Editor, click on the **Compile** tab.
+
+![](../../../../images/lm/tutorial_distribute_model_compile.png)
+
+Click **Compile Package** to compile the
+package into a .kmp file. Compiling takes all the files listed for the
+package, compresses them (using a .ZIP-compatible format) and adds the
+package information, all into a single file. If any files are not
+available, an error will be listed in the Messages window.
+
+After compiling, you can test the package installation in the mobile
+Keyman apps. You should test that all the model installs successfully,
+that the Welcome file is displayed during the install, and that the
+documentation is accessible to the end user.
+
+## Distributing a package on the Keyman Cloud Lexical Model Repository
+
+Once you have tested the package to your satisfaction, it is time to
+distribute it. We recommend submitting your lexical model package to the
+[Keyman Lexical Models Repository](https://github.com/keymanapp/lexical-models)
+
+## Distributing a package on your own website
+
+If you distribute a package on your own site, we have the following
+recommendations:
+
+1. Ensure the MIME type on the web server or folder for .KMP files is
+ set up to application/octet-stream. Without this, .KMP files may be
+ recognised as .ZIP files -- this is not helpful to the end user as
+ it will be opened in the wrong application.
+2. Avoid putting the .KMP file in an archive (e.g. .ZIP) or
+ self-expanding archive (.EXE) - this makes it harder for end users
+ to install. A .KMP file is already compressed (it is actually just a
+ ZIP archive file!) and you won't save much space by recompressing
+ it.
+3. Include a link to the Keyman download page:
+ `http://keyman.com/downloads/`
+
+## Distributing a package by email
+
+- Attaching the KMP file directly to an email may be blocked for security
+reasons. As mentioned above, a KMP file is basically a ZIP file and
+lexical model data is in JavaScript, this is a combination that looks
+suspicious to many email servers. You can upload it to a Google drive,
+and email a link for downloading the file
+
+## Installing the lexical model package
+
+- The same method for installing custom keyboard packages to mobile
+devices can be used for installing lexical model packages. Refer to
+these pages for [Keyman for iPhone and iPad](../../../distribute/install-kmp-ios) and [Keyman for Android](../../../distribute/install-kmp-android).
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/index.md b/developer/docs/help/guides/lexical-models/index.md
new file mode 100644
index 00000000000..e913ce7007c
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/index.md
@@ -0,0 +1,22 @@
+---
+title: Developing lexical models (dictionaries)
+---
+
+A **lexical model** (Keyman apps use the term
+**dictionary**) is what powers **predictive text**
+and **autocorrect** for a language. If you want your keyboard to predict
+and correct words in your language, you must create a lexical model that
+generates suggestions for your language.
+
+[What is a lexical model? Or: how do I add prediction and autocorrection
+to my keyboard?](intro)
+
+[Tutorial: Developing a lexical model from a word list](tutorial)
+
+[Advanced lexical models topics](advanced)
+
+[Distribute a lexical model](distribute)
+
+## Reference
+
+[Wordlist TSV format](../../reference/file-types/tsv)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/intro/index.md b/developer/docs/help/guides/lexical-models/intro/index.md
new file mode 100644
index 00000000000..314a571ea52
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/intro/index.md
@@ -0,0 +1,162 @@
+---
+title: What is a lexical model?
+---
+
+
+
+![Smartphone keyboard displaying suggestions for the English phrase ”on my w-”](../../../images/lm/whatis-on-my-w.png)
+
+##### Predictive text for English.
+
+
+
+Many mobile phone keyboards enjoy **predictive text** for their
+languages.
+
+Predictive text is the feature on your keyboard that displays a series
+of _predictions_, typically above your keyboard,
+that try to guess the word or words that you are typing next. For
+example, if I start typing the English phrase “On my w”, your keyboard’s
+predictive text feature will infer, using its knowledge of the English
+language, that the word you are typing is most likely “way”, followed by
+other, less likely suggestions, such as “whole”, or “website”.
+
+The same feature that provides predictive text can also suggest
+_corrections_ to what you are typing. For
+example, if I start typing “thr” on my English keyboard, my keyboard
+will suggest that I meant to type “the” instead. This **autocorrect**
+feature is powered by your keyboard’s knowledge of the current language.
+
+The way your keyboard knows how to suggest **predictions** and
+**corrections** for your language is through its **lexical model**.
+
+
+
+![diagram of the context ”on my w” used as input to the lexical model; the results of the lexical model are a list of suggestions.](../../../images/lm/overview.svg)
+
+##### How the lexical model is involved in generating suggestions.
+
+
+
+## Why should I create a lexical model for my language?
+
+### Words are difficult to type
+
+
+
+![Typing “n-a-i-v” on a smartphone. The keyboard suggests “naïve” for this input.](../../../images/lm/whatis-naiv.png)
+
+##### Typing “naiv” on a smartphone with predictive text.
+
+
+
+Some words have many accents, diacritics, or similar-looking forms. This
+is not very common in the English language, however, this is quite
+common in other languages. Predictive text can recognize forms without
+the correct diacritics, or recognize forms that are simpler to type than
+the orthographically correct version. This allows the typist to type
+words quickly, with minimum effort.
+
+For example, in English, I want to type “naïve”, however, my keyboard
+does not have the ï key present on its main
+layout. Sure, if I use a keyboard that I can press-and-hold the
+i key, it may pop-up additional characters,
+and I can probably find ï nestled there
+among the other options. However, I have to go out of my way to type the
+correct variant, whereas the incorrect variant will be perfectly
+understood. In most cases, I choose the option that is more economical
+to type–“naive”—rather than the “correct” option.
+
+However, a **lexical model** that understands the English language will
+see the word “naive” does not exist, but a similarly typed variant
+“naïve” does exist. Therefore, when I type “naive”, the lexical model
+will suggest “naïve”, and I can select it and have the correctly spelled
+version without having to long-press and select the correct “ï”. Even
+better than that, I can choose the suggestion right after typing “naiv”,
+as there are relatively view English words that start with the prefix of
+“naiv-”
+
+### Words are long
+
+
+
+![The top suggestion shows “incomprehensible”](../../../images/lm/whatis-incompr.png)
+
+Typing “incompr” on an English keyboard.
+
+
+
+Sometimes, the words are very long, but can be typed in far fewer
+keystrokes if predictive text is used. For example, if in English, I
+want to write the word “incomprehensible” (a 16-letter word), a lexical
+model for English can predict it from the prefix “incompr” and save 8
+keystrokes!
+
+English is not prone to extraordinarily long words; the average word
+length in English is about 9 letters. However, languages that are fond
+of compounding, such as German (e.g.,
+_scheinwerferreinigungsanlage_ is a 28-letter word
+which means “headlight washers”), or _polysynthetic
+languages_ in which one word can have the meaning of a complete
+sentence in English, such as Mohawk (e.g.,
+_sahonwanhotónkwahse_ is a 20-letter word which
+means “she
+opened the door for him again”). For these languages, predictive
+text can save even more keystrokes than in English.
+
+### People make mistakes
+
+
+
+![The first suggestion is to write the word “the” instead.](../../../images/lm/whatis-correct-thr.png)
+
+##### Typing “thr” on an English keyboard.
+
+
+
+When typing quickly on a small phone screen, mistakes are inevitable.
+Say I try to write the word “the” on my phone. I press
+t , then h , but
+as I try to type e , I press a few
+millimeters to the right of the intended key and press
+r instead. With a lexical model, the
+predictive text feature understands that in English, “thr” is not a
+complete word in-and-of-itself; however, a word that is typed quite
+similarly, “the”, is a very common word. Therefore, the lexical model
+provides enough information to assume that the user intended to type
+“the” instead of “thr”. Thus, one of its **suggestions** is the
+**correction** of “the” in place of “thr”.
+
+However, the predictive text feature is not overly presumptuous; what if
+the typist really did want to type “thr”? As a result, “thr” is
+suggested as a _keep suggestion_. When the typist
+selects the “keep” suggestion, whatever they had originally typed is
+kept, even if the lexical model suggests what it thinks is a far more
+likely correction.
+
+## What do I need to make my own lexical model?
+
+To make a lexical model, you need some information about your language.
+At bare minimum, you need a list of words in your language. Keyman
+Developer supports importing a word list as a spreadsheet of words in
+your language that you wish to use for predictions and corrections. This
+will create a _word list lexical model_.
+
+If you have such a list of words, you can continue to the
+[tutorial](../tutorial) to create a word list lexical model for your
+language!
+
+However, to make a more accurate lexical model, you will need an idea of
+how to rank suggestions relative to each other. For this, you may extend
+the simple word list with **counts**. Each word has a count of how often
+it has been seen in a representative collection of texts in your
+language. For example, I could download articles from the English
+language Wikipedia, and count how often Wikipedia contributors have
+typed “rule” versus how many times they have typed “rupturewort”. This
+will help us understand how to rank these two suggestions given the
+context of “ru”.
+
+You can extend your spreadsheet with counts (placed in the second
+column) to make a more accurate—and thus more useful—lexical model.
+
+[Next: Developing a lexical model from a word list](../tutorial)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/index.md b/developer/docs/help/guides/lexical-models/tutorial/index.md
new file mode 100644
index 00000000000..d6887656b3f
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/index.md
@@ -0,0 +1,29 @@
+---
+title: Tutorial: Developing a lexical model from a word list
+---
+
+[Step 1: Prerequisites for building a lexical model](step-1)
+
+[Step 2: Creating a lexical model project](step-2)
+
+[Step 3: Get some language data](step-3)
+
+[Step 4: Compile the lexical model](step-4)
+
+## Overview
+
+In this tutorial, will learn how to create a **word list lexical
+model**. If you are not sure what a lexical model is, or why you would
+make one, read [“What is a lexical model?”](../intro) first, then come
+back here to follow this tutorial.
+
+## Let's begin
+
+Let's get started! Move on to the next topic to begin the first step.
+
+At the bottom of each page in the tutorial, will be a link to the next
+step. You can use these links to work your way through the tutorial. You
+may also find links to reference information, which you can select to
+learn more about a particular aspect of creating Keyman keyboards.
+
+[Step 1: Prerequisites for building a lexical model](step-1)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-1.md b/developer/docs/help/guides/lexical-models/tutorial/step-1.md
new file mode 100644
index 00000000000..9bd7f84c86f
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-1.md
@@ -0,0 +1,22 @@
+---
+title: Step 1: Prerequisites for building a lexical model
+---
+
+To prepare a lexical model based on a word list, we will need a few
+things:
+
+- Install [Keyman Developer](https://keyman.com/developer) 12.0 or greater (Windows only)
+- A list of words in your language, stored in an spreadsheet, in dictionary-making software or, other means.
+
+> ### Note
+Currently, lexical models work better with languages that use regular
+spaces as word boundaries. For complex scripts such as Khmer, Thai, Lao,
+etc, a pre-processing is required before getting it to work, i.e. word
+segmentation has to be determined and/or configured. See the advanced
+topic on making a custom [word breaker](../advanced/word-breaker).
+This will be addressed in a future
+[feature](https://github.com/keymanapp/keyman/issues/5025).
+
+Now to create a lexical model project!
+
+[Step 2: Creating a lexical model project](step-2)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-2.md b/developer/docs/help/guides/lexical-models/tutorial/step-2.md
new file mode 100644
index 00000000000..bdfb063ae46
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-2.md
@@ -0,0 +1,115 @@
+---
+title: Step 2: Creating a lexical model project
+---
+
+## Create the new project
+
+Start Keyman Developer. On the “Welcome” screen, click on
+**New Project...**. The “New Project” dialog
+will appear. Select “Wordlist Lexical Model” and press
+**OK**.
+
+
+
+![“New Project” dialog](../../../images/ui/frmNewLMProject.png)
+
+###### The “New Project” dialog, with “Wordlist Lexical Model” selected.
+
+
+
+## Provide required information
+
+
+
+![New LM Project Parameters](../../../images/ui/frmNewLMProjectParameters.png)
+
+###### The New Lexical Model dialog box.
+
+
+
+The “New Wordlist Lexical Model Project” dialog will appear.
+
+To make sharing your lexical model easier, a project needs the following
+information:
+
+Author Name
+: This is either your **full name** or the **organization** you're
+ creating a model for. In this example, I am creating a lexical model
+ on behalf of my organization, the National Research Council Canada,
+ so I write that as the author name.
+
+Model Name
+: We recommend the name of the **language**, **dialect**, or
+ **community** that this model is intended for. The name must be
+ written in all the Latin letters or Arabic numerals. In this
+ example, we're creating a language model for SENĆOŦEN, so we use the
+ model name `Sencoten`.
+
+### Provide auxiliary information
+
+The following information is also required, but most users will use
+**default values**.
+
+Copyright
+: Who owns the rights to this model and its data? This field should
+ contain the word "Copyright", the copyright symbol "©", and the full name of the rights
+ owner. Do not put the year of copyright in this field (see Full Copyright). Typically,
+ you can use the automatically generated default value: *Copyright © **Your Full
+ Name or Your Organization***.
+
+Version
+: If this is the first time you've created a lexical model for you
+ language, you should leave the version as **1.0**. Otherwise, your
+ version number must conform to the following rules: A version string
+ made of `major revision number`.`minor revision number`.
+
+## Determine your language's BCP 47 language tag
+
+Keyman needs to know how to link your model to the appropriate keyboard
+layout, so that they can both work together. To do this, Keyman utilizes
+[BCP 47](../../../reference/bcp-47) language tags.
+
+To add a language tag, click the **Add**
+button to bring up the “Select BCP 47 Tag” dialog box.
+
+
+
+![Select BCP 47 Tag dialog](../../../images/ui/frmNewLMProjectSelectLanguage.png)
+
+###### The “Select BCP 47 Tag” dialog box for SENĆOŦEN.
+
+
+
+Enter the BCP 47 language tag that you have selected. [Learn more about BCP 47 language tags](../../../reference/bcp-47)
+
+Once you are finished adding the primary language, click
+**OK** to return to the **New Lexical Model
+Project dialog**.
+
+## The Model ID
+
+Keyman will create a **model ID** which is how
+Keyman sorts and organizes different lexical models. If you choose to
+share your model publicly, the model ID is vital for both people and
+Keyman to identify and use your lexical model!
+
+Keyman automatically generates a model ID for you, given all the
+information already filled out. If you're satisfied with the generated
+model ID, you can [skip to the next step](#toc-double-check-the-information).
+
+In this example, my generated model ID is
+`national_research_council_canada.str.sencoten`, derived from my
+organization name, the name of the primary language, and my model name.
+However, I find the “author ID” part of the generated model ID
+excessively long. I changed the author ID to `nrc`, and the model ID
+automatically changes to the much more manageable `nrc.str.sencoten`.
+
+## Double-check the information
+
+Verify that all of the information is correct. Once all of the required
+information has been filled in and verified, click
+**OK** to create the project.
+
+Once we have created the project, we can begin to prepare the data!
+
+[Step 3: Get some language data](step-3)
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-3.md b/developer/docs/help/guides/lexical-models/tutorial/step-3.md
new file mode 100644
index 00000000000..88d9af90aec
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-3.md
@@ -0,0 +1,143 @@
+---
+title: Step 3: Get some language data
+---
+
+To predict words in your language, a lexical model needs to know the
+words in your language!
+
+Keyman Developer understands how to read words in a [TSV file](../../../reference/file-types/tsv). This kind of file can be saved
+from a **spreadsheet** application like [Google Sheets](https://sheets.google.com/) or [Microsoft Excel](https://products.office.com/en/excel). Other users may also use
+**language data management software** like [SIL FieldWorks Language
+Explorer (FLEx)](https://software.sil.org/fieldworks/) to export an
+appropriate **TSV** file.
+
+One simple way to create your TSV file is to use the **PrimerPrep**
+tool:
+
+1. Install PrimerPrep (info at
+ )
+2. Run PrimerPrep (note that on the first run it often takes a couple
+ of minutes; subsequent starts are faster)
+3. Click on the Add Text(s) button; select one or more plain text
+ (UTF-8) files in the language to analyze
+4. The word list with frequency counts appears in the pane to the right
+5. From the File menu, select Save Word List… and specify the file name
+ and location (a .tsv extension is recommended)
+
+> **For advanced users**: Ultimately, what Keyman Developer requires is a
+tab-separated values (TSV) file in a specfic format described in the
+[file types reference](../../../reference/file-types/tsv). Refer to this
+reference file if you are coding your own exporter.
+
+## Example Wordlist
+
+I have words in my language of choice, SENĆOŦEN. Here is my list of
+words, with the count of how many times I’ve seen the word:
+
+List of ten SENĆOŦEN words, with counts
+
+
+| Word | Count |
+|-------|-------|
+| TŦE | 13644 |
+| E | 9134 |
+| SEN | 4816 |
+| Ȼ | 3479 |
+| SW̱ | 2621 |
+| NIȽ | 2314 |
+| U¸ | 2298 |
+| I¸ | 1988 |
+| ȻSE | 1925 |
+| I | 1884 |
+
+
+## Editing the .TSV in Keyman Developer
+
+If you plan to only edit a few entries for your wordlist, you can use
+the TSV editor in Keyman Developer. The project template created a
+wordlist named **wordlist.tsv** that we will now edit.
+
+In Keyman Developer project view, select the Models tab and click on
+wordlist.tsv.
+
+
+
+![Open TSV file in Keyman Developer](../../../images/lm/developer-open-tsv.png)
+
+##### Open a .tsv file in Keyman Developer
+
+
+
+Keyman Developer already generated a few example words when it created
+the template wordlist.tsv file.
+
+
+
+![Example wordlist.tsv generated by template](../../../images/lm/template-tsv.png)
+
+##### Template wordlist.tsv
+
+
+
+We will replace these entries with SENĆOŦEN words from our wordlist. For
+each row, edit the "Word Form" and "Count". Counts are optional for each
+word: that is, some words may specify counts in the second column, while
+other words may leave the second column blank. To create a new entry at
+the bottom, click "Add word...". When you are finished, you'll have a
+wordlist that looks like this:
+
+
+
+![Editing wordlist.tsv in Keyman Developer](../../../images/lm/edited-tsv.png)
+
+##### Edited wordlist.tsv for SENĆOŦEN
+
+
+
+After saving your wordlist file, you can move on to Step 4.
+
+## Editing the .TSV in Google Sheets
+
+Alternatively, you may want to use a different spreadsheet tool for
+editing large wordlists. I’ve entered this information into my
+spreadsheet of choice, [Google Sheets](https://sheets.google.com/). I’ve
+shared this spreadsheet publicly
+[here](https://docs.google.com/spreadsheets/d/10zhIc439BCSSooL_-HeJ6TUHd-ovkiXYcIGe-pHDTSg/edit?usp=sharing).
+The order of the columns matters:
+
+The first column (column A) **must** be the “words”. If provided, the
+second column (column B) **must** be the “counts”. Counts are optional
+for each word: that is, some words may specify counts in the second
+column, while other words may leave the second column blank. The third
+column (column C) is always ignored. You may use this column as a
+comment. The spreadsheet can be as simple as a single column of all of
+the words in the language, with each word being separated by a line
+break.
+
+This is what my word list looks like in Google Sheets:
+
+
+
+![screenshot of the word list in Google Sheets](../../../images/lm/sencoten-sheets-full.png)
+
+##### The word list, as it appears in Google Sheets
+
+
+
+Now, we download the spreadsheet in the [required format](../../../reference/file-types/tsv). To do this, in Google
+Sheets, select “File” » “Download as” » “Tab-separated values (.tsv,
+current sheet)”.
+
+
+
+![screenshot of “Save as…” menu in Google Sheets, selecting ”TSV”](../../../images/lm/sencoten-sheets-save-as.png)
+
+##### Exporting the TSV file from Google Sheets
+
+
+
+I’ll save mine as **wordlist.tsv**.
+
+Now that we have our word list, let's compile our model!
+
+[Step 4: Compiling the lexical model](step-4)
\ No newline at end of file
diff --git a/developer/docs/help/guides/lexical-models/tutorial/step-4.md b/developer/docs/help/guides/lexical-models/tutorial/step-4.md
new file mode 100644
index 00000000000..28867a3afc9
--- /dev/null
+++ b/developer/docs/help/guides/lexical-models/tutorial/step-4.md
@@ -0,0 +1,34 @@
+---
+title: Step 4: Compile the lexical model
+---
+
+Before use the lexical model, we must compile it. In this step, the
+`.tsv` wordlist and [the model definition file](../advanced/model-definition-file) get
+_compiled_ into a single `.model.js` file. This
+file is the one that Keyman uses internally to generate suggestions.
+Later on, we will bundle the `.model.js` file into a lexical model
+package so that Keyman apps can install the lexical model.
+
+## Models tab
+
+In Keyman Developer project view, select the "Models" tab.
+
+
+
+
+
+Click on the **Build models** button to
+compile the lexical model. The Message window will display the results
+of the compilation. If you have no typing errors, the lexical model
+should compile successfully. If successful, this will create a
+`.model.js` file in a build subdirectory of the lexical model project
+directory.
+
+## Next steps
+
+This concludes the lexical model tutorial. Here's what's next:
+
+- Now that the model is built, we are ready to [distribute our lexical model](../distribute/packages).
+- (*For advanced users*) We can do some [advanced customization](../advanced/) by modifying the [model definition file](../advanced/model-definition-file). Advanced customizations
+ require a some comfort with modifying code!
\ No newline at end of file
diff --git a/developer/docs/help/guides/test/index.md b/developer/docs/help/guides/test/index.md
new file mode 100644
index 00000000000..4dac01ec933
--- /dev/null
+++ b/developer/docs/help/guides/test/index.md
@@ -0,0 +1,10 @@
+---
+title: Testing keyboards
+---
+
+- [How to test your keyboard layout — touch and
+ desktop](keyboard-touch-and-desktop)
+
+# Testing lexical models
+
+- [How to test your lexical model](lexical-model)
diff --git a/developer/docs/help/guides/test/keyboard-touch-and-desktop.md b/developer/docs/help/guides/test/keyboard-touch-and-desktop.md
new file mode 100644
index 00000000000..9db50afbbbc
--- /dev/null
+++ b/developer/docs/help/guides/test/keyboard-touch-and-desktop.md
@@ -0,0 +1,149 @@
+---
+title: How to test your keyboard layout — touch and desktop
+---
+
+Keyman Developer includes full touch layout editing tools. In the image
+below I am editing a Khmer Angkor touch layout sample.
+
+[![Touch Editor -
+Khmer](../../images/testing/touch-editor-khmer-800wi.png "Touch Editor - Khmer")](../../images/testing/touch-editor-khmer.png)
+
+Once you have created your keyboard layout, you need to test it. Your
+keyboard layout may cover desktops as well as touch devices. The
+**Build** tab of the keyboard editor gives you access to testing and
+debugging commands for all platforms.
+
+[![Touch Editor - Build
+tab](../../images/testing/touch-editor-build-800wi.png "Touch Editor - Build tab")](../../images/testing/touch-editor-build.png)
+
+Testing is easy on Windows: press the **Start Debugging** button on the
+**Build** tab to test the rules in your keyboard layout with a [fully
+interactive debugger](../../context/debug). This allows you to step
+through complex rules, inspecting the contents of the stores referenced
+in each rule, and examine the context, deadkey state and output at each
+point.
+
+After validating your rules, you will want to test the [on screen
+keyboard](../../context/keyboard-editor#toc-on-screen-tab) and how your
+keyboard feels within other applications in Windows. We recommend
+[creating a package](../distribute/tutorial) in order to simplify the
+installation of both the on screen keyboard and the keyboard (which are
+stored as two separate files). Then click the **Install** button in
+**Build** tab of the package wizard and you are done.
+
+## Preparing to test
+
+But how do you test your touch layout on your iPhone, iPad or Android
+device? We've thought about this as well. Keyman Developer includes a
+web server specifically for testing and installing keyboard layouts on
+these devices.
+
+To start the web server and debugging session, on the **Build** tab of
+your keyboard editor, click **Test Keyboard on web** (we may rename
+these buttons in a later release to reflect the cross-platform targets
+here).
+
+You may need to open up the host port on your local computer firewall;
+most firewall software will prompt you at this point. The default port
+that Keyman Developer uses is 8008. You can change this in the
+**Tools**/**Options**/**Debugger** dialog if necessary.
+
+You will see a list of web addresses in the list box below the **Test
+Keyboard on web** label. This lists all the computer and domain names
+and IP addresses that Keyman Developer is listening on that we can find.
+Why so many? Because of the variety of network setups, different users
+will need to use different addresses; Keyman Developer leaves that up to
+you! We call the list the **Debug Host List**.
+
+![KeymanWeb Debug
+Host](../../images/testing/startdebugging-kd10.png "KeymanWeb Debug Host")
+
+## Testing your web keyboard on your desktop
+
+You can test your desktop layout by selecting an address in the debug
+host list and clicking **Open debugger in local browser**. This will
+load the KeymanWeb Debug Host page in your browser, and you should be
+able to select your keyboard from the list and start using it.
+
+## Testing your keyboard on a touch device
+
+To start testing on your mobile device, your device needs to be on the
+same network as your PC. Then open your web browser on the device. Which
+browser? On iOS, use Safari. On Android devices, we recommend using
+Chrome.
+
+Then you will need to pick an address from the debug host list in Keyman
+Developer that your device can find. An IP address on the same network
+is almost always going to work, but you may find that a canonical domain
+name works as well, if you are in an environment with a name server. The
+localhost and 127.0.0.1 addresses are local to your PC -- so don't try
+those.
+
+Enter the address into the URL bar on your browser; don't forget to type
+the **:8008** port at the end of the address you choose (on most devices
+you can skip the **http://** at the start):
+
+Alternatively, you can click the **Send addresses to email...** button.
+It sends a list of the debug host addresses to an email address of your
+choice, so you can then just click the link in your email on your target
+device.
+
+![Enter the debug host
+URL](../../images/testing/frame/android-enter-debug-host.png "Enter the debug host URL")
+
+All going well, you should be presented with a page like this:
+
+![Viewing the debug host on
+iPhone](../../images/testing/frame/android-debug-host.png "Viewing the debug host on iPhone")
+
+If you get a Host Not Found error, try different addresses from the
+**KeymanWeb Debug Host** list, check your computer's firewall settings,
+and make sure that both devices are on the same network.
+
+Now you can instantly test your layout in KeymanWeb Touch, by clicking
+in the blank edit box: the touch layout will appear. If you make changes
+in Keyman Developer, just recompile (**Compile Keyboard** button, or
+F7), and reload on the touch device -- you don't need to click the
+**Test Keyboard on web** button again.
+
+![Testing a keyboard on
+iPhone](../../images/testing/frame/android-debug-keyboard.png "Testing a keyboard on iPhone")
+
+## Loading your keyboard into the native Keyman apps
+
+If you do not have Keyman currently installed on your target device, you
+can click **Install Keyman for Android** or **Install Keyman for iOS**
+to bring up the appropriate store for installing Keyman.
+
+Finally, you can also install your keyboard layout directly into Keyman
+for Android or Keyman for iPhone and iPad by clicking the **Add keyboard
+to Keyman for Android** button or **Add keyboard to Keyman for iOS**
+button on the test page on your device.
+
+![Installing the keyboard into native
+app](../../images/testing/frame/installing-native-keyboard-1.png "Installing the keyboard into native app")
+
+![Installing the keyboard into native
+app](../../images/testing/frame/installing-native-keyboard-2.png "Installing the keyboard into native app")
+
+Now your keyboard layout is installed and available in the Keyman App,
+and if you have the Keyman version with the system keyboard support
+installed, across all apps.
+
+You can test multiple keyboards just by clicking **Test Keyboard on
+web** in each keyboard editor.
+
+## Related articles
+
+Other articles on developing touch layouts:
+
+- [Creating a touch keyboard layout for Amharic - part
+ 1](../develop/creating-a-touch-keyboard-layout-for-amharic)
+- [Creating a touch keyboard layout for Amharic - the nitty
+ gritty](../develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+
+You can distribute your keyboard to other users by following the
+instructions in this article:
+
+- [Distribute keyboards to Keyman
+ applications](../distribute/packages)
diff --git a/developer/docs/help/guides/test/keyboard-touch-mobile-emulator.md b/developer/docs/help/guides/test/keyboard-touch-mobile-emulator.md
new file mode 100644
index 00000000000..fb68b11f2b2
--- /dev/null
+++ b/developer/docs/help/guides/test/keyboard-touch-mobile-emulator.md
@@ -0,0 +1,61 @@
+---
+title: How to test your touch layout in the Google Chrome mobile emulator
+---
+
+This post builds on knowledge in another article about [testing Keyman
+touch layouts](keyboard-touch-and-desktop). Now, we provide an
+alternative test platform for your keyboards, running on your desktop
+computer rather than your touch device.
+
+Google Chrome includes a mobile emulator for web pages, for various
+devices including iPhones, iPads and Androids. Recent builds of
+KeymanWeb work well with this emulator and this provides a quicker and
+easier way, in many cases, to get a feel for the layout of your keyboard
+and do rapid testing.
+
+It is still essential to test your layout on a real device as well as
+the emulator: the feel of clicking on keys with a mouse is very
+different, and gestures such as longpress menus will feel awkward on the
+emulator. We use both native mobile and emulator testing when building
+our layouts, switching between the two frequently. Some of the emulated
+devices emulated may or not work as well a real device.
+
+## Test Process
+
+1. Start testing your keyboard with the [original steps on testing
+ touch keyboards](keyboard-touch-and-desktop).
+ ![](../../images/testing/startdebugging-kd10.png "StartDebugging")
+2. Open Chrome, navigate to the debug host page for your keyboard, and
+ press F12.
+ ![Chrome1](../../images/testing/chrome1.png "Chrome1")
+3. In the Developer Tools window, click the mobile icon:
+ ![Chrome2](../../images/testing/chrome2.png "Chrome2")
+4. Back in the debug host page, select the appropriate device from the
+ mobile emulation toolbar, then press F5 to reload and enable the
+ touch mode. Some recommended devices are iPad or iPad Pro for tablet
+ testing, and iPhone X or Nexus 6P for mobile testing. Do not select
+ "Responsive".
+ ![Chrome3](../../images/testing/chrome3.png "Chrome3")
+5. At this point, you should see a simulation of selected device's
+ screen, and KeymanWeb should be presenting its touch keyboard rather
+ than the desktop equivalent. You'll also see the mouse cursor has
+ turned into a fuzzy circle, to simulate a fingertip instead of a
+ precise arrow.
+ ![Chrome4"](../../images/testing/chrome4.png "Chrome4")
+
+You can now test the keyboard layout and get an idea of how it will feel
+on a real touch device. Rotation, or changing device types will require
+a page load (F5) in order to render correctly.
+
+- [Creating a touch keyboard layout for Amharic - part
+ 1](../develop/creating-a-touch-keyboard-layout-for-amharic)
+- [Creating a touch keyboard layout for Amharic - the nitty
+ gritty](../develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty)
+- [How to test your keyboard layout — touch and
+ desktop](keyboard-touch-and-desktop)
+
+You can distribute your keyboard to other users by following the
+instructions in this article:
+
+- [Distribute keyboards to Keyman
+ applications](../distribute/packages)
diff --git a/developer/docs/help/guides/test/lexical-model.md b/developer/docs/help/guides/test/lexical-model.md
new file mode 100644
index 00000000000..e46d9a262dd
--- /dev/null
+++ b/developer/docs/help/guides/test/lexical-model.md
@@ -0,0 +1,67 @@
+---
+title: How to test your lexical model
+---
+
+It is helpful to be familiar with processes for testing touch keyboards
+before starting to test a lexical model. This tutorial builds on
+knowledge found in the following two articles:
+
+- [Testing keyboard layouts on devices](keyboard-touch-and-desktop)
+
+## Test Process
+
+Lexical models are tested in Keyman Developer's web testing environment.
+They can be loaded on any device that can access the testing web site,
+in the same way as testing a keyboard layout.
+
+A lexical model must be tested in conjunction with a keyboard. The
+keyboard provides the input, and the lexical model will present
+suggestions based on that input. In normal use, a lexical model is
+linked to a keyboard by the BCP 47 language code with which they are
+both registered. However, within the debug environment, any lexical
+model can be tested with any keyboard -- but of course an Arabic lexical
+model will not provide useful suggestions with a Hindi keyboard, so you
+still need a keyboard which will provide sensible input to the model.
+
+There are two ways to load a lexical model in the web-based test
+environment:
+
+1. The first option is to start testing a keyboard as you would, in the
+ web test environment. The keyboard will remain available within the
+ web test environment until Keyman Developer is restarted, even if
+ you switch projects.
+2. The second way is to specify a compiled .js keyboard file for the
+ lexical model test to load when you start testing, in the **Build**
+ tab of the Lexical Model Editor. This keyboard can be selected once
+ and remains saved in your project user settings for future tests.
+
+To start testing a lexical model, follow the steps below:
+
+1. Ensure the model is compiled by pressing F7 within the lexical model
+ editor, or pressing the **Compile Model** button within the
+ **Build** tab.
+2. Prepare the associated keyboard for testing as you normally would,
+ and load it in the web test environment following the instructions
+ above. In the example below, I have selected the fv_sencoten
+ keyboard to use during my lexical model test (you may alternatively
+ choose to load the keyboard project and start testing from there).
+ ![](../../images/lm/test-model.png "model-test-page")
+3. Choose the URL in the web addresses list as shown in the image
+ above. Keyman Developer lists all the addresses on your computer
+ that it can find; some will be addressable by other devices, where
+ as addresses such as 'localhost' or '127.0.0.1' are only visible on
+ the local computer. Typically, any other device must still be on the
+ same network, and you must make sure you allow Keyman Developer
+ through your local computer firewall for port 8008 (the default
+ debugging port).
+4. Once you start testing, you can swap between models from the
+ **Models** menu in the test web page. As you type on the touch
+ keyboard, you should see suggestions change in the banner, and you
+ should be able to select the suggestions. You can also do the same
+ testing in a web browser on a mobile device. Note: you can point
+ your mobile device camera at the QRCode on screen to quickly load
+ the selected URL on your mobile device.
+
+In order to test the lexical model inside Keyman on a mobile device, you
+will need to [prepare the model file for
+distribution](../lexical-models/distribute).
diff --git a/developer/docs/help/images/app/dist-file-browser-ap.png b/developer/docs/help/images/app/dist-file-browser-ap.png
new file mode 100644
index 00000000000..e0957ac4dfe
Binary files /dev/null and b/developer/docs/help/images/app/dist-file-browser-ap.png differ
diff --git a/developer/docs/help/images/app/dist-install1-ap.png b/developer/docs/help/images/app/dist-install1-ap.png
new file mode 100644
index 00000000000..9150ea481a7
Binary files /dev/null and b/developer/docs/help/images/app/dist-install1-ap.png differ
diff --git a/developer/docs/help/images/app/dist-install1-i.png b/developer/docs/help/images/app/dist-install1-i.png
new file mode 100644
index 00000000000..e4632eafc93
Binary files /dev/null and b/developer/docs/help/images/app/dist-install1-i.png differ
diff --git a/developer/docs/help/images/app/dist-kmp-open-i.png b/developer/docs/help/images/app/dist-kmp-open-i.png
new file mode 100644
index 00000000000..a6220a38562
Binary files /dev/null and b/developer/docs/help/images/app/dist-kmp-open-i.png differ
diff --git a/developer/docs/help/images/app/dist-kmp-success-i.png b/developer/docs/help/images/app/dist-kmp-success-i.png
new file mode 100644
index 00000000000..af1108946b9
Binary files /dev/null and b/developer/docs/help/images/app/dist-kmp-success-i.png differ
diff --git a/developer/docs/help/images/app/dist-storage-permission-ap.png b/developer/docs/help/images/app/dist-storage-permission-ap.png
new file mode 100644
index 00000000000..0deb5cf41af
Binary files /dev/null and b/developer/docs/help/images/app/dist-storage-permission-ap.png differ
diff --git a/developer/docs/help/images/app/dist-url-screen-ap.png b/developer/docs/help/images/app/dist-url-screen-ap.png
new file mode 100644
index 00000000000..bf15950a5b1
Binary files /dev/null and b/developer/docs/help/images/app/dist-url-screen-ap.png differ
diff --git a/developer/docs/help/images/app/dist-url-screen-i.png b/developer/docs/help/images/app/dist-url-screen-i.png
new file mode 100644
index 00000000000..1c002964675
Binary files /dev/null and b/developer/docs/help/images/app/dist-url-screen-i.png differ
diff --git a/developer/docs/help/images/app/dist-welcome-ap.png b/developer/docs/help/images/app/dist-welcome-ap.png
new file mode 100644
index 00000000000..39dadf470d2
Binary files /dev/null and b/developer/docs/help/images/app/dist-welcome-ap.png differ
diff --git a/developer/docs/help/images/app/dist-welcome-i.png b/developer/docs/help/images/app/dist-welcome-i.png
new file mode 100644
index 00000000000..cfa0cc725fc
Binary files /dev/null and b/developer/docs/help/images/app/dist-welcome-i.png differ
diff --git a/developer/docs/help/images/app/settings-language-ap.png b/developer/docs/help/images/app/settings-language-ap.png
new file mode 100644
index 00000000000..e6b2ac4c9d0
Binary files /dev/null and b/developer/docs/help/images/app/settings-language-ap.png differ
diff --git a/developer/docs/help/images/dist-file-browser-ap.png b/developer/docs/help/images/dist-file-browser-ap.png
new file mode 100644
index 00000000000..e0957ac4dfe
Binary files /dev/null and b/developer/docs/help/images/dist-file-browser-ap.png differ
diff --git a/developer/docs/help/images/dist-install1-ap.png b/developer/docs/help/images/dist-install1-ap.png
new file mode 100644
index 00000000000..9150ea481a7
Binary files /dev/null and b/developer/docs/help/images/dist-install1-ap.png differ
diff --git a/developer/docs/help/images/dist-install1-i.png b/developer/docs/help/images/dist-install1-i.png
new file mode 100644
index 00000000000..e4632eafc93
Binary files /dev/null and b/developer/docs/help/images/dist-install1-i.png differ
diff --git a/developer/docs/help/images/dist-kmp-open-i.png b/developer/docs/help/images/dist-kmp-open-i.png
new file mode 100644
index 00000000000..a6220a38562
Binary files /dev/null and b/developer/docs/help/images/dist-kmp-open-i.png differ
diff --git a/developer/docs/help/images/dist-kmp-success-i.png b/developer/docs/help/images/dist-kmp-success-i.png
new file mode 100644
index 00000000000..af1108946b9
Binary files /dev/null and b/developer/docs/help/images/dist-kmp-success-i.png differ
diff --git a/developer/docs/help/images/dist-storage-permission-ap.png b/developer/docs/help/images/dist-storage-permission-ap.png
new file mode 100644
index 00000000000..0deb5cf41af
Binary files /dev/null and b/developer/docs/help/images/dist-storage-permission-ap.png differ
diff --git a/developer/docs/help/images/dist-url-screen-ap.png b/developer/docs/help/images/dist-url-screen-ap.png
new file mode 100644
index 00000000000..bf15950a5b1
Binary files /dev/null and b/developer/docs/help/images/dist-url-screen-ap.png differ
diff --git a/developer/docs/help/images/dist-url-screen-i.png b/developer/docs/help/images/dist-url-screen-i.png
new file mode 100644
index 00000000000..1c002964675
Binary files /dev/null and b/developer/docs/help/images/dist-url-screen-i.png differ
diff --git a/developer/docs/help/images/dist-welcome-ap.png b/developer/docs/help/images/dist-welcome-ap.png
new file mode 100644
index 00000000000..8d753979b12
Binary files /dev/null and b/developer/docs/help/images/dist-welcome-ap.png differ
diff --git a/developer/docs/help/images/dist-welcome-i.png b/developer/docs/help/images/dist-welcome-i.png
new file mode 100644
index 00000000000..cfa0cc725fc
Binary files /dev/null and b/developer/docs/help/images/dist-welcome-i.png differ
diff --git a/developer/docs/help/images/ipa.gif b/developer/docs/help/images/ipa.gif
new file mode 100644
index 00000000000..9d861961f17
Binary files /dev/null and b/developer/docs/help/images/ipa.gif differ
diff --git a/developer/docs/help/images/lm/developer-open-tsv.png b/developer/docs/help/images/lm/developer-open-tsv.png
new file mode 100644
index 00000000000..4a13865b76b
Binary files /dev/null and b/developer/docs/help/images/lm/developer-open-tsv.png differ
diff --git a/developer/docs/help/images/lm/edited-tsv.png b/developer/docs/help/images/lm/edited-tsv.png
new file mode 100644
index 00000000000..e60c291eb29
Binary files /dev/null and b/developer/docs/help/images/lm/edited-tsv.png differ
diff --git a/developer/docs/help/images/lm/overview.svg b/developer/docs/help/images/lm/overview.svg
new file mode 100644
index 00000000000..5259abfd5c3
--- /dev/null
+++ b/developer/docs/help/images/lm/overview.svg
@@ -0,0 +1 @@
+Lexical Model knowledge of your language and its spelling rules on my w context way website “w” suggestions
\ No newline at end of file
diff --git a/developer/docs/help/images/lm/sencoten-sheets-full.png b/developer/docs/help/images/lm/sencoten-sheets-full.png
new file mode 100644
index 00000000000..9ac2d487ff6
Binary files /dev/null and b/developer/docs/help/images/lm/sencoten-sheets-full.png differ
diff --git a/developer/docs/help/images/lm/sencoten-sheets-save-as.png b/developer/docs/help/images/lm/sencoten-sheets-save-as.png
new file mode 100644
index 00000000000..dcb879551d7
Binary files /dev/null and b/developer/docs/help/images/lm/sencoten-sheets-save-as.png differ
diff --git a/developer/docs/help/images/lm/template-tsv.png b/developer/docs/help/images/lm/template-tsv.png
new file mode 100644
index 00000000000..d480c29912a
Binary files /dev/null and b/developer/docs/help/images/lm/template-tsv.png differ
diff --git a/developer/docs/help/images/lm/test-model.png b/developer/docs/help/images/lm/test-model.png
new file mode 100644
index 00000000000..740bc648b0c
Binary files /dev/null and b/developer/docs/help/images/lm/test-model.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_3.png b/developer/docs/help/images/lm/tutorial_distribute_model_3.png
new file mode 100644
index 00000000000..2bc7bbad8c4
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_3.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_3_files.png b/developer/docs/help/images/lm/tutorial_distribute_model_3_files.png
new file mode 100644
index 00000000000..417a9dd49e8
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_3_files.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_compile.png b/developer/docs/help/images/lm/tutorial_distribute_model_compile.png
new file mode 100644
index 00000000000..b302e26e9b4
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_compile.png differ
diff --git a/developer/docs/help/images/lm/tutorial_distribute_model_details.png b/developer/docs/help/images/lm/tutorial_distribute_model_details.png
new file mode 100644
index 00000000000..bf506cac906
Binary files /dev/null and b/developer/docs/help/images/lm/tutorial_distribute_model_details.png differ
diff --git a/developer/docs/help/images/lm/whatis-correct-thr.png b/developer/docs/help/images/lm/whatis-correct-thr.png
new file mode 100644
index 00000000000..28bf1fa82c1
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-correct-thr.png differ
diff --git a/developer/docs/help/images/lm/whatis-incompr.png b/developer/docs/help/images/lm/whatis-incompr.png
new file mode 100644
index 00000000000..02d680a7c61
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-incompr.png differ
diff --git a/developer/docs/help/images/lm/whatis-naiv.png b/developer/docs/help/images/lm/whatis-naiv.png
new file mode 100644
index 00000000000..e8061682337
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-naiv.png differ
diff --git a/developer/docs/help/images/lm/whatis-on-my-w.png b/developer/docs/help/images/lm/whatis-on-my-w.png
new file mode 100644
index 00000000000..d01d216ed68
Binary files /dev/null and b/developer/docs/help/images/lm/whatis-on-my-w.png differ
diff --git a/developer/docs/help/images/settings-language-ap.png b/developer/docs/help/images/settings-language-ap.png
new file mode 100644
index 00000000000..e6b2ac4c9d0
Binary files /dev/null and b/developer/docs/help/images/settings-language-ap.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_1.png b/developer/docs/help/images/simpleTouchKeyboard_1.png
new file mode 100644
index 00000000000..1f5610d6541
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_1.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_10.png b/developer/docs/help/images/simpleTouchKeyboard_10.png
new file mode 100644
index 00000000000..460e1dbae97
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_10.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_11.png b/developer/docs/help/images/simpleTouchKeyboard_11.png
new file mode 100644
index 00000000000..25af1e721f0
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_11.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_12-2.png b/developer/docs/help/images/simpleTouchKeyboard_12-2.png
new file mode 100644
index 00000000000..946fc499008
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_12-2.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_12.png b/developer/docs/help/images/simpleTouchKeyboard_12.png
new file mode 100644
index 00000000000..4b945093a14
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_12.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_13.png b/developer/docs/help/images/simpleTouchKeyboard_13.png
new file mode 100644
index 00000000000..5277c515886
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_13.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_14.png b/developer/docs/help/images/simpleTouchKeyboard_14.png
new file mode 100644
index 00000000000..b73483c39b4
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_14.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_15.png b/developer/docs/help/images/simpleTouchKeyboard_15.png
new file mode 100644
index 00000000000..a1b619e318b
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_15.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_15b.png b/developer/docs/help/images/simpleTouchKeyboard_15b.png
new file mode 100644
index 00000000000..86d1c67d9a0
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_15b.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_16.png b/developer/docs/help/images/simpleTouchKeyboard_16.png
new file mode 100644
index 00000000000..4e730d33695
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_16.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_16b.png b/developer/docs/help/images/simpleTouchKeyboard_16b.png
new file mode 100644
index 00000000000..7dd5feab6b9
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_16b.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_17.png b/developer/docs/help/images/simpleTouchKeyboard_17.png
new file mode 100644
index 00000000000..621809ce215
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_17.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_18.png b/developer/docs/help/images/simpleTouchKeyboard_18.png
new file mode 100644
index 00000000000..31c10b444b9
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_18.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_19.png b/developer/docs/help/images/simpleTouchKeyboard_19.png
new file mode 100644
index 00000000000..b547473cf33
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_19.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_2.png b/developer/docs/help/images/simpleTouchKeyboard_2.png
new file mode 100644
index 00000000000..763ca83a0c7
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_2.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_20.png b/developer/docs/help/images/simpleTouchKeyboard_20.png
new file mode 100644
index 00000000000..82d06a8cc05
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_20.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_3.png b/developer/docs/help/images/simpleTouchKeyboard_3.png
new file mode 100644
index 00000000000..6779461e05d
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_3.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_36.png b/developer/docs/help/images/simpleTouchKeyboard_36.png
new file mode 100644
index 00000000000..ae690484cec
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_36.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_4.png b/developer/docs/help/images/simpleTouchKeyboard_4.png
new file mode 100644
index 00000000000..023bd944e55
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_4.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_5.png b/developer/docs/help/images/simpleTouchKeyboard_5.png
new file mode 100644
index 00000000000..7038dc0515c
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_5.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_6.png b/developer/docs/help/images/simpleTouchKeyboard_6.png
new file mode 100644
index 00000000000..85b461e8c4b
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_6.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_7.png b/developer/docs/help/images/simpleTouchKeyboard_7.png
new file mode 100644
index 00000000000..6b2d2c06750
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_7.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_8.png b/developer/docs/help/images/simpleTouchKeyboard_8.png
new file mode 100644
index 00000000000..a161a657c54
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_8.png differ
diff --git a/developer/docs/help/images/simpleTouchKeyboard_9.png b/developer/docs/help/images/simpleTouchKeyboard_9.png
new file mode 100644
index 00000000000..b246c832199
Binary files /dev/null and b/developer/docs/help/images/simpleTouchKeyboard_9.png differ
diff --git a/developer/docs/help/images/testing/chrome1.png b/developer/docs/help/images/testing/chrome1.png
new file mode 100644
index 00000000000..3adbe9cde85
Binary files /dev/null and b/developer/docs/help/images/testing/chrome1.png differ
diff --git a/developer/docs/help/images/testing/chrome2.png b/developer/docs/help/images/testing/chrome2.png
new file mode 100644
index 00000000000..713019ce2c2
Binary files /dev/null and b/developer/docs/help/images/testing/chrome2.png differ
diff --git a/developer/docs/help/images/testing/chrome3.png b/developer/docs/help/images/testing/chrome3.png
new file mode 100644
index 00000000000..76df121c401
Binary files /dev/null and b/developer/docs/help/images/testing/chrome3.png differ
diff --git a/developer/docs/help/images/testing/chrome4.png b/developer/docs/help/images/testing/chrome4.png
new file mode 100644
index 00000000000..8e3d70fc50d
Binary files /dev/null and b/developer/docs/help/images/testing/chrome4.png differ
diff --git a/developer/docs/help/images/testing/frame/android-debug-host.png b/developer/docs/help/images/testing/frame/android-debug-host.png
new file mode 100644
index 00000000000..2783af508c9
Binary files /dev/null and b/developer/docs/help/images/testing/frame/android-debug-host.png differ
diff --git a/developer/docs/help/images/testing/frame/android-debug-keyboard.png b/developer/docs/help/images/testing/frame/android-debug-keyboard.png
new file mode 100644
index 00000000000..a112a881aa9
Binary files /dev/null and b/developer/docs/help/images/testing/frame/android-debug-keyboard.png differ
diff --git a/developer/docs/help/images/testing/frame/android-enter-debug-host.png b/developer/docs/help/images/testing/frame/android-enter-debug-host.png
new file mode 100644
index 00000000000..13070f46800
Binary files /dev/null and b/developer/docs/help/images/testing/frame/android-enter-debug-host.png differ
diff --git a/developer/docs/help/images/testing/frame/installing-native-keyboard-1.png b/developer/docs/help/images/testing/frame/installing-native-keyboard-1.png
new file mode 100644
index 00000000000..b115fdbec82
Binary files /dev/null and b/developer/docs/help/images/testing/frame/installing-native-keyboard-1.png differ
diff --git a/developer/docs/help/images/testing/frame/installing-native-keyboard-2.png b/developer/docs/help/images/testing/frame/installing-native-keyboard-2.png
new file mode 100644
index 00000000000..448c2d20bef
Binary files /dev/null and b/developer/docs/help/images/testing/frame/installing-native-keyboard-2.png differ
diff --git a/developer/docs/help/images/testing/startdebugging-kd10.png b/developer/docs/help/images/testing/startdebugging-kd10.png
new file mode 100644
index 00000000000..197d0b9c7b8
Binary files /dev/null and b/developer/docs/help/images/testing/startdebugging-kd10.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-build-800wi.png b/developer/docs/help/images/testing/touch-editor-build-800wi.png
new file mode 100644
index 00000000000..99a9c4c690a
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-build-800wi.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-build.png b/developer/docs/help/images/testing/touch-editor-build.png
new file mode 100644
index 00000000000..26f1f6ec759
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-build.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-khmer-800wi.png b/developer/docs/help/images/testing/touch-editor-khmer-800wi.png
new file mode 100644
index 00000000000..26ad7b4936f
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-khmer-800wi.png differ
diff --git a/developer/docs/help/images/testing/touch-editor-khmer.png b/developer/docs/help/images/testing/touch-editor-khmer.png
new file mode 100644
index 00000000000..5658e0f9f73
Binary files /dev/null and b/developer/docs/help/images/testing/touch-editor-khmer.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard.png b/developer/docs/help/images/touch_amharic_keyboard.png
new file mode 100644
index 00000000000..57a9693a1bc
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_2.png b/developer/docs/help/images/touch_amharic_keyboard_2.png
new file mode 100644
index 00000000000..a0e2fcb3764
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_2.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_3.png b/developer/docs/help/images/touch_amharic_keyboard_3.png
new file mode 100644
index 00000000000..bf6bc5a4091
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_3.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_4.png b/developer/docs/help/images/touch_amharic_keyboard_4.png
new file mode 100644
index 00000000000..689ab500a64
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_4.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_5.png b/developer/docs/help/images/touch_amharic_keyboard_5.png
new file mode 100644
index 00000000000..1d06fc3ef3f
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_5.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_6.png b/developer/docs/help/images/touch_amharic_keyboard_6.png
new file mode 100644
index 00000000000..a9979426b6a
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_6.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_7.png b/developer/docs/help/images/touch_amharic_keyboard_7.png
new file mode 100644
index 00000000000..454a73bf200
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_7.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_8.png b/developer/docs/help/images/touch_amharic_keyboard_8.png
new file mode 100644
index 00000000000..2c7e5a297cf
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_8.png differ
diff --git a/developer/docs/help/images/touch_amharic_keyboard_9.png b/developer/docs/help/images/touch_amharic_keyboard_9.png
new file mode 100644
index 00000000000..205e428d73f
Binary files /dev/null and b/developer/docs/help/images/touch_amharic_keyboard_9.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_3.png b/developer/docs/help/images/tutorial_distribute_keyboard_3.png
new file mode 100644
index 00000000000..33e7cdf336a
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_3.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_3_files.png b/developer/docs/help/images/tutorial_distribute_keyboard_3_files.png
new file mode 100644
index 00000000000..561931522f3
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_3_files.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_compile.png b/developer/docs/help/images/tutorial_distribute_keyboard_compile.png
new file mode 100644
index 00000000000..865fa186e55
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_compile.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_details.png b/developer/docs/help/images/tutorial_distribute_keyboard_details.png
new file mode 100644
index 00000000000..08dd9ec0123
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_details.png differ
diff --git a/developer/docs/help/images/tutorial_distribute_keyboard_shortcuts.png b/developer/docs/help/images/tutorial_distribute_keyboard_shortcuts.png
new file mode 100644
index 00000000000..8084a3797a4
Binary files /dev/null and b/developer/docs/help/images/tutorial_distribute_keyboard_shortcuts.png differ
diff --git a/developer/docs/help/images/tutorial_keyboard_qfrench.gif b/developer/docs/help/images/tutorial_keyboard_qfrench.gif
new file mode 100644
index 00000000000..99e274b4548
Binary files /dev/null and b/developer/docs/help/images/tutorial_keyboard_qfrench.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includesdocs.gif b/developer/docs/help/images/tutorial_package_includesdocs.gif
new file mode 100644
index 00000000000..6eebbb9fc0c
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includesdocs.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includesfonts.gif b/developer/docs/help/images/tutorial_package_includesfonts.gif
new file mode 100644
index 00000000000..cbeca7bd533
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includesfonts.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includesosk.gif b/developer/docs/help/images/tutorial_package_includesosk.gif
new file mode 100644
index 00000000000..5db43bf1b95
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includesosk.gif differ
diff --git a/developer/docs/help/images/tutorial_package_includeswelcome.gif b/developer/docs/help/images/tutorial_package_includeswelcome.gif
new file mode 100644
index 00000000000..9313bd04812
Binary files /dev/null and b/developer/docs/help/images/tutorial_package_includeswelcome.gif differ
diff --git a/developer/docs/help/images/ui/BuildTab.png b/developer/docs/help/images/ui/BuildTab.png
new file mode 100644
index 00000000000..5fe027dcc69
Binary files /dev/null and b/developer/docs/help/images/ui/BuildTab.png differ
diff --git a/developer/docs/help/images/ui/Debug_Menu.png b/developer/docs/help/images/ui/Debug_Menu.png
new file mode 100644
index 00000000000..61e749cfa6d
Binary files /dev/null and b/developer/docs/help/images/ui/Debug_Menu.png differ
diff --git a/developer/docs/help/images/ui/Debug_Toolbar.png b/developer/docs/help/images/ui/Debug_Toolbar.png
new file mode 100644
index 00000000000..0fc598f2f8a
Binary files /dev/null and b/developer/docs/help/images/ui/Debug_Toolbar.png differ
diff --git a/developer/docs/help/images/ui/LongPress_Flicks_Multitaps.png b/developer/docs/help/images/ui/LongPress_Flicks_Multitaps.png
new file mode 100644
index 00000000000..ba398273d1e
Binary files /dev/null and b/developer/docs/help/images/ui/LongPress_Flicks_Multitaps.png differ
diff --git a/developer/docs/help/images/ui/NGROK_Phone.png b/developer/docs/help/images/ui/NGROK_Phone.png
new file mode 100644
index 00000000000..b33ef1e3166
Binary files /dev/null and b/developer/docs/help/images/ui/NGROK_Phone.png differ
diff --git a/developer/docs/help/images/ui/OnScreenKeyboard.png b/developer/docs/help/images/ui/OnScreenKeyboard.png
new file mode 100644
index 00000000000..cb827878b01
Binary files /dev/null and b/developer/docs/help/images/ui/OnScreenKeyboard.png differ
diff --git a/developer/docs/help/images/ui/TouchLayout_Design.png b/developer/docs/help/images/ui/TouchLayout_Design.png
new file mode 100644
index 00000000000..3e073e812ff
Binary files /dev/null and b/developer/docs/help/images/ui/TouchLayout_Design.png differ
diff --git a/developer/docs/help/images/ui/TouchLayout_Design_2.png b/developer/docs/help/images/ui/TouchLayout_Design_2.png
new file mode 100644
index 00000000000..6bae06255f9
Binary files /dev/null and b/developer/docs/help/images/ui/TouchLayout_Design_2.png differ
diff --git a/developer/docs/help/images/ui/frmAboutTike.png b/developer/docs/help/images/ui/frmAboutTike.png
new file mode 100644
index 00000000000..240f1ce87b0
Binary files /dev/null and b/developer/docs/help/images/ui/frmAboutTike.png differ
diff --git a/developer/docs/help/images/ui/frmCharacterMapFilter.png b/developer/docs/help/images/ui/frmCharacterMapFilter.png
new file mode 100644
index 00000000000..52efac91d70
Binary files /dev/null and b/developer/docs/help/images/ui/frmCharacterMapFilter.png differ
diff --git a/developer/docs/help/images/ui/frmCharacterMapNew.png b/developer/docs/help/images/ui/frmCharacterMapNew.png
new file mode 100644
index 00000000000..4a933022f75
Binary files /dev/null and b/developer/docs/help/images/ui/frmCharacterMapNew.png differ
diff --git a/developer/docs/help/images/ui/frmDebug.png b/developer/docs/help/images/ui/frmDebug.png
new file mode 100644
index 00000000000..1aa67a4036a
Binary files /dev/null and b/developer/docs/help/images/ui/frmDebug.png differ
diff --git a/developer/docs/help/images/ui/frmEditLanguageExample.png b/developer/docs/help/images/ui/frmEditLanguageExample.png
new file mode 100644
index 00000000000..352da90d8a9
Binary files /dev/null and b/developer/docs/help/images/ui/frmEditLanguageExample.png differ
diff --git a/developer/docs/help/images/ui/frmKeyTest.png b/developer/docs/help/images/ui/frmKeyTest.png
new file mode 100644
index 00000000000..583bc16793c
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeyTest.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_CallStack.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_CallStack.png
new file mode 100644
index 00000000000..aff2c122322
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_CallStack.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_Deadkeys.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Deadkeys.png
new file mode 100644
index 00000000000..4b48d4f61f3
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Deadkeys.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_Elements.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Elements.png
new file mode 100644
index 00000000000..fc78a911958
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_Elements.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_RegressionTest.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_RegressionTest.png
new file mode 100644
index 00000000000..bebe3ac569c
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_RegressionTest.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Debug_State.png b/developer/docs/help/images/ui/frmKeymanWizard_Debug_State.png
new file mode 100644
index 00000000000..d14ed69a4f3
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Debug_State.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Details.png b/developer/docs/help/images/ui/frmKeymanWizard_Details.png
new file mode 100644
index 00000000000..d21db5f87f6
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Details.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Icon.png b/developer/docs/help/images/ui/frmKeymanWizard_Icon.png
new file mode 100644
index 00000000000..92a621ac93f
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Icon.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Layout_Code.png b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Code.png
new file mode 100644
index 00000000000..2fbe3991e59
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Code.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_Layout_Design.png b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Design.png
new file mode 100644
index 00000000000..5d3b570555b
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_Layout_Design.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_New.png b/developer/docs/help/images/ui/frmKeymanWizard_New.png
new file mode 100644
index 00000000000..7fdb5ac14a0
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_New.png differ
diff --git a/developer/docs/help/images/ui/frmKeymanWizard_TouchLayout_Code.png b/developer/docs/help/images/ui/frmKeymanWizard_TouchLayout_Code.png
new file mode 100644
index 00000000000..b20f0bd2852
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeymanWizard_TouchLayout_Code.png differ
diff --git a/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png b/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png
new file mode 100644
index 00000000000..6f0c8313085
Binary files /dev/null and b/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png differ
diff --git a/developer/docs/help/images/ui/frmLDMLEditor.png b/developer/docs/help/images/ui/frmLDMLEditor.png
new file mode 100644
index 00000000000..c3f887ab641
Binary files /dev/null and b/developer/docs/help/images/ui/frmLDMLEditor.png differ
diff --git a/developer/docs/help/images/ui/frmList_Local_URLs.png b/developer/docs/help/images/ui/frmList_Local_URLs.png
new file mode 100644
index 00000000000..18917dfc8f8
Binary files /dev/null and b/developer/docs/help/images/ui/frmList_Local_URLs.png differ
diff --git a/developer/docs/help/images/ui/frmMessages.png b/developer/docs/help/images/ui/frmMessages.png
new file mode 100644
index 00000000000..377e9290344
Binary files /dev/null and b/developer/docs/help/images/ui/frmMessages.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Build.png b/developer/docs/help/images/ui/frmModelEditor_Build.png
new file mode 100644
index 00000000000..b437544518f
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Build.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Details.png b/developer/docs/help/images/ui/frmModelEditor_Details.png
new file mode 100644
index 00000000000..a9eb7163c8c
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Details.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Source.png b/developer/docs/help/images/ui/frmModelEditor_Source.png
new file mode 100644
index 00000000000..d887196e510
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Source.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Wordlist_Code.png b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Code.png
new file mode 100644
index 00000000000..f853f76fc36
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Code.png differ
diff --git a/developer/docs/help/images/ui/frmModelEditor_Wordlist_Design.png b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Design.png
new file mode 100644
index 00000000000..d0770cd804e
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelEditor_Wordlist_Design.png differ
diff --git a/developer/docs/help/images/ui/frmModelsCompile.png b/developer/docs/help/images/ui/frmModelsCompile.png
new file mode 100644
index 00000000000..d02fdf1eba9
Binary files /dev/null and b/developer/docs/help/images/ui/frmModelsCompile.png differ
diff --git a/developer/docs/help/images/ui/frmNGROK_Config.png b/developer/docs/help/images/ui/frmNGROK_Config.png
new file mode 100644
index 00000000000..ffbbecef230
Binary files /dev/null and b/developer/docs/help/images/ui/frmNGROK_Config.png differ
diff --git a/developer/docs/help/images/ui/frmNGROK_Server_1.png b/developer/docs/help/images/ui/frmNGROK_Server_1.png
new file mode 100644
index 00000000000..e617f9313dd
Binary files /dev/null and b/developer/docs/help/images/ui/frmNGROK_Server_1.png differ
diff --git a/developer/docs/help/images/ui/frmNGROK_Server_2.png b/developer/docs/help/images/ui/frmNGROK_Server_2.png
new file mode 100644
index 00000000000..b8b3e571d9d
Binary files /dev/null and b/developer/docs/help/images/ui/frmNGROK_Server_2.png differ
diff --git a/developer/docs/help/images/ui/frmNew.png b/developer/docs/help/images/ui/frmNew.png
new file mode 100644
index 00000000000..6748baf7d82
Binary files /dev/null and b/developer/docs/help/images/ui/frmNew.png differ
diff --git a/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png b/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png
new file mode 100644
index 00000000000..6002ea9943d
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png differ
diff --git a/developer/docs/help/images/ui/frmNewLMProject.png b/developer/docs/help/images/ui/frmNewLMProject.png
new file mode 100644
index 00000000000..6e1b096f51d
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLMProject.png differ
diff --git a/developer/docs/help/images/ui/frmNewLMProjectParameters.png b/developer/docs/help/images/ui/frmNewLMProjectParameters.png
new file mode 100644
index 00000000000..d58b569064c
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLMProjectParameters.png differ
diff --git a/developer/docs/help/images/ui/frmNewLMProjectSelectLanguage.png b/developer/docs/help/images/ui/frmNewLMProjectSelectLanguage.png
new file mode 100644
index 00000000000..8767a2d2670
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewLMProjectSelectLanguage.png differ
diff --git a/developer/docs/help/images/ui/frmNewProject.png b/developer/docs/help/images/ui/frmNewProject.png
new file mode 100644
index 00000000000..8fc13cd763f
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewProject.png differ
diff --git a/developer/docs/help/images/ui/frmNewProjectParameters.png b/developer/docs/help/images/ui/frmNewProjectParameters.png
new file mode 100644
index 00000000000..df93ecac2f2
Binary files /dev/null and b/developer/docs/help/images/ui/frmNewProjectParameters.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_CharacterMap.png b/developer/docs/help/images/ui/frmOptions_CharacterMap.png
new file mode 100644
index 00000000000..75c8fc38433
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_CharacterMap.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_Debugger.png b/developer/docs/help/images/ui/frmOptions_Debugger.png
new file mode 100644
index 00000000000..adb2f56c20f
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_Debugger.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_Editor.png b/developer/docs/help/images/ui/frmOptions_Editor.png
new file mode 100644
index 00000000000..ae8d858d30c
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_Editor.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_General.png b/developer/docs/help/images/ui/frmOptions_General.png
new file mode 100644
index 00000000000..514c04a5f71
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_General.png differ
diff --git a/developer/docs/help/images/ui/frmOptions_Server.png b/developer/docs/help/images/ui/frmOptions_Server.png
new file mode 100644
index 00000000000..47b4beb8193
Binary files /dev/null and b/developer/docs/help/images/ui/frmOptions_Server.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Build.png b/developer/docs/help/images/ui/frmPackageEditor_Build.png
new file mode 100644
index 00000000000..953f1b60662
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Build.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Details.png b/developer/docs/help/images/ui/frmPackageEditor_Details.png
new file mode 100644
index 00000000000..be071b7e294
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Details.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_EditExample.png b/developer/docs/help/images/ui/frmPackageEditor_EditExample.png
new file mode 100644
index 00000000000..96c70237a9a
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_EditExample.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_EditRelatedPackage.png b/developer/docs/help/images/ui/frmPackageEditor_EditRelatedPackage.png
new file mode 100644
index 00000000000..977fb822432
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_EditRelatedPackage.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Files.png b/developer/docs/help/images/ui/frmPackageEditor_Files.png
new file mode 100644
index 00000000000..6b9b6a4cef4
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Files.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Keyboards.png b/developer/docs/help/images/ui/frmPackageEditor_Keyboards.png
new file mode 100644
index 00000000000..fc252413d57
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Keyboards.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_SelectWebFonts.png b/developer/docs/help/images/ui/frmPackageEditor_SelectWebFonts.png
new file mode 100644
index 00000000000..64d284dc458
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_SelectWebFonts.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Select_BCP_47_Tag.png b/developer/docs/help/images/ui/frmPackageEditor_Select_BCP_47_Tag.png
new file mode 100644
index 00000000000..5895b452901
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Select_BCP_47_Tag.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Shortcuts.png b/developer/docs/help/images/ui/frmPackageEditor_Shortcuts.png
new file mode 100644
index 00000000000..e9fe8c2fc45
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Shortcuts.png differ
diff --git a/developer/docs/help/images/ui/frmPackageEditor_Source.png b/developer/docs/help/images/ui/frmPackageEditor_Source.png
new file mode 100644
index 00000000000..3ab4d8406f6
Binary files /dev/null and b/developer/docs/help/images/ui/frmPackageEditor_Source.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Distribution.png b/developer/docs/help/images/ui/frmProject_Distribution.png
new file mode 100644
index 00000000000..5f05db0747e
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Distribution.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Keyboards.png b/developer/docs/help/images/ui/frmProject_Keyboards.png
new file mode 100644
index 00000000000..7d9e6ef36e0
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Keyboards.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Packaging.png b/developer/docs/help/images/ui/frmProject_Packaging.png
new file mode 100644
index 00000000000..6b7db832371
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Packaging.png differ
diff --git a/developer/docs/help/images/ui/frmProject_Welcome.png b/developer/docs/help/images/ui/frmProject_Welcome.png
new file mode 100644
index 00000000000..7b899c4fe6e
Binary files /dev/null and b/developer/docs/help/images/ui/frmProject_Welcome.png differ
diff --git a/developer/docs/help/images/ui/obj.gif b/developer/docs/help/images/ui/obj.gif
new file mode 100644
index 00000000000..455e0b330a9
Binary files /dev/null and b/developer/docs/help/images/ui/obj.gif differ
diff --git a/developer/docs/help/index.md b/developer/docs/help/index.md
new file mode 100644
index 00000000000..9e9c250abb3
--- /dev/null
+++ b/developer/docs/help/index.md
@@ -0,0 +1,38 @@
+---
+title: Keyman Developer 18.0 User Guide
+---
+
+Need help using Keyman Developer to create your keyboard layouts? You'll
+find everything you need here, including product documentation, guides
+and tutorials, and full reference information.
+
+## Guides and Tutorials
+- [What is Keyman Developer?](guides/intro)
+- [What's new in 18.0](whats-new)
+- [Developing Keyman keyboard layouts](guides/develop)
+- [Testing Keyman keyboards](guides/test)
+- [Distributing Keyman keyboards](guides/distribute)
+- [What is a lexical model?](guides/lexical-models)
+- [Developing a lexical model](guides/lexical-models/tutorial)
+
+## Reference
+- [Keyman keyboard language reference](../language/reference)
+- [Keyman keyboard language guide](../language/guide)
+- [Customising Keyman Engine for Desktop](../engine/desktop)
+- [Error codes, File types and Tools](reference/)
+- [Keyman Developer user interface](context/)
+- [Additional Notes](main/)
+- [Version History](../version-history/)
+
+## Ask for Help
+- [Ask other users in the SIL Keyman Community](https://community.software.sil.org/c/keyman)
+
+## Other Resources
+
+- [Keyboard Quality White Paper](/developer/whitepaper1.1.pdf)
+- [Knowledge Base](/kb)
+- [Keyman Desktop Help](/products/desktop)
+- [Keyman Engine for Desktop](/developer/engine/desktop/current-version/)
+- [Keyman Engine for Web](/developer/engine/web/)
+- [Keyman Engine for iPhone and iPad](/developer/engine/iphone-and-ipad/current-version/)
+- [Keyman Engine for Android](/developer/engine/android/current-version/)
\ No newline at end of file
diff --git a/developer/docs/help/main/credits.md b/developer/docs/help/main/credits.md
new file mode 100644
index 00000000000..f2c8a027c5b
--- /dev/null
+++ b/developer/docs/help/main/credits.md
@@ -0,0 +1,7 @@
+---
+title: Additional Credits
+---
+
+## Code2000, Code2001 and Code2002 fonts
+
+© 2006 James Kass.
diff --git a/developer/docs/help/main/index.md b/developer/docs/help/main/index.md
new file mode 100644
index 00000000000..6e309aa46e0
--- /dev/null
+++ b/developer/docs/help/main/index.md
@@ -0,0 +1,9 @@
+---
+title: Keyman Developer Additional Notes
+---
+
+[Keyman Developer Version History](../../version-history)
+
+[Keyman® Developer End User Licence and Services Agreement](license)
+
+[Additional Credits](credits)
diff --git a/developer/docs/help/main/license.md b/developer/docs/help/main/license.md
new file mode 100644
index 00000000000..73b788f9000
--- /dev/null
+++ b/developer/docs/help/main/license.md
@@ -0,0 +1,26 @@
+---
+title: Keyman® Developer License
+---
+
+MIT License
+
+Copyright (c) 1994-2018 SIL International
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/developer/docs/help/reference/api/index.md b/developer/docs/help/reference/api/index.md
new file mode 100644
index 00000000000..caac7457fed
--- /dev/null
+++ b/developer/docs/help/reference/api/index.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md)
+
+## API Reference
+
+## Packages
+
+| Package | Description |
+| --- | --- |
+| [@keymanapp/kmc-analyze](./kmc-analyze.md) | kmc-analyze - keyboard analysis classes, including tools for &displayMap
. |
+| [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) | |
+| [@keymanapp/kmc-kmn](./kmc-kmn.md) | kmc-kmn - Keyman keyboard compiler |
+| [@keymanapp/kmc-ldml](./kmc-ldml.md) | |
+| [@keymanapp/kmc-model](./kmc-model.md) | |
+| [@keymanapp/kmc-model-info](./kmc-model-info.md) | |
+| [@keymanapp/kmc-package](./kmc-package.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse._constructor_.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse._constructor_.md
new file mode 100644
index 00000000000..b2270b6c17b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse._constructor_.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [(constructor)](./kmc-analyze.analyzeoskcharacteruse._constructor_.md)
+
+## AnalyzeOskCharacterUse.(constructor)
+
+Constructs a new instance of the `AnalyzeOskCharacterUse` class
+
+**Signature:**
+
+```typescript
+constructor(callbacks: CompilerCallbacks, options?: AnalyzeOskCharacterUseOptions);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | |
+| options | [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) | _(Optional)_ |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.analyze.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.analyze.md
new file mode 100644
index 00000000000..b4e8732921d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.analyze.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [analyze](./kmc-analyze.analyzeoskcharacteruse.analyze.md)
+
+## AnalyzeOskCharacterUse.analyze() method
+
+Analyzes a single source file for Unicode character usage. Can parse .kmn, .kvks, .keyman-touch-layout file formats. Can be called multiple times to collect results from more than one file. Use [AnalyzeOskCharacterUse.getStrings()](./kmc-analyze.analyzeoskcharacteruse.getstrings.md) to retrieve results.
+
+Note: `analyze()` collects key cap data, so calling this for a .kmn file will retrieve the key caps from the .kvks and .keyman-touch-layout files that it references, rather than key cap data from the .kmn file itself.
+
+**Signature:**
+
+```typescript
+analyze(file: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| file | string | relative or absolute path to a Keyman source file |
+
+**Returns:**
+
+Promise<boolean>
+
+true if the file is successfully loaded and parsed
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.clear.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.clear.md
new file mode 100644
index 00000000000..0c8cee7b99f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.clear.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [clear](./kmc-analyze.analyzeoskcharacteruse.clear.md)
+
+## AnalyzeOskCharacterUse.clear() method
+
+Clears analysis data collected from previous calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md)
+
+**Signature:**
+
+```typescript
+clear(): void;
+```
+**Returns:**
+
+void
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.getstrings.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.getstrings.md
new file mode 100644
index 00000000000..c01f1a76d9f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.getstrings.md
@@ -0,0 +1,34 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) > [getStrings](./kmc-analyze.analyzeoskcharacteruse.getstrings.md)
+
+## AnalyzeOskCharacterUse.getStrings() method
+
+Returns the collected results from earlier calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md). This generates a mapping from a key cap (one or more characters) to a PUA code, for use with `&displayMap`.
+
+Three output formats are supported:
+
+- .txt: tab-separated string format, with three columns: PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
+
+- .md: formatted for documentation purposes. Generates a Markdown table (GFM) with PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
+
+- .json: returns the final aggregated data as an array of strings, which can be joined to form a JSON blob of an object with a single member, `map`, which is an array of objects.
+
+**Signature:**
+
+```typescript
+getStrings(format?: '.txt' | '.md' | '.json'): string[];
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| format | '.txt' \| '.md' \| '.json' | _(Optional)_ file format to return - can be '.txt', '.md', or '.json' |
+
+**Returns:**
+
+string\[\]
+
+an array of strings, formatted according to the `format` parameter.
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.md
new file mode 100644
index 00000000000..72302bc73c4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruse.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md)
+
+## AnalyzeOskCharacterUse class
+
+Analyze the characters used in On Screen Keyboard files (.kvks, .keyman-touch-layout) for use with `&displayMap`.
+
+**Signature:**
+
+```typescript
+export declare class AnalyzeOskCharacterUse
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(callbacks, options)](./kmc-analyze.analyzeoskcharacteruse._constructor_.md) | | Constructs a new instance of the AnalyzeOskCharacterUse
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [analyze(file)](./kmc-analyze.analyzeoskcharacteruse.analyze.md) | | Analyzes a single source file for Unicode character usage. Can parse .kmn, .kvks, .keyman-touch-layout file formats. Can be called multiple times to collect results from more than one file. Use [AnalyzeOskCharacterUse.getStrings()](./kmc-analyze.analyzeoskcharacteruse.getstrings.md) to retrieve results.
Note: analyze()
collects key cap data, so calling this for a .kmn file will retrieve the key caps from the .kvks and .keyman-touch-layout files that it references, rather than key cap data from the .kmn file itself.
|
+| [clear()](./kmc-analyze.analyzeoskcharacteruse.clear.md) | | Clears analysis data collected from previous calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md) |
+| [getStrings(format)](./kmc-analyze.analyzeoskcharacteruse.getstrings.md) | | Returns the collected results from earlier calls to [AnalyzeOskCharacterUse.analyze()](./kmc-analyze.analyzeoskcharacteruse.analyze.md). This generates a mapping from a key cap (one or more characters) to a PUA code, for use with &displayMap
.
Three output formats are supported:
- .txt: tab-separated string format, with three columns: PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
- .md: formatted for documentation purposes. Generates a Markdown table (GFM) with PUA, Key Cap, and plain string. The PUA and Key Cap columns are formatted as Unicode Scalar Values, e.g. U+0061, and the plain string is the original key cap string.
- .json: returns the final aggregated data as an array of strings, which can be joined to form a JSON blob of an object with a single member, map
, which is an array of objects.
|
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md
new file mode 100644
index 00000000000..e105b24b29d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) > [includeCounts](./kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md)
+
+## AnalyzeOskCharacterUseOptions.includeCounts property
+
+If true, reports number of references to each character found in each source file
+
+**Signature:**
+
+```typescript
+includeCounts?: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.md
new file mode 100644
index 00000000000..cfaa60ffb34
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md)
+
+## AnalyzeOskCharacterUseOptions interface
+
+Options for character analysis
+
+**Signature:**
+
+```typescript
+export interface AnalyzeOskCharacterUseOptions
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [includeCounts?](./kmc-analyze.analyzeoskcharacteruseoptions.includecounts.md) | | boolean | _(Optional)_ If true, reports number of references to each character found in each source file |
+| [puaBase?](./kmc-analyze.analyzeoskcharacteruseoptions.puabase.md) | | number | _(Optional)_ First character to use in PUA for remapping with &displayMap, defaults to U+F100 |
+| [stripDottedCircle?](./kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md) | | boolean | _(Optional)_ If true, strips U+25CC from the key cap before further analysis |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.puabase.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.puabase.md
new file mode 100644
index 00000000000..e1ff19b2184
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.puabase.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) > [puaBase](./kmc-analyze.analyzeoskcharacteruseoptions.puabase.md)
+
+## AnalyzeOskCharacterUseOptions.puaBase property
+
+First character to use in PUA for remapping with &displayMap, defaults to U+F100
+
+**Signature:**
+
+```typescript
+puaBase?: number;
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md
new file mode 100644
index 00000000000..db975bf710d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) > [stripDottedCircle](./kmc-analyze.analyzeoskcharacteruseoptions.stripdottedcircle.md)
+
+## AnalyzeOskCharacterUseOptions.stripDottedCircle property
+
+If true, strips U+25CC from the key cap before further analysis
+
+**Signature:**
+
+```typescript
+stripDottedCircle?: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua._constructor_.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua._constructor_.md
new file mode 100644
index 00000000000..a0d5f23e35c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [(constructor)](./kmc-analyze.analyzeoskrewritepua._constructor_.md)
+
+## AnalyzeOskRewritePua.(constructor)
+
+Constructs a new instance of the `AnalyzeOskRewritePua` class
+
+**Signature:**
+
+```typescript
+constructor(callbacks: CompilerCallbacks);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.analyze.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.analyze.md
new file mode 100644
index 00000000000..7cdaa00da60
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.analyze.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [analyze](./kmc-analyze.analyzeoskrewritepua.analyze.md)
+
+## AnalyzeOskRewritePua.analyze() method
+
+Analyze a keyboard file or files, and provide a remapped output. Accepts a .kmn, .kvks, .keyman-touch-layout file formats. For .kmn, will rewrite associated On Screen Keyboard file formats. Can be called multiple times to rewrite multiple files. Use the [AnalyzeOskRewritePua.data](./kmc-analyze.analyzeoskrewritepua.data.md) property to retrieve the output file content for writing. This does not modify the source file.
+
+**Signature:**
+
+```typescript
+analyze(file: string, mapping: Osk.StringResult[]): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| file | string | relative or absolute path to a Keyman source file |
+| mapping | Osk.StringResult\[\] | OSK keycap map provided by [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) |
+
+**Returns:**
+
+Promise<boolean>
+
+true if the file is successfully loaded and rewritten
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.clear.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.clear.md
new file mode 100644
index 00000000000..001d0c494b2
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.clear.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [clear](./kmc-analyze.analyzeoskrewritepua.clear.md)
+
+## AnalyzeOskRewritePua.clear() method
+
+Clears data collected from previous calls to [AnalyzeOskRewritePua.analyze()](./kmc-analyze.analyzeoskrewritepua.analyze.md)
+
+**Signature:**
+
+```typescript
+clear(): void;
+```
+**Returns:**
+
+void
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.data.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.data.md
new file mode 100644
index 00000000000..9e3091ca7cb
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.data.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) > [data](./kmc-analyze.analyzeoskrewritepua.data.md)
+
+## AnalyzeOskRewritePua.data property
+
+Returns the file data for OSK files rewritten with PUA characters, for use with `&displayMap`.
+
+**Signature:**
+
+```typescript
+get data(): {
+ [index: string]: Uint8Array;
+ };
+```
diff --git a/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.md b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.md
new file mode 100644
index 00000000000..731b90bfb64
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.analyzeoskrewritepua.md
@@ -0,0 +1,33 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md) > [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md)
+
+## AnalyzeOskRewritePua class
+
+Rewrite On Screen Keyboard files (.kvks, .keyman-touch-layout) with PUA codepoints, based on analysis provided by [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) class.
+
+**Signature:**
+
+```typescript
+export declare class AnalyzeOskRewritePua
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(callbacks)](./kmc-analyze.analyzeoskrewritepua._constructor_.md) | | Constructs a new instance of the AnalyzeOskRewritePua
class |
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [data](./kmc-analyze.analyzeoskrewritepua.data.md) | readonly
| { \[index: string\]: Uint8Array; } | Returns the file data for OSK files rewritten with PUA characters, for use with &displayMap
. |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [analyze(file, mapping)](./kmc-analyze.analyzeoskrewritepua.analyze.md) | | Analyze a keyboard file or files, and provide a remapped output. Accepts a .kmn, .kvks, .keyman-touch-layout file formats. For .kmn, will rewrite associated On Screen Keyboard file formats. Can be called multiple times to rewrite multiple files. Use the [AnalyzeOskRewritePua.data](./kmc-analyze.analyzeoskrewritepua.data.md) property to retrieve the output file content for writing. This does not modify the source file. |
+| [clear()](./kmc-analyze.analyzeoskrewritepua.clear.md) | | Clears data collected from previous calls to [AnalyzeOskRewritePua.analyze()](./kmc-analyze.analyzeoskrewritepua.analyze.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-analyze.md b/developer/docs/help/reference/api/kmc-analyze.md
new file mode 100644
index 00000000000..3b514bcf962
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-analyze.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-analyze](./kmc-analyze.md)
+
+## kmc-analyze package
+
+kmc-analyze - keyboard analysis classes, including tools for `&displayMap`.
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) | Analyze the characters used in On Screen Keyboard files (.kvks, .keyman-touch-layout) for use with &displayMap
. |
+| [AnalyzeOskRewritePua](./kmc-analyze.analyzeoskrewritepua.md) | Rewrite On Screen Keyboard files (.kvks, .keyman-touch-layout) with PUA codepoints, based on analysis provided by [AnalyzeOskCharacterUse](./kmc-analyze.analyzeoskcharacteruse.md) class. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [AnalyzeOskCharacterUseOptions](./kmc-analyze.analyzeoskcharacteruseoptions.md) | Options for character analysis |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler._constructor_.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler._constructor_.md
new file mode 100644
index 00000000000..0196e3ca32f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler._constructor_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [(constructor)](./kmc-keyboard-info.keyboardinfocompiler._constructor_.md)
+
+## KeyboardInfoCompiler.(constructor)
+
+Constructs a new instance of the `KeyboardInfoCompiler` class
+
+**Signature:**
+
+```typescript
+constructor();
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.init.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.init.md
new file mode 100644
index 00000000000..fb9b1460444
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [init](./kmc-keyboard-info.keyboardinfocompiler.init.md)
+
+## KeyboardInfoCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: KeyboardInfoCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.md
new file mode 100644
index 00000000000..8bca9bd0e32
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md)
+
+## KeyboardInfoCompiler class
+
+Compiles source data from a keyboard project to a .keyboard\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class KeyboardInfoCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)()](./kmc-keyboard-info.keyboardinfocompiler._constructor_.md) | | Constructs a new instance of the KeyboardInfoCompiler
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-keyboard-info.keyboardinfocompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-keyboard-info.keyboardinfocompiler.run.md) | | Builds a .keyboard\_info file with metadata from the keyboard and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KeyboardInfoCompiler.init()](./kmc-keyboard-info.keyboardinfocompiler.init.md) function to read any input files by disk.
This function is intended for use within the keyboards repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed. For full documentation, see: https://help.keyman.com/developer/cloud/keyboard\_info/
|
+| [write(artifacts)](./kmc-keyboard-info.keyboardinfocompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .keyboard\_info file - metadata file used by keyman.com
|
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.run.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.run.md
new file mode 100644
index 00000000000..711d8948648
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.run.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [run](./kmc-keyboard-info.keyboardinfocompiler.run.md)
+
+## KeyboardInfoCompiler.run() method
+
+Builds a .keyboard\_info file with metadata from the keyboard and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KeyboardInfoCompiler.init()](./kmc-keyboard-info.keyboardinfocompiler.init.md) function to read any input files by disk.
+
+This function is intended for use within the keyboards repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed. For full documentation, see: https://help.keyman.com/developer/cloud/keyboard\_info/
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.write.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.write.md
new file mode 100644
index 00000000000..9ac5bf1b336
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) > [write](./kmc-keyboard-info.keyboardinfocompiler.write.md)
+
+## KeyboardInfoCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .keyboard\_info file - metadata file used by keyman.com
+
+**Signature:**
+
+```typescript
+write(artifacts: KeyboardInfoCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md
new file mode 100644
index 00000000000..e3b46d10e55
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) > [keyboard\_info](./kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md)
+
+## KeyboardInfoCompilerArtifacts.keyboard\_info property
+
+Binary keyboard info filedata and filename - used by keyman.com
+
+**Signature:**
+
+```typescript
+keyboard_info: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.md
new file mode 100644
index 00000000000..fcfa258df4b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md)
+
+## KeyboardInfoCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [keyboard\_info](./kmc-keyboard-info.keyboardinfocompilerartifacts.keyboard_info.md) | | KeymanCompilerArtifact | Binary keyboard info filedata and filename - used by keyman.com |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.md
new file mode 100644
index 00000000000..63f1279300e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md)
+
+## KeyboardInfoCompilerOptions interface
+
+Options for the .keyboard\_info compiler
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [sources](./kmc-keyboard-info.keyboardinfocompileroptions.sources.md) | | [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) | Description of sources and metadata required to build a .keyboard\_info file |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.sources.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.sources.md
new file mode 100644
index 00000000000..371f1734776
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompileroptions.sources.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md) > [sources](./kmc-keyboard-info.keyboardinfocompileroptions.sources.md)
+
+## KeyboardInfoCompilerOptions.sources property
+
+Description of sources and metadata required to build a .keyboard\_info file
+
+**Signature:**
+
+```typescript
+sources: KeyboardInfoSources;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md
new file mode 100644
index 00000000000..0554d415c53
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md) > [artifacts](./kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md)
+
+## KeyboardInfoCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KeyboardInfoCompiler.write()](./kmc-keyboard-info.keyboardinfocompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: KeyboardInfoCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.md
new file mode 100644
index 00000000000..96de0472e3b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfocompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md)
+
+## KeyboardInfoCompilerResult interface
+
+Build artifacts from the .keyboard\_info compiler
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-keyboard-info.keyboardinfocompilerresult.artifacts.md) | | [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KeyboardInfoCompiler.write()](./kmc-keyboard-info.keyboardinfocompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.forpublishing.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.forpublishing.md
new file mode 100644
index 00000000000..5eb9fe6d6f9
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.forpublishing.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [forPublishing](./kmc-keyboard-info.keyboardinfosources.forpublishing.md)
+
+## KeyboardInfoSources.forPublishing property
+
+Return an error if project does not meet requirements of keyboards repository
+
+**Signature:**
+
+```typescript
+forPublishing: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.jsfilename.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.jsfilename.md
new file mode 100644
index 00000000000..15e0557724b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.jsfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [jsFilename](./kmc-keyboard-info.keyboardinfosources.jsfilename.md)
+
+## KeyboardInfoSources.jsFilename property
+
+The compiled keyboard filename and relative path (.js only)
+
+**Signature:**
+
+```typescript
+jsFilename?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kmpfilename.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kmpfilename.md
new file mode 100644
index 00000000000..53689423218
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kmpfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [kmpFilename](./kmc-keyboard-info.keyboardinfosources.kmpfilename.md)
+
+## KeyboardInfoSources.kmpFilename property
+
+The compiled package filename and relative path (.kmp)
+
+**Signature:**
+
+```typescript
+kmpFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kpsfilename.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kpsfilename.md
new file mode 100644
index 00000000000..577820f1454
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.kpsfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [kpsFilename](./kmc-keyboard-info.keyboardinfosources.kpsfilename.md)
+
+## KeyboardInfoSources.kpsFilename property
+
+The source package filename and relative path (.kps)
+
+**Signature:**
+
+```typescript
+kpsFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.lastcommitdate.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.lastcommitdate.md
new file mode 100644
index 00000000000..398a0ab9552
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.lastcommitdate.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [lastCommitDate](./kmc-keyboard-info.keyboardinfosources.lastcommitdate.md)
+
+## KeyboardInfoSources.lastCommitDate property
+
+Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ'
+
+**Signature:**
+
+```typescript
+lastCommitDate?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.md
new file mode 100644
index 00000000000..5a6caf26d68
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md)
+
+## KeyboardInfoSources interface
+
+Description of sources and metadata required to build a .keyboard\_info file
+
+**Signature:**
+
+```typescript
+export interface KeyboardInfoSources
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [forPublishing](./kmc-keyboard-info.keyboardinfosources.forpublishing.md) | | boolean | Return an error if project does not meet requirements of keyboards repository |
+| [jsFilename?](./kmc-keyboard-info.keyboardinfosources.jsfilename.md) | | string | _(Optional)_ The compiled keyboard filename and relative path (.js only) |
+| [kmpFilename](./kmc-keyboard-info.keyboardinfosources.kmpfilename.md) | | string | The compiled package filename and relative path (.kmp) |
+| [kpsFilename](./kmc-keyboard-info.keyboardinfosources.kpsfilename.md) | | string | The source package filename and relative path (.kps) |
+| [lastCommitDate?](./kmc-keyboard-info.keyboardinfosources.lastcommitdate.md) | | string | _(Optional)_ Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' |
+| [sourcePath](./kmc-keyboard-info.keyboardinfosources.sourcepath.md) | | string | The path in the keymanapp/keyboards repo where this keyboard may be found |
+
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.sourcepath.md b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.sourcepath.md
new file mode 100644
index 00000000000..197a5d123be
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.keyboardinfosources.sourcepath.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md) > [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) > [sourcePath](./kmc-keyboard-info.keyboardinfosources.sourcepath.md)
+
+## KeyboardInfoSources.sourcePath property
+
+The path in the keymanapp/keyboards repo where this keyboard may be found
+
+**Signature:**
+
+```typescript
+sourcePath: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-keyboard-info.md b/developer/docs/help/reference/api/kmc-keyboard-info.md
new file mode 100644
index 00000000000..403add9e832
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-keyboard-info.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-keyboard-info](./kmc-keyboard-info.md)
+
+## kmc-keyboard-info package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [KeyboardInfoCompiler](./kmc-keyboard-info.keyboardinfocompiler.md) | Compiles source data from a keyboard project to a .keyboard\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [KeyboardInfoCompilerArtifacts](./kmc-keyboard-info.keyboardinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [KeyboardInfoCompilerOptions](./kmc-keyboard-info.keyboardinfocompileroptions.md) | Options for the .keyboard\_info compiler |
+| [KeyboardInfoCompilerResult](./kmc-keyboard-info.keyboardinfocompilerresult.md) | Build artifacts from the .keyboard\_info compiler |
+| [KeyboardInfoSources](./kmc-keyboard-info.keyboardinfosources.md) | Description of sources and metadata required to build a .keyboard\_info file |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler._constructor_.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler._constructor_.md
new file mode 100644
index 00000000000..42a1cd666cb
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler._constructor_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [(constructor)](./kmc-kmn.kmncompiler._constructor_.md)
+
+## KmnCompiler.(constructor)
+
+Constructs a new instance of the `KmnCompiler` class
+
+**Signature:**
+
+```typescript
+constructor();
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.init.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.init.md
new file mode 100644
index 00000000000..5d2698b5443
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [init](./kmc-kmn.kmncompiler.init.md)
+
+## KmnCompiler.init() method
+
+Initialize the compiler, including loading the WASM host for kmcmplib. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: KmnCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [KmnCompilerOptions](./kmc-kmn.kmncompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.md
new file mode 100644
index 00000000000..d41c05fc2e3
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md)
+
+## KmnCompiler class
+
+Compiles a .kmn file to a .kmx, .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class KmnCompiler implements KeymanCompiler, UnicodeSetParser
+```
+**Implements:** KeymanCompiler, UnicodeSetParser
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)()](./kmc-kmn.kmncompiler._constructor_.md) | | Constructs a new instance of the KmnCompiler
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-kmn.kmncompiler.init.md) | | Initialize the compiler, including loading the WASM host for kmcmplib. Copies options. |
+| [run(infile, outfile)](./kmc-kmn.kmncompiler.run.md) | | Compiles a .kmn file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmnCompiler.init()](./kmc-kmn.kmncompiler.init.md) function to read any input files by disk. |
+| [verifyInitialized()](./kmc-kmn.kmncompiler.verifyinitialized.md) | | Verify that wasm is spun up OK. |
+| [write(artifacts)](./kmc-kmn.kmncompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.run.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.run.md
new file mode 100644
index 00000000000..a97f40ab962
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [run](./kmc-kmn.kmncompiler.run.md)
+
+## KmnCompiler.run() method
+
+Compiles a .kmn file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmnCompiler.init()](./kmc-kmn.kmncompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(infile: string, outfile: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| infile | string | Path to source file. Path will be parsed to find relative references in the .kmn file, such as icon or On Screen Keyboard file |
+| outfile | string | Path to output file. The file will not be written to, but will be included in the result for use by [KmnCompiler.write()](./kmc-kmn.kmncompiler.write.md). |
+
+**Returns:**
+
+Promise<[KmnCompilerResult](./kmc-kmn.kmncompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.verifyinitialized.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.verifyinitialized.md
new file mode 100644
index 00000000000..f2d38795172
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.verifyinitialized.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [verifyInitialized](./kmc-kmn.kmncompiler.verifyinitialized.md)
+
+## KmnCompiler.verifyInitialized() method
+
+Verify that wasm is spun up OK.
+
+**Signature:**
+
+```typescript
+verifyInitialized(): boolean;
+```
+**Returns:**
+
+boolean
+
+true if OK
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompiler.write.md b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.write.md
new file mode 100644
index 00000000000..ebc1b8d8437
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompiler](./kmc-kmn.kmncompiler.md) > [write](./kmc-kmn.kmncompiler.write.md)
+
+## KmnCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: KmnCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.js.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.js.md
new file mode 100644
index 00000000000..12706601f1e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.js.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) > [js](./kmc-kmn.kmncompilerartifacts.js.md)
+
+## KmnCompilerArtifacts.js property
+
+Javascript keyboard filedata and filename - installable into KeymanWeb, Keyman mobile products
+
+**Signature:**
+
+```typescript
+js?: KeymanCompilerArtifactOptional;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kmx.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kmx.md
new file mode 100644
index 00000000000..c850b6d036b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kmx.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) > [kmx](./kmc-kmn.kmncompilerartifacts.kmx.md)
+
+## KmnCompilerArtifacts.kmx property
+
+Binary keyboard filedata and filename - installable into Keyman desktop projects
+
+**Signature:**
+
+```typescript
+kmx?: KeymanCompilerArtifactOptional;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kvk.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kvk.md
new file mode 100644
index 00000000000..4a6bb87d65a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.kvk.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) > [kvk](./kmc-kmn.kmncompilerartifacts.kvk.md)
+
+## KmnCompilerArtifacts.kvk property
+
+Binary on screen keyboard filedata and filename - installable into Keyman desktop projects alongside .kmx
+
+**Signature:**
+
+```typescript
+kvk?: KeymanCompilerArtifactOptional;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.md
new file mode 100644
index 00000000000..37561d34a02
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerartifacts.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md)
+
+## KmnCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface KmnCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [js?](./kmc-kmn.kmncompilerartifacts.js.md) | | KeymanCompilerArtifactOptional | _(Optional)_ Javascript keyboard filedata and filename - installable into KeymanWeb, Keyman mobile products |
+| [kmx?](./kmc-kmn.kmncompilerartifacts.kmx.md) | | KeymanCompilerArtifactOptional | _(Optional)_ Binary keyboard filedata and filename - installable into Keyman desktop projects |
+| [kvk?](./kmc-kmn.kmncompilerartifacts.kvk.md) | | KeymanCompilerArtifactOptional | _(Optional)_ Binary on screen keyboard filedata and filename - installable into Keyman desktop projects alongside .kmx |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompileroptions.md b/developer/docs/help/reference/api/kmc-kmn.kmncompileroptions.md
new file mode 100644
index 00000000000..1591a9313b6
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompileroptions.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerOptions](./kmc-kmn.kmncompileroptions.md)
+
+## KmnCompilerOptions interface
+
+Options for the .kmn compiler
+
+**Signature:**
+
+```typescript
+export interface KmnCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.artifacts.md
new file mode 100644
index 00000000000..12a4dac8596
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) > [artifacts](./kmc-kmn.kmncompilerresult.artifacts.md)
+
+## KmnCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmnCompiler.write()](./kmc-kmn.kmncompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: KmnCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.displaymap.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.displaymap.md
new file mode 100644
index 00000000000..cdf6cbe9a4f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.displaymap.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) > [displayMap](./kmc-kmn.kmncompilerresult.displaymap.md)
+
+## KmnCompilerResult.displayMap property
+
+Mapping data for `&displayMap`, intended for use by kmc-analyze
+
+**Signature:**
+
+```typescript
+displayMap?: Osk.PuaMap;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.extra.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.extra.md
new file mode 100644
index 00000000000..9263bb78037
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.extra.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) > [extra](./kmc-kmn.kmncompilerresult.extra.md)
+
+## KmnCompilerResult.extra property
+
+Internal additional metadata used by secondary compile phases such as KmwCompiler, not intended for external use
+
+**Signature:**
+
+```typescript
+extra: KmnCompilerResultExtra;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.md b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.md
new file mode 100644
index 00000000000..b50a6025b41
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.kmncompilerresult.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md)
+
+## KmnCompilerResult interface
+
+Build artifacts from the .kmn compiler
+
+**Signature:**
+
+```typescript
+export interface KmnCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-kmn.kmncompilerresult.artifacts.md) | | [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmnCompiler.write()](./kmc-kmn.kmncompiler.write.md) |
+| [displayMap?](./kmc-kmn.kmncompilerresult.displaymap.md) | | [Osk.PuaMap](./kmc-kmn.osk.puamap.md) | _(Optional)_ Mapping data for &displayMap
, intended for use by kmc-analyze |
+| [extra](./kmc-kmn.kmncompilerresult.extra.md) | | KmnCompilerResultExtra | Internal additional metadata used by secondary compile phases such as KmwCompiler, not intended for external use |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.md b/developer/docs/help/reference/api/kmc-kmn.md
new file mode 100644
index 00000000000..253dbbac62b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md)
+
+## kmc-kmn package
+
+kmc-kmn - Keyman keyboard compiler
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [KmnCompiler](./kmc-kmn.kmncompiler.md) | Compiles a .kmn file to a .kmx, .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [KmnCompilerArtifacts](./kmc-kmn.kmncompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [KmnCompilerOptions](./kmc-kmn.kmncompileroptions.md) | Options for the .kmn compiler |
+| [KmnCompilerResult](./kmc-kmn.kmncompilerresult.md) | Build artifacts from the .kmn compiler |
+
+## Namespaces
+
+| Namespace | Description |
+| --- | --- |
+| [Osk](./kmc-kmn.osk.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.md b/developer/docs/help/reference/api/kmc-kmn.osk.md
new file mode 100644
index 00000000000..7257d523fec
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md)
+
+## Osk namespace
+
+## Functions
+
+| Function | Description |
+| --- | --- |
+| [parseMapping(mapping)](./kmc-kmn.osk.parsemapping.md) | |
+| [remapTouchLayout(source, map)](./kmc-kmn.osk.remaptouchlayout.md) | |
+| [remapVisualKeyboard(vk, map)](./kmc-kmn.osk.remapvisualkeyboard.md) | |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [StringRef](./kmc-kmn.osk.stringref.md) | |
+| [StringRefUsage](./kmc-kmn.osk.stringrefusage.md) | |
+| [StringResult](./kmc-kmn.osk.stringresult.md) | Represents a single key cap found by AnalyzeOskCharacterUse
|
+
+## Type Aliases
+
+| Type Alias | Description |
+| --- | --- |
+| [PuaMap](./kmc-kmn.osk.puamap.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.parsemapping.md b/developer/docs/help/reference/api/kmc-kmn.osk.parsemapping.md
new file mode 100644
index 00000000000..d2dbbdffe50
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.parsemapping.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [parseMapping](./kmc-kmn.osk.parsemapping.md)
+
+## Osk.parseMapping() function
+
+**Signature:**
+
+```typescript
+export declare function parseMapping(mapping: any): PuaMap;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| mapping | any | |
+
+**Returns:**
+
+[PuaMap](./kmc-kmn.osk.puamap.md)
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.puamap.md b/developer/docs/help/reference/api/kmc-kmn.osk.puamap.md
new file mode 100644
index 00000000000..4a56d392123
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.puamap.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [PuaMap](./kmc-kmn.osk.puamap.md)
+
+## Osk.PuaMap type
+
+**Signature:**
+
+```typescript
+export type PuaMap = {
+ [index: string]: string;
+};
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.remaptouchlayout.md b/developer/docs/help/reference/api/kmc-kmn.osk.remaptouchlayout.md
new file mode 100644
index 00000000000..b6c8c76239f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.remaptouchlayout.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [remapTouchLayout](./kmc-kmn.osk.remaptouchlayout.md)
+
+## Osk.remapTouchLayout() function
+
+**Signature:**
+
+```typescript
+export declare function remapTouchLayout(source: TouchLayout.TouchLayoutFile, map: PuaMap): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| source | TouchLayout.TouchLayoutFile | |
+| map | [PuaMap](./kmc-kmn.osk.puamap.md) | |
+
+**Returns:**
+
+boolean
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.remapvisualkeyboard.md b/developer/docs/help/reference/api/kmc-kmn.osk.remapvisualkeyboard.md
new file mode 100644
index 00000000000..b6c41d28032
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.remapvisualkeyboard.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [remapVisualKeyboard](./kmc-kmn.osk.remapvisualkeyboard.md)
+
+## Osk.remapVisualKeyboard() function
+
+**Signature:**
+
+```typescript
+export declare function remapVisualKeyboard(vk: VisualKeyboard.VisualKeyboard, map: PuaMap): boolean;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| vk | VisualKeyboard.VisualKeyboard | |
+| map | [PuaMap](./kmc-kmn.osk.puamap.md) | |
+
+**Returns:**
+
+boolean
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringref.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.md
new file mode 100644
index 00000000000..24e66ec891a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRef](./kmc-kmn.osk.stringref.md)
+
+## Osk.StringRef interface
+
+**Signature:**
+
+```typescript
+export interface StringRef
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [str](./kmc-kmn.osk.stringref.str.md) | | string | |
+| [usages](./kmc-kmn.osk.stringref.usages.md) | | [StringRefUsage](./kmc-kmn.osk.stringrefusage.md)\[\] | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringref.str.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.str.md
new file mode 100644
index 00000000000..85e45efa043
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.str.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRef](./kmc-kmn.osk.stringref.md) > [str](./kmc-kmn.osk.stringref.str.md)
+
+## Osk.StringRef.str property
+
+**Signature:**
+
+```typescript
+str: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringref.usages.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.usages.md
new file mode 100644
index 00000000000..8e6c209879a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringref.usages.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRef](./kmc-kmn.osk.stringref.md) > [usages](./kmc-kmn.osk.stringref.usages.md)
+
+## Osk.StringRef.usages property
+
+**Signature:**
+
+```typescript
+usages: StringRefUsage[];
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.count.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.count.md
new file mode 100644
index 00000000000..73cbde62ebd
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.count.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRefUsage](./kmc-kmn.osk.stringrefusage.md) > [count](./kmc-kmn.osk.stringrefusage.count.md)
+
+## Osk.StringRefUsage.count property
+
+**Signature:**
+
+```typescript
+count: number;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.filename.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.filename.md
new file mode 100644
index 00000000000..f199327033d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.filename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRefUsage](./kmc-kmn.osk.stringrefusage.md) > [filename](./kmc-kmn.osk.stringrefusage.filename.md)
+
+## Osk.StringRefUsage.filename property
+
+**Signature:**
+
+```typescript
+filename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.md
new file mode 100644
index 00000000000..403b0af7fe0
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringrefusage.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringRefUsage](./kmc-kmn.osk.stringrefusage.md)
+
+## Osk.StringRefUsage interface
+
+**Signature:**
+
+```typescript
+export interface StringRefUsage
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [count](./kmc-kmn.osk.stringrefusage.count.md) | | number | |
+| [filename](./kmc-kmn.osk.stringrefusage.filename.md) | | string | |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.md
new file mode 100644
index 00000000000..1e5a6d10194
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md)
+
+## Osk.StringResult interface
+
+Represents a single key cap found by `AnalyzeOskCharacterUse`
+
+**Signature:**
+
+```typescript
+export interface StringResult
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [pua](./kmc-kmn.osk.stringresult.pua.md) | | string | hexadecimal single character in PUA range, without 'U+' prefix, e.g. 'F100' |
+| [str](./kmc-kmn.osk.stringresult.str.md) | | string | the key cap string |
+| [unicode](./kmc-kmn.osk.stringresult.unicode.md) | | string | unicode code points in for reference, without 'U+' prefix, e.g. '0061 0301' |
+| [usages](./kmc-kmn.osk.stringresult.usages.md) | | [StringRefUsage](./kmc-kmn.osk.stringrefusage.md)\[\] \| string\[\] | files in which the string is referenced; will be an array of if includeCounts is true, otherwise will be an array of strings listing files in which the key cap may be found |
+
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.pua.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.pua.md
new file mode 100644
index 00000000000..84b2880dc0e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.pua.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [pua](./kmc-kmn.osk.stringresult.pua.md)
+
+## Osk.StringResult.pua property
+
+hexadecimal single character in PUA range, without 'U+' prefix, e.g. 'F100'
+
+**Signature:**
+
+```typescript
+pua: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.str.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.str.md
new file mode 100644
index 00000000000..e24371b1d89
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.str.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [str](./kmc-kmn.osk.stringresult.str.md)
+
+## Osk.StringResult.str property
+
+the key cap string
+
+**Signature:**
+
+```typescript
+str: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.unicode.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.unicode.md
new file mode 100644
index 00000000000..92c8a789d50
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.unicode.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [unicode](./kmc-kmn.osk.stringresult.unicode.md)
+
+## Osk.StringResult.unicode property
+
+unicode code points in for reference, without 'U+' prefix, e.g. '0061 0301'
+
+**Signature:**
+
+```typescript
+unicode: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.usages.md b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.usages.md
new file mode 100644
index 00000000000..0010cc8cb89
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-kmn.osk.stringresult.usages.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-kmn](./kmc-kmn.md) > [Osk](./kmc-kmn.osk.md) > [StringResult](./kmc-kmn.osk.stringresult.md) > [usages](./kmc-kmn.osk.stringresult.usages.md)
+
+## Osk.StringResult.usages property
+
+files in which the string is referenced; will be an array of if includeCounts is true, otherwise will be an array of strings listing files in which the key cap may be found
+
+**Signature:**
+
+```typescript
+usages: StringRefUsage[] | string[];
+```
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.md b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.md
new file mode 100644
index 00000000000..3ee68592870
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md)
+
+## LdmlCompilerOptions interface
+
+Options for the .xml LDML keyboard compiler
+
+**Signature:**
+
+```typescript
+export interface LdmlCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [readerOptions](./kmc-ldml.ldmlcompileroptions.readeroptions.md) | | LDMLKeyboardXMLSourceFileReaderOptions | Paths and other options required for reading .xml files |
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.readeroptions.md b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.readeroptions.md
new file mode 100644
index 00000000000..59e3f53b736
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlcompileroptions.readeroptions.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md) > [readerOptions](./kmc-ldml.ldmlcompileroptions.readeroptions.md)
+
+## LdmlCompilerOptions.readerOptions property
+
+Paths and other options required for reading .xml files
+
+**Signature:**
+
+```typescript
+readerOptions: LDMLKeyboardXMLSourceFileReaderOptions;
+```
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.init.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.init.md
new file mode 100644
index 00000000000..6fd83e9d463
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) > [init](./kmc-ldml.ldmlkeyboardcompiler.init.md)
+
+## LdmlKeyboardCompiler.init() method
+
+Initialize the compiler, including loading the WASM host for uset parsing. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: LdmlCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.md
new file mode 100644
index 00000000000..fdd5d32c16b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md)
+
+## LdmlKeyboardCompiler class
+
+Compiles a LDML keyboard .xml file to a .kmx (KMXPlus), .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class LdmlKeyboardCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-ldml.ldmlkeyboardcompiler.init.md) | | Initialize the compiler, including loading the WASM host for uset parsing. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-ldml.ldmlkeyboardcompiler.run.md) | | Compiles a LDML keyboard .xml file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LdmlKeyboardCompiler.init()](./kmc-ldml.ldmlkeyboardcompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-ldml.ldmlkeyboardcompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.run.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.run.md
new file mode 100644
index 00000000000..cb0e9eaa7f4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) > [run](./kmc-ldml.ldmlkeyboardcompiler.run.md)
+
+## LdmlKeyboardCompiler.run() method
+
+Compiles a LDML keyboard .xml file to .kmx, .kvk, and/or .js files. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LdmlKeyboardCompiler.init()](./kmc-ldml.ldmlkeyboardcompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<LdmlKeyboardCompilerResult>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.write.md b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.write.md
new file mode 100644
index 00000000000..434b463adb4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.ldmlkeyboardcompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md) > [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) > [write](./kmc-ldml.ldmlkeyboardcompiler.write.md)
+
+## LdmlKeyboardCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .kmx file - binary keyboard used by Keyman on desktop platforms - .kvk file - binary on screen keyboard used by Keyman on desktop platforms - .js file - Javascript keyboard for web and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: LdmlKeyboardCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | LdmlKeyboardCompilerArtifacts | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-ldml.md b/developer/docs/help/reference/api/kmc-ldml.md
new file mode 100644
index 00000000000..ca921083a22
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-ldml.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-ldml](./kmc-ldml.md)
+
+## kmc-ldml package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [LdmlKeyboardCompiler](./kmc-ldml.ldmlkeyboardcompiler.md) | Compiles a LDML keyboard .xml file to a .kmx (KMXPlus), .kvk, and/or .js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [LdmlCompilerOptions](./kmc-ldml.ldmlcompileroptions.md) | Options for the .xml LDML keyboard compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.md b/developer/docs/help/reference/api/kmc-model-info.md
new file mode 100644
index 00000000000..f7cbd6d4631
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md)
+
+## kmc-model-info package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) | Compiles source data from a lexical model project to a .model\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+| [ModelInfoSources](./kmc-model-info.modelinfosources.md) | Description of sources and metadata required to build a .model\_info file |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md) | Options for the .model\_info compiler |
+| [ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md) | Build artifacts from the .model\_info compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler._constructor_.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler._constructor_.md
new file mode 100644
index 00000000000..9aadd791a08
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler._constructor_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [(constructor)](./kmc-model-info.modelinfocompiler._constructor_.md)
+
+## ModelInfoCompiler.(constructor)
+
+Constructs a new instance of the `ModelInfoCompiler` class
+
+**Signature:**
+
+```typescript
+constructor();
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.init.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.init.md
new file mode 100644
index 00000000000..ff8383bb27e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [init](./kmc-model-info.modelinfocompiler.init.md)
+
+## ModelInfoCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: ModelInfoCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.md
new file mode 100644
index 00000000000..05b0c58ba68
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md)
+
+## ModelInfoCompiler class
+
+Compiles source data from a lexical model project to a .model\_info. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class ModelInfoCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)()](./kmc-model-info.modelinfocompiler._constructor_.md) | | Constructs a new instance of the ModelInfoCompiler
class |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-model-info.modelinfocompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-model-info.modelinfocompiler.run.md) | | Builds .model\_info file with metadata from the model and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [ModelInfoCompiler.init()](./kmc-model-info.modelinfocompiler.init.md) function to read any input files by disk.
This function is intended for use within the lexical-models repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed.
|
+| [write(artifacts)](./kmc-model-info.modelinfocompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .model\_info file - metadata file used by keyman.com
|
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.run.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.run.md
new file mode 100644
index 00000000000..ead3fa50032
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.run.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [run](./kmc-model-info.modelinfocompiler.run.md)
+
+## ModelInfoCompiler.run() method
+
+Builds .model\_info file with metadata from the model and package source file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [ModelInfoCompiler.init()](./kmc-model-info.modelinfocompiler.init.md) function to read any input files by disk.
+
+This function is intended for use within the lexical-models repository. While many of the parameters could be deduced from each other, they are specified here to reduce the number of places the filenames are constructed.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.write.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.write.md
new file mode 100644
index 00000000000..e2d2e5fd27e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompiler](./kmc-model-info.modelinfocompiler.md) > [write](./kmc-model-info.modelinfocompiler.write.md)
+
+## ModelInfoCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .model\_info file - metadata file used by keyman.com
+
+**Signature:**
+
+```typescript
+write(artifacts: ModelInfoCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.md
new file mode 100644
index 00000000000..1c7e2e7df65
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md)
+
+## ModelInfoCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface ModelInfoCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [model\_info](./kmc-model-info.modelinfocompilerartifacts.model_info.md) | | KeymanCompilerArtifact | Binary model info filedata and filename - used by keyman.com |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.model_info.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.model_info.md
new file mode 100644
index 00000000000..1f66e96f344
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerartifacts.model_info.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) > [model\_info](./kmc-model-info.modelinfocompilerartifacts.model_info.md)
+
+## ModelInfoCompilerArtifacts.model\_info property
+
+Binary model info filedata and filename - used by keyman.com
+
+**Signature:**
+
+```typescript
+model_info: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.md
new file mode 100644
index 00000000000..984d9bf70a7
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md)
+
+## ModelInfoCompilerOptions interface
+
+Options for the .model\_info compiler
+
+**Signature:**
+
+```typescript
+export interface ModelInfoCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [sources](./kmc-model-info.modelinfocompileroptions.sources.md) | | [ModelInfoSources](./kmc-model-info.modelinfosources.md) | Description of sources and metadata required to build a .model\_info file |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.sources.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.sources.md
new file mode 100644
index 00000000000..40c986596e9
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompileroptions.sources.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerOptions](./kmc-model-info.modelinfocompileroptions.md) > [sources](./kmc-model-info.modelinfocompileroptions.sources.md)
+
+## ModelInfoCompilerOptions.sources property
+
+Description of sources and metadata required to build a .model\_info file
+
+**Signature:**
+
+```typescript
+sources: ModelInfoSources;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.artifacts.md
new file mode 100644
index 00000000000..9dc3bdf6ea4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md) > [artifacts](./kmc-model-info.modelinfocompilerresult.artifacts.md)
+
+## ModelInfoCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [ModelInfoCompiler.write()](./kmc-model-info.modelinfocompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: ModelInfoCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.md b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.md
new file mode 100644
index 00000000000..0980afd7f76
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfocompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoCompilerResult](./kmc-model-info.modelinfocompilerresult.md)
+
+## ModelInfoCompilerResult interface
+
+Build artifacts from the .model\_info compiler
+
+**Signature:**
+
+```typescript
+export interface ModelInfoCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-model-info.modelinfocompilerresult.artifacts.md) | | [ModelInfoCompilerArtifacts](./kmc-model-info.modelinfocompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [ModelInfoCompiler.write()](./kmc-model-info.modelinfocompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.forpublishing.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.forpublishing.md
new file mode 100644
index 00000000000..0877529d209
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.forpublishing.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [forPublishing](./kmc-model-info.modelinfosources.forpublishing.md)
+
+## ModelInfoSources.forPublishing property
+
+Return an error if project does not meet requirements of lexical-models repository
+
+**Signature:**
+
+```typescript
+forPublishing: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpfilename.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpfilename.md
new file mode 100644
index 00000000000..4d47a8ea9a4
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [kmpFileName](./kmc-model-info.modelinfosources.kmpfilename.md)
+
+## ModelInfoSources.kmpFileName property
+
+The compiled package filename and relative path (.kmp)
+
+**Signature:**
+
+```typescript
+kmpFileName: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpjsondata.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpjsondata.md
new file mode 100644
index 00000000000..d0fbf7ebb7a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kmpjsondata.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [kmpJsonData](./kmc-model-info.modelinfosources.kmpjsondata.md)
+
+## ModelInfoSources.kmpJsonData property
+
+The data from the .kps file, transformed to kmp.json
+
+**Signature:**
+
+```typescript
+kmpJsonData: KmpJsonFile.KmpJsonFile;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kpsfilename.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kpsfilename.md
new file mode 100644
index 00000000000..c80e6755afe
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.kpsfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [kpsFilename](./kmc-model-info.modelinfosources.kpsfilename.md)
+
+## ModelInfoSources.kpsFilename property
+
+The source package filename and relative path (.kps)
+
+**Signature:**
+
+```typescript
+kpsFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.lastcommitdate.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.lastcommitdate.md
new file mode 100644
index 00000000000..c78c7d5b9db
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.lastcommitdate.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [lastCommitDate](./kmc-model-info.modelinfosources.lastcommitdate.md)
+
+## ModelInfoSources.lastCommitDate property
+
+Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ'
+
+**Signature:**
+
+```typescript
+lastCommitDate?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.md
new file mode 100644
index 00000000000..c74ab3cafd6
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md)
+
+## ModelInfoSources class
+
+Description of sources and metadata required to build a .model\_info file
+
+**Signature:**
+
+```typescript
+export declare class ModelInfoSources
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [forPublishing](./kmc-model-info.modelinfosources.forpublishing.md) | | boolean | Return an error if project does not meet requirements of lexical-models repository |
+| [kmpFileName](./kmc-model-info.modelinfosources.kmpfilename.md) | | string | The compiled package filename and relative path (.kmp) |
+| [kmpJsonData](./kmc-model-info.modelinfosources.kmpjsondata.md) | | KmpJsonFile.KmpJsonFile | The data from the .kps file, transformed to kmp.json |
+| [kpsFilename](./kmc-model-info.modelinfosources.kpsfilename.md) | | string | The source package filename and relative path (.kps) |
+| [lastCommitDate?](./kmc-model-info.modelinfosources.lastcommitdate.md) | | string | _(Optional)_ Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' |
+| [model\_id](./kmc-model-info.modelinfosources.model_id.md) | | string | The identifier for the model |
+| [modelFileName](./kmc-model-info.modelinfosources.modelfilename.md) | | string | The compiled model filename and relative path (.js) |
+| [sourcePath](./kmc-model-info.modelinfosources.sourcepath.md) | | string | The path in the keymanapp/lexical-models repo where this model may be found |
+
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.model_id.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.model_id.md
new file mode 100644
index 00000000000..d0d40cde82b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.model_id.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [model\_id](./kmc-model-info.modelinfosources.model_id.md)
+
+## ModelInfoSources.model\_id property
+
+The identifier for the model
+
+**Signature:**
+
+```typescript
+model_id: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.modelfilename.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.modelfilename.md
new file mode 100644
index 00000000000..2f4567e44fe
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.modelfilename.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [modelFileName](./kmc-model-info.modelinfosources.modelfilename.md)
+
+## ModelInfoSources.modelFileName property
+
+The compiled model filename and relative path (.js)
+
+**Signature:**
+
+```typescript
+modelFileName: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model-info.modelinfosources.sourcepath.md b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.sourcepath.md
new file mode 100644
index 00000000000..7df6ad03e57
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model-info.modelinfosources.sourcepath.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model-info](./kmc-model-info.md) > [ModelInfoSources](./kmc-model-info.modelinfosources.md) > [sourcePath](./kmc-model-info.modelinfosources.sourcepath.md)
+
+## ModelInfoSources.sourcePath property
+
+The path in the keymanapp/lexical-models repo where this model may be found
+
+**Signature:**
+
+```typescript
+sourcePath: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.casedwordformtokeyspec.md b/developer/docs/help/reference/api/kmc-model.casedwordformtokeyspec.md
new file mode 100644
index 00000000000..5403c6f0c5b
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.casedwordformtokeyspec.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [CasedWordformToKeySpec](./kmc-model.casedwordformtokeyspec.md)
+
+## CasedWordformToKeySpec type
+
+Simplifies input text to facilitate finding entries within a lexical model's lexicon, using the model's `applyCasing` function to assist in the keying process. 14.0
+
+**Signature:**
+
+```typescript
+export type CasedWordformToKeySpec = (term: string, applyCasing?: CasingFunction) => string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.init.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.init.md
new file mode 100644
index 00000000000..56e6fc5c9bc
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) > [init](./kmc-model.lexicalmodelcompiler.init.md)
+
+## LexicalModelCompiler.init() method
+
+Initialize the compiler. There are currently no options specific to the lexical model compiler
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, _options: CompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| \_options | CompilerOptions | |
+
+**Returns:**
+
+Promise<boolean>
+
+always succeeds and returns true
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.md
new file mode 100644
index 00000000000..c7cab5b8f1c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md)
+
+## LexicalModelCompiler class
+
+Compiles a .model.ts file to a .model.js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class LexicalModelCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, \_options)](./kmc-model.lexicalmodelcompiler.init.md) | | Initialize the compiler. There are currently no options specific to the lexical model compiler |
+| [run(inputFilename, outputFilename)](./kmc-model.lexicalmodelcompiler.run.md) | | Compiles a .model.ts file to .model.js. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LexicalModelCompiler.init()](./kmc-model.lexicalmodelcompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-model.lexicalmodelcompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .model.js file - Javascript lexical model for web and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.run.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.run.md
new file mode 100644
index 00000000000..e96c22038a8
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) > [run](./kmc-model.lexicalmodelcompiler.run.md)
+
+## LexicalModelCompiler.run() method
+
+Compiles a .model.ts file to .model.js. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [LexicalModelCompiler.init()](./kmc-model.lexicalmodelcompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.write.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.write.md
new file mode 100644
index 00000000000..c3fd2f6934c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) > [write](./kmc-model.lexicalmodelcompiler.write.md)
+
+## LexicalModelCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .model.js file - Javascript lexical model for web and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: LexicalModelCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+always returns true
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.js.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.js.md
new file mode 100644
index 00000000000..99f75d6a584
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.js.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) > [js](./kmc-model.lexicalmodelcompilerartifacts.js.md)
+
+## LexicalModelCompilerArtifacts.js property
+
+Javascript model filedata and filename - installable into KeymanWeb, Keyman mobile products
+
+**Signature:**
+
+```typescript
+js: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.md
new file mode 100644
index 00000000000..903a3948a7d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md)
+
+## LexicalModelCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface LexicalModelCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [js](./kmc-model.lexicalmodelcompilerartifacts.js.md) | | KeymanCompilerArtifact | Javascript model filedata and filename - installable into KeymanWeb, Keyman mobile products |
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.artifacts.md
new file mode 100644
index 00000000000..2d4259416db
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md) > [artifacts](./kmc-model.lexicalmodelcompilerresult.artifacts.md)
+
+## LexicalModelCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [LexicalModelCompiler.write()](./kmc-model.lexicalmodelcompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: LexicalModelCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.md
new file mode 100644
index 00000000000..eb541fc46a9
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelcompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md)
+
+## LexicalModelCompilerResult interface
+
+Build artifacts from the lexical model compiler
+
+**Signature:**
+
+```typescript
+export interface LexicalModelCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-model.lexicalmodelcompilerresult.artifacts.md) | | [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [LexicalModelCompiler.write()](./kmc-model.lexicalmodelcompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.applycasing.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.applycasing.md
new file mode 100644
index 00000000000..fb8f9c11005
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.applycasing.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [applyCasing](./kmc-model.lexicalmodelsource.applycasing.md)
+
+## LexicalModelSource.applyCasing property
+
+Specifies the casing rules for a language. Should implement three casing forms: - 'lower' -- a fully-lowercased version of the text appropriate for the language's use of the writing system. - 'upper' -- a fully-uppercased version of the text - 'initial' -- a version preserving the input casing aside from the initial character, which is uppercased (like with proper nouns and sentence-initial words in English sentences.)
+
+This is only utilized if `languageUsesCasing` is defined and set to `true`. 14.0
+
+**Signature:**
+
+```typescript
+readonly applyCasing?: CasingFunction;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.languageusescasing.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.languageusescasing.md
new file mode 100644
index 00000000000..51d899d4748
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.languageusescasing.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [languageUsesCasing](./kmc-model.lexicalmodelsource.languageusescasing.md)
+
+## LexicalModelSource.languageUsesCasing property
+
+When set to `true`, suggestions will attempt to match the case of the input text even if the lexicon entries use a different casing scheme due to search term keying effects. 14.0
+
+**Signature:**
+
+```typescript
+readonly languageUsesCasing?: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.md
new file mode 100644
index 00000000000..f3f88cfd6fe
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md)
+
+## LexicalModelSource interface
+
+Base interface for a lexical model source definition
+
+**Signature:**
+
+```typescript
+export interface LexicalModelSource extends LexicalModelDeclaration
+```
+**Extends:** LexicalModelDeclaration
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [applyCasing?](./kmc-model.lexicalmodelsource.applycasing.md) | readonly
| CasingFunction | _(Optional)_ Specifies the casing rules for a language. Should implement three casing forms: - 'lower' -- a fully-lowercased version of the text appropriate for the language's use of the writing system. - 'upper' -- a fully-uppercased version of the text - 'initial' -- a version preserving the input casing aside from the initial character, which is uppercased (like with proper nouns and sentence-initial words in English sentences.)
This is only utilized if languageUsesCasing
is defined and set to true
. 14.0
|
+| [languageUsesCasing?](./kmc-model.lexicalmodelsource.languageusescasing.md) | readonly
| boolean | _(Optional)_ When set to true
, suggestions will attempt to match the case of the input text even if the lexicon entries use a different casing scheme due to search term keying effects. 14.0 |
+| [punctuation?](./kmc-model.lexicalmodelsource.punctuation.md) | readonly
| LexicalModelPunctuation | _(Optional)_ Punctuation and spacing suggested by the model. |
+| [rootClass?](./kmc-model.lexicalmodelsource.rootclass.md) | readonly
| string | _(Optional)_ The name of the type to instantiate (without parameters) as the base object for a custom predictive model. |
+| [searchTermToKey?](./kmc-model.lexicalmodelsource.searchtermtokey.md) | readonly
| [WordformToKeySpec](./kmc-model.wordformtokeyspec.md) | _(Optional)_ How to simplify words, to convert them into simplified search keys This often involves removing accents, lowercasing, etc. |
+| [sources](./kmc-model.lexicalmodelsource.sources.md) | readonly
| Array<string> | |
+| [wordBreaker?](./kmc-model.lexicalmodelsource.wordbreaker.md) | readonly
| [WordBreakerSpec](./kmc-model.wordbreakerspec.md) \| [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md) | _(Optional)_ Which word breaker to use. Choose from:
- 'default' -- breaks according to Unicode UAX \#29 §4.1 Default Word Boundary Specification, which works well for \*most\* languages. - 'ascii' -- a very simple word breaker, for demonstration purposes only. - word breaking function -- provide your own function that breaks words. - class-based word-breaker - may be supported in the future.
|
+
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.punctuation.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.punctuation.md
new file mode 100644
index 00000000000..be062d49237
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.punctuation.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [punctuation](./kmc-model.lexicalmodelsource.punctuation.md)
+
+## LexicalModelSource.punctuation property
+
+Punctuation and spacing suggested by the model.
+
+**Signature:**
+
+```typescript
+readonly punctuation?: LexicalModelPunctuation;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.rootclass.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.rootclass.md
new file mode 100644
index 00000000000..2e4371eed16
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.rootclass.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [rootClass](./kmc-model.lexicalmodelsource.rootclass.md)
+
+## LexicalModelSource.rootClass property
+
+The name of the type to instantiate (without parameters) as the base object for a custom predictive model.
+
+**Signature:**
+
+```typescript
+readonly rootClass?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.searchtermtokey.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.searchtermtokey.md
new file mode 100644
index 00000000000..ca9d0d8ae8e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.searchtermtokey.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [searchTermToKey](./kmc-model.lexicalmodelsource.searchtermtokey.md)
+
+## LexicalModelSource.searchTermToKey property
+
+How to simplify words, to convert them into simplified search keys This often involves removing accents, lowercasing, etc.
+
+**Signature:**
+
+```typescript
+readonly searchTermToKey?: WordformToKeySpec;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.sources.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.sources.md
new file mode 100644
index 00000000000..6d11c0d372f
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.sources.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [sources](./kmc-model.lexicalmodelsource.sources.md)
+
+## LexicalModelSource.sources property
+
+**Signature:**
+
+```typescript
+readonly sources: Array;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.wordbreaker.md b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.wordbreaker.md
new file mode 100644
index 00000000000..c8f9053d263
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.lexicalmodelsource.wordbreaker.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [LexicalModelSource](./kmc-model.lexicalmodelsource.md) > [wordBreaker](./kmc-model.lexicalmodelsource.wordbreaker.md)
+
+## LexicalModelSource.wordBreaker property
+
+Which word breaker to use. Choose from:
+
+- 'default' -- breaks according to Unicode UAX \#29 §4.1 Default Word Boundary Specification, which works well for \*most\* languages. - 'ascii' -- a very simple word breaker, for demonstration purposes only. - word breaking function -- provide your own function that breaks words. - class-based word-breaker - may be supported in the future.
+
+**Signature:**
+
+```typescript
+readonly wordBreaker?: WordBreakerSpec | SimpleWordBreakerSpec;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.md b/developer/docs/help/reference/api/kmc-model.md
new file mode 100644
index 00000000000..fa0d8688436
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.md
@@ -0,0 +1,30 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md)
+
+## kmc-model package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [LexicalModelCompiler](./kmc-model.lexicalmodelcompiler.md) | Compiles a .model.ts file to a .model.js. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [LexicalModelCompilerArtifacts](./kmc-model.lexicalmodelcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [LexicalModelCompilerResult](./kmc-model.lexicalmodelcompilerresult.md) | Build artifacts from the lexical model compiler |
+| [LexicalModelSource](./kmc-model.lexicalmodelsource.md) | Base interface for a lexical model source definition |
+| [WordBreakerSpec](./kmc-model.wordbreakerspec.md) | Keyman 14.0+ word breaker specification:
Can support all old word breaking specification, but can also be extended with options.
14.0
|
+
+## Type Aliases
+
+| Type Alias | Description |
+| --- | --- |
+| [CasedWordformToKeySpec](./kmc-model.casedwordformtokeyspec.md) | Simplifies input text to facilitate finding entries within a lexical model's lexicon, using the model's applyCasing
function to assist in the keying process. 14.0 |
+| [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md) | Simplified word breaker specification.
11.0
|
+| [SimpleWordformToKeySpec](./kmc-model.simplewordformtokeyspec.md) | Simplifies input text to facilitate finding entries within a lexical model's lexicon. 11.0 |
+| [WordformToKeySpec](./kmc-model.wordformtokeyspec.md) | Simplifies input text to facilitate finding entries within a lexical model's lexicon. |
+
diff --git a/developer/docs/help/reference/api/kmc-model.simplewordbreakerspec.md b/developer/docs/help/reference/api/kmc-model.simplewordbreakerspec.md
new file mode 100644
index 00000000000..420a61218ea
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.simplewordbreakerspec.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md)
+
+## SimpleWordBreakerSpec type
+
+Simplified word breaker specification.
+
+ 11.0
+
+**Signature:**
+
+```typescript
+export type SimpleWordBreakerSpec = 'default' | 'ascii' | WordBreakingFunction;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.simplewordformtokeyspec.md b/developer/docs/help/reference/api/kmc-model.simplewordformtokeyspec.md
new file mode 100644
index 00000000000..65e1cfb656d
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.simplewordformtokeyspec.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [SimpleWordformToKeySpec](./kmc-model.simplewordformtokeyspec.md)
+
+## SimpleWordformToKeySpec type
+
+Simplifies input text to facilitate finding entries within a lexical model's lexicon. 11.0
+
+**Signature:**
+
+```typescript
+export type SimpleWordformToKeySpec = (term: string) => string;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.joinwordsat.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.joinwordsat.md
new file mode 100644
index 00000000000..8a5c7d7e683
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.joinwordsat.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md) > [joinWordsAt](./kmc-model.wordbreakerspec.joinwordsat.md)
+
+## WordBreakerSpec.joinWordsAt property
+
+If present, joins words that were split by the word breaker together at the given strings. e.g.,
+
+joinWordsAt: \['-'\] // to keep hyphenated items together
+
+ 14.0
+
+**Signature:**
+
+```typescript
+readonly joinWordsAt?: string[];
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.md
new file mode 100644
index 00000000000..564f4ee8d57
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md)
+
+## WordBreakerSpec interface
+
+Keyman 14.0+ word breaker specification:
+
+Can support all old word breaking specification, but can also be extended with options.
+
+ 14.0
+
+**Signature:**
+
+```typescript
+export interface WordBreakerSpec
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [joinWordsAt?](./kmc-model.wordbreakerspec.joinwordsat.md) | readonly
| string\[\] | _(Optional)_ If present, joins words that were split by the word breaker together at the given strings. e.g.,
joinWordsAt: \['-'\] // to keep hyphenated items together
14.0
|
+| [overrideScriptDefaults?](./kmc-model.wordbreakerspec.overridescriptdefaults.md) | readonly
| OverrideScriptDefaults | _(Optional)_ Overrides word splitting behaviour for certain scripts. For example, specifing that spaces break words in certain South-East Asian scripts that otherwise do not use spaces.
14.0
|
+| [use](./kmc-model.wordbreakerspec.use.md) | readonly
| [SimpleWordBreakerSpec](./kmc-model.simplewordbreakerspec.md) | |
+
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.overridescriptdefaults.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.overridescriptdefaults.md
new file mode 100644
index 00000000000..4c21721cc9c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.overridescriptdefaults.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md) > [overrideScriptDefaults](./kmc-model.wordbreakerspec.overridescriptdefaults.md)
+
+## WordBreakerSpec.overrideScriptDefaults property
+
+Overrides word splitting behaviour for certain scripts. For example, specifing that spaces break words in certain South-East Asian scripts that otherwise do not use spaces.
+
+ 14.0
+
+**Signature:**
+
+```typescript
+readonly overrideScriptDefaults?: OverrideScriptDefaults;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordbreakerspec.use.md b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.use.md
new file mode 100644
index 00000000000..5e3844226ad
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordbreakerspec.use.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordBreakerSpec](./kmc-model.wordbreakerspec.md) > [use](./kmc-model.wordbreakerspec.use.md)
+
+## WordBreakerSpec.use property
+
+**Signature:**
+
+```typescript
+readonly use: SimpleWordBreakerSpec;
+```
diff --git a/developer/docs/help/reference/api/kmc-model.wordformtokeyspec.md b/developer/docs/help/reference/api/kmc-model.wordformtokeyspec.md
new file mode 100644
index 00000000000..5a2b4951a3e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-model.wordformtokeyspec.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-model](./kmc-model.md) > [WordformToKeySpec](./kmc-model.wordformtokeyspec.md)
+
+## WordformToKeySpec type
+
+Simplifies input text to facilitate finding entries within a lexical model's lexicon.
+
+**Signature:**
+
+```typescript
+export type WordformToKeySpec = SimpleWordformToKeySpec | CasedWordformToKeySpec;
+```
+**References:** [SimpleWordformToKeySpec](./kmc-model.simplewordformtokeyspec.md), [CasedWordformToKeySpec](./kmc-model.casedwordformtokeyspec.md)
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.init.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.init.md
new file mode 100644
index 00000000000..c1010a494ee
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [init](./kmc-package.kmpcompiler.init.md)
+
+## KmpCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: KmpCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.md
new file mode 100644
index 00000000000..f23b5025fa0
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md)
+
+## KmpCompiler class
+
+Compiles a .kps file to a .kmp archive. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class KmpCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [normalizePath](./kmc-package.kmpcompiler.normalizepath.md) | readonly
| (path: string) => string | |
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-package.kmpcompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-package.kmpcompiler.run.md) | | Compiles a .kps file to .kmp file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmpCompiler.init()](./kmc-package.kmpcompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-package.kmpcompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .kmp file - binary keyboard package used by Keyman on desktop and touch platforms
|
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.normalizepath.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.normalizepath.md
new file mode 100644
index 00000000000..cf754363740
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.normalizepath.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [normalizePath](./kmc-package.kmpcompiler.normalizepath.md)
+
+## KmpCompiler.normalizePath property
+
+**Signature:**
+
+```typescript
+readonly normalizePath: (path: string) => string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.run.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.run.md
new file mode 100644
index 00000000000..4b584dd7979
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [run](./kmc-package.kmpcompiler.run.md)
+
+## KmpCompiler.run() method
+
+Compiles a .kps file to .kmp file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [KmpCompiler.init()](./kmc-package.kmpcompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[KmpCompilerResult](./kmc-package.kmpcompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompiler.write.md b/developer/docs/help/reference/api/kmc-package.kmpcompiler.write.md
new file mode 100644
index 00000000000..c4878571df5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompiler](./kmc-package.kmpcompiler.md) > [write](./kmc-package.kmpcompiler.write.md)
+
+## KmpCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .kmp file - binary keyboard package used by Keyman on desktop and touch platforms
+
+**Signature:**
+
+```typescript
+write(artifacts: KmpCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.kmp.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.kmp.md
new file mode 100644
index 00000000000..612daab90f5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.kmp.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) > [kmp](./kmc-package.kmpcompilerartifacts.kmp.md)
+
+## KmpCompilerArtifacts.kmp property
+
+Binary keyboard package filedata and filename - installable into Keyman desktop and mobile projects
+
+**Signature:**
+
+```typescript
+kmp: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.md
new file mode 100644
index 00000000000..1a7f6f883ea
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md)
+
+## KmpCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface KmpCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [kmp](./kmc-package.kmpcompilerartifacts.kmp.md) | | KeymanCompilerArtifact | Binary keyboard package filedata and filename - installable into Keyman desktop and mobile projects |
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompileroptions.md b/developer/docs/help/reference/api/kmc-package.kmpcompileroptions.md
new file mode 100644
index 00000000000..763eb040004
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompileroptions.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md)
+
+## KmpCompilerOptions interface
+
+Options for the .kps compiler
+
+**Signature:**
+
+```typescript
+export interface KmpCompilerOptions extends CompilerOptions
+```
+**Extends:** CompilerOptions
+
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.artifacts.md
new file mode 100644
index 00000000000..4905af4ac7c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerResult](./kmc-package.kmpcompilerresult.md) > [artifacts](./kmc-package.kmpcompilerresult.artifacts.md)
+
+## KmpCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmpCompiler.write()](./kmc-package.kmpcompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: KmpCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.md b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.md
new file mode 100644
index 00000000000..3371a01f9ef
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.kmpcompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [KmpCompilerResult](./kmc-package.kmpcompilerresult.md)
+
+## KmpCompilerResult interface
+
+Build artifacts from the .kps compiler
+
+**Signature:**
+
+```typescript
+export interface KmpCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-package.kmpcompilerresult.artifacts.md) | | [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [KmpCompiler.write()](./kmc-package.kmpcompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-package.md b/developer/docs/help/reference/api/kmc-package.md
new file mode 100644
index 00000000000..53ab00c1907
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md)
+
+## kmc-package package
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| [KmpCompiler](./kmc-package.kmpcompiler.md) | Compiles a .kps file to a .kmp archive. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+| [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) | Compiles a .kps file to a .exe installer. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO. |
+
+## Interfaces
+
+| Interface | Description |
+| --- | --- |
+| [KmpCompilerArtifacts](./kmc-package.kmpcompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md) | Options for the .kps compiler |
+| [KmpCompilerResult](./kmc-package.kmpcompilerresult.md) | Build artifacts from the .kps compiler |
+| [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation |
+| [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md) | Options for the .kps Windows package installer compiler |
+| [WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md) | Build artifacts from the .kps Windows package installer compiler |
+| [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) | Sources and metadata for the Windows package installer compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.init.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.init.md
new file mode 100644
index 00000000000..160d3265414
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.init.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) > [init](./kmc-package.windowspackageinstallercompiler.init.md)
+
+## WindowsPackageInstallerCompiler.init() method
+
+Initialize the compiler. Copies options.
+
+**Signature:**
+
+```typescript
+init(callbacks: CompilerCallbacks, options: WindowsPackageInstallerCompilerOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| callbacks | CompilerCallbacks | Callbacks for external interfaces, including message reporting and file io |
+| options | [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md) | Compiler options |
+
+**Returns:**
+
+Promise<boolean>
+
+false if initialization fails
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.md
new file mode 100644
index 00000000000..c691fece706
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.md
@@ -0,0 +1,23 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md)
+
+## WindowsPackageInstallerCompiler class
+
+Compiles a .kps file to a .exe installer. The compiler does not read or write from filesystem or network directly, but relies on callbacks for all external IO.
+
+**Signature:**
+
+```typescript
+export declare class WindowsPackageInstallerCompiler implements KeymanCompiler
+```
+**Implements:** KeymanCompiler
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [init(callbacks, options)](./kmc-package.windowspackageinstallercompiler.init.md) | | Initialize the compiler. Copies options. |
+| [run(inputFilename, outputFilename)](./kmc-package.windowspackageinstallercompiler.run.md) | | Compiles a .kps file to .exe Windows package installer file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [WindowsPackageInstallerCompiler.init()](./kmc-package.windowspackageinstallercompiler.init.md) function to read any input files by disk. |
+| [write(artifacts)](./kmc-package.windowspackageinstallercompiler.write.md) | | Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
- .exe file - binary Windows package installer executable file
|
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.run.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.run.md
new file mode 100644
index 00000000000..1e79d3d9124
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.run.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) > [run](./kmc-package.windowspackageinstallercompiler.run.md)
+
+## WindowsPackageInstallerCompiler.run() method
+
+Compiles a .kps file to .exe Windows package installer file. Returns an object containing binary artifacts on success. The files are passed in by name, and the compiler will use callbacks as passed to the [WindowsPackageInstallerCompiler.init()](./kmc-package.windowspackageinstallercompiler.init.md) function to read any input files by disk.
+
+**Signature:**
+
+```typescript
+run(inputFilename: string, outputFilename?: string): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| inputFilename | string | |
+| outputFilename | string | _(Optional)_ |
+
+**Returns:**
+
+Promise<[WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md)>
+
+Binary artifacts on success, null on failure.
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.write.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.write.md
new file mode 100644
index 00000000000..487696c5292
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompiler.write.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompiler](./kmc-package.windowspackageinstallercompiler.md) > [write](./kmc-package.windowspackageinstallercompiler.write.md)
+
+## WindowsPackageInstallerCompiler.write() method
+
+Write artifacts from a successful compile to disk, via callbacks methods. The artifacts written may include:
+
+- .exe file - binary Windows package installer executable file
+
+**Signature:**
+
+```typescript
+write(artifacts: WindowsPackageInstallerCompilerArtifacts): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| artifacts | [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) | object containing artifact binary data to write out |
+
+**Returns:**
+
+Promise<boolean>
+
+true on success
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.exe.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.exe.md
new file mode 100644
index 00000000000..1fd3e8e522e
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.exe.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) > [exe](./kmc-package.windowspackageinstallercompilerartifacts.exe.md)
+
+## WindowsPackageInstallerCompilerArtifacts.exe property
+
+Binary package installer filedata and filename - installable into Keyman desktop and mobile projects
+
+**Signature:**
+
+```typescript
+exe: KeymanCompilerArtifact;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.md
new file mode 100644
index 00000000000..2a21d3a3116
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerartifacts.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md)
+
+## WindowsPackageInstallerCompilerArtifacts interface
+
+Internal in-memory build artifacts from a successful compilation
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerCompilerArtifacts extends KeymanCompilerArtifacts
+```
+**Extends:** KeymanCompilerArtifacts
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [exe](./kmc-package.windowspackageinstallercompilerartifacts.exe.md) | | KeymanCompilerArtifact | Binary package installer filedata and filename - installable into Keyman desktop and mobile projects |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.md
new file mode 100644
index 00000000000..b0aed5a5052
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md)
+
+## WindowsPackageInstallerCompilerOptions interface
+
+Options for the .kps Windows package installer compiler
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerCompilerOptions extends KmpCompilerOptions
+```
+**Extends:** [KmpCompilerOptions](./kmc-package.kmpcompileroptions.md)
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [sources](./kmc-package.windowspackageinstallercompileroptions.sources.md) | | [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) | Sources and metadata for the Windows package installer compiler |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.sources.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.sources.md
new file mode 100644
index 00000000000..de1b74dbd7a
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompileroptions.sources.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerOptions](./kmc-package.windowspackageinstallercompileroptions.md) > [sources](./kmc-package.windowspackageinstallercompileroptions.sources.md)
+
+## WindowsPackageInstallerCompilerOptions.sources property
+
+Sources and metadata for the Windows package installer compiler
+
+**Signature:**
+
+```typescript
+sources: WindowsPackageInstallerSources;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.artifacts.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.artifacts.md
new file mode 100644
index 00000000000..d1ea7e4a5a3
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.artifacts.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md) > [artifacts](./kmc-package.windowspackageinstallercompilerresult.artifacts.md)
+
+## WindowsPackageInstallerCompilerResult.artifacts property
+
+Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [WindowsPackageInstallerCompiler.write()](./kmc-package.windowspackageinstallercompiler.write.md)
+
+**Signature:**
+
+```typescript
+artifacts: WindowsPackageInstallerCompilerArtifacts;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.md
new file mode 100644
index 00000000000..de9701b148c
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallercompilerresult.md
@@ -0,0 +1,21 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerCompilerResult](./kmc-package.windowspackageinstallercompilerresult.md)
+
+## WindowsPackageInstallerCompilerResult interface
+
+Build artifacts from the .kps Windows package installer compiler
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerCompilerResult extends KeymanCompilerResult
+```
+**Extends:** KeymanCompilerResult
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [artifacts](./kmc-package.windowspackageinstallercompilerresult.artifacts.md) | | [WindowsPackageInstallerCompilerArtifacts](./kmc-package.windowspackageinstallercompilerartifacts.md) | Internal in-memory build artifacts from a successful compilation. Caller can write these to disk with [WindowsPackageInstallerCompiler.write()](./kmc-package.windowspackageinstallercompiler.write.md) |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.appname.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.appname.md
new file mode 100644
index 00000000000..ca6069c3802
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.appname.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [appName](./kmc-package.windowspackageinstallersources.appname.md)
+
+## WindowsPackageInstallerSources.appName property
+
+**Signature:**
+
+```typescript
+appName?: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.licensefilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.licensefilename.md
new file mode 100644
index 00000000000..d96a5a29757
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.licensefilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [licenseFilename](./kmc-package.windowspackageinstallersources.licensefilename.md)
+
+## WindowsPackageInstallerSources.licenseFilename property
+
+**Signature:**
+
+```typescript
+licenseFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.md
new file mode 100644
index 00000000000..0130266bad5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.md
@@ -0,0 +1,26 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md)
+
+## WindowsPackageInstallerSources interface
+
+Sources and metadata for the Windows package installer compiler
+
+**Signature:**
+
+```typescript
+export interface WindowsPackageInstallerSources
+```
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [appName?](./kmc-package.windowspackageinstallersources.appname.md) | | string | _(Optional)_ |
+| [licenseFilename](./kmc-package.windowspackageinstallersources.licensefilename.md) | | string | |
+| [msiFilename](./kmc-package.windowspackageinstallersources.msifilename.md) | | string | |
+| [setupExeFilename](./kmc-package.windowspackageinstallersources.setupexefilename.md) | | string | |
+| [startDisabled](./kmc-package.windowspackageinstallersources.startdisabled.md) | | boolean | |
+| [startWithConfiguration](./kmc-package.windowspackageinstallersources.startwithconfiguration.md) | | boolean | |
+| [titleImageFilename?](./kmc-package.windowspackageinstallersources.titleimagefilename.md) | | string | _(Optional)_ |
+
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.msifilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.msifilename.md
new file mode 100644
index 00000000000..f6cc53efbc1
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.msifilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [msiFilename](./kmc-package.windowspackageinstallersources.msifilename.md)
+
+## WindowsPackageInstallerSources.msiFilename property
+
+**Signature:**
+
+```typescript
+msiFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.setupexefilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.setupexefilename.md
new file mode 100644
index 00000000000..8e00113ffa5
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.setupexefilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [setupExeFilename](./kmc-package.windowspackageinstallersources.setupexefilename.md)
+
+## WindowsPackageInstallerSources.setupExeFilename property
+
+**Signature:**
+
+```typescript
+setupExeFilename: string;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startdisabled.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startdisabled.md
new file mode 100644
index 00000000000..89ba631c164
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startdisabled.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [startDisabled](./kmc-package.windowspackageinstallersources.startdisabled.md)
+
+## WindowsPackageInstallerSources.startDisabled property
+
+**Signature:**
+
+```typescript
+startDisabled: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startwithconfiguration.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startwithconfiguration.md
new file mode 100644
index 00000000000..6ec14bae7a6
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.startwithconfiguration.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [startWithConfiguration](./kmc-package.windowspackageinstallersources.startwithconfiguration.md)
+
+## WindowsPackageInstallerSources.startWithConfiguration property
+
+**Signature:**
+
+```typescript
+startWithConfiguration: boolean;
+```
diff --git a/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.titleimagefilename.md b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.titleimagefilename.md
new file mode 100644
index 00000000000..c663ba44d06
--- /dev/null
+++ b/developer/docs/help/reference/api/kmc-package.windowspackageinstallersources.titleimagefilename.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [@keymanapp/kmc-package](./kmc-package.md) > [WindowsPackageInstallerSources](./kmc-package.windowspackageinstallersources.md) > [titleImageFilename](./kmc-package.windowspackageinstallersources.titleimagefilename.md)
+
+## WindowsPackageInstallerSources.titleImageFilename property
+
+**Signature:**
+
+```typescript
+titleImageFilename?: string;
+```
diff --git a/developer/docs/help/reference/bcp-47.md b/developer/docs/help/reference/bcp-47.md
new file mode 100644
index 00000000000..f5c8a49539e
--- /dev/null
+++ b/developer/docs/help/reference/bcp-47.md
@@ -0,0 +1,113 @@
+---
+title: BCP 47 language tags
+---
+
+Packages and lexical models may reference a [BCP 47 language tag][1]. A BCP
+47 tag is a standard way of referencing a language, used widely in the computer
+industry. Keyman uses BCP 47 tags in a number of areas, including:
+
+* providing language metadata about installed keyboards to the operating system
+ and to applications, so that, for example, spell checking has the correct
+ language, through [package metadata](../guides/distribute/)
+* [linking lexical models to keyboard layouts](../guides/lexical-models/)
+* facilitating searches for keyboards and lexical models on the Keyman site,
+ through a [.keyboard_info file](/developer/cloud/keyboard_info)
+
+A BCP 47 language tag is made up of multiple subtags. There are many possible
+subtags, but only three types are currently used in most places in Keyman
+Developer:
+
+* [language subtag](#toc-the-language-subtag)
+* [script subtag](#toc-the-script-subtag)
+* [region subtag](#toc-the-region-subtag)
+
+BCP 47 tags are case insensitive, but there are conventions for casing which you
+should use for readability; see the subtag descriptions for details.
+
+The following are all examples of valid BCP 47 tags:
+
+* `en`: English
+* `en-US`: English, in United States
+* `km-Khmr-KH`: Khmer, written in the Khmer script, in Cambodia
+* `km-fonipa`: Khmer, transcribed in IPA
+
+### The language subtag
+
+The only required option is the Language subtag, which is an [ISO 639-1][2] or
+[ISO 639-3][3] code.
+
+ISO 639-1 tags are a two-letter code. ISO 639-3 tags are a three-letter code.
+First, try to find your language on the list of two-letter ISO 639-1 codes.
+[This Wikipedia page][4] lists all of the two-letter codes.
+
+If you can't find a two-letter code, you'll need to find the closest
+three-letter code. You can use [Glottolog][5] to search for your language, and
+it will give you an appropriate code. In this example, I searched Glottolog for
+“[Saanich][6]” (name of the First Nations that speak SENĆOŦEN) and found `str`
+as the code for all Straits Salish languages.
+
+The Language subtag is conventionally written in lower case.
+
+The next two subtags are **optional**, however, they allow you to be more
+specific about your language.
+
+### The script subtag
+
+The Script subtag allows you to specify the writing system used in your language
+model or keyboard. If your language only uses one writing system, omit the
+Script subtag.
+
+Otherwise, in cases where a language can be written in many different writing
+systems, you can choose the four letter [ISO 15924][7] script tag that your
+keyboard or lexical model produces.
+
+For example, Plains Cree can either be written in _standard Roman orthography_,
+a **Latin** derived script, or it can be written in _syllabics_, which is part
+of the **Canadian Aboriginal syllabics** family of writing systems. If I wrote a
+keyboard or lexical model that produced syllabics, I would choose `Cans`, as
+that is the **ISO 15924** tag for Canadian Aboriginal syllabics.
+
+The Script subtag is conventionally written in title case - first letter
+capitalized.
+
+### The region subtag
+
+The Region subtag allows you to specify the region your language or dialect is
+spoken in. If your language is only spoken in one region, omit the Region
+subtag.
+
+Otherwise, some languages vary between different regions and countries. In our
+example, SENĆOŦEN describes the language that covers entire W̱SÁNEĆ region, so
+this field may be left blank.
+
+However, large languages, like English, Spanish, or French have quite different
+vocabulary and even different grammatical rules from region to region and
+country to country. For example, the variety of Spanish spoken in Spain
+regularly uses words that are uncommon or even vulgar in both in Mexico, and in
+Latin America. Additionally, regions may have vocabulary that doesn't exist in
+the other regions where the language is spoken.
+
+If I were working with a language specific to one country, I would use the [ISO
+3166-1 alpha-2][8] country code for the region subtag. For example, `ES` for
+Spain or `MX` for Mexico.
+
+However, if I were working with Latin American Spanish (a group of countries), I
+would need to specify Latin America's [UN M49][9] region code. For Latin
+America, its code is `419`. My lexical model would not suggest words that are
+common in Spain, but vulgar in Latin America, however it would predict words
+like "pupupsas" and "chuchitos", which are words that are uncommon in both Spain
+and Mexico.
+
+Another common UN M49 region code is `001` for the whole world.
+
+Alphabetic region subtags are conventionally written in upper case.
+
+[1]: https://en.wikipedia.org/wiki/IETF_language_tag
+[2]: https://en.wikipedia.org/wiki/ISO_639-1
+[3]: https://en.wikipedia.org/wiki/ISO_639-3
+[4]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+[5]: https://glottolog.org/glottolog/language
+[6]: https://glottolog.org/resource/languoid/id/saan1246
+[7]: https://en.wikipedia.org/wiki/ISO_15924
+[8]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+[9]: https://en.wikipedia.org/wiki/UN_M49
diff --git a/developer/docs/help/reference/editor-themes.md b/developer/docs/help/reference/editor-themes.md
new file mode 100644
index 00000000000..3a624c923fc
--- /dev/null
+++ b/developer/docs/help/reference/editor-themes.md
@@ -0,0 +1,46 @@
+---
+title: Custom Editor Themes
+---
+
+Keyman Developer embeds the Monaco open source text editor. The
+highlighting and styles for the text editor are customisable with a JSON
+file configured in the [Options dialog](../context/options). The JSON
+file must be a valid [Monaco theme](https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-tokens-and-colors)
+(passed as second parameter to `monaco.editor.defineTheme`).
+
+## Example
+
+```js
+{
+ "base": "vs-dark",
+ "inherit": true,
+ "rules": [
+ { "token": "comment", "foreground": "ffa500", "background": "303030", "fontStyle": "italic underline" },
+ { "token": "comment.js", "foreground": "008800", "fontStyle": "bold" },
+ { "token": "comment.css", "foreground": "0000ff", "fontStyle": "bold", "inherit": false, "background": "808080" }
+ ]
+}
+```
+
+## Keyman keyboard language tokens
+
+The Keyman keyboard language highlighting uses the following token
+names:
+
+| Token | Source Element |
+|----------------|-------------------------------------------------|
+| annotation | System store names |
+| bracket | |
+| comment | Comments |
+| identifier | Store and Group names |
+| invalid | Invalid code |
+| keyword | Keywords such as `begin`, `any` |
+| number | Decimal character codes, deprecated, e.g. `d65` |
+| number.hex | Unicode character codes, e.g. `U+1234` |
+| number.octal | Character codes expressed in octal |
+| operator | `+`, `>` and other operators |
+| string | Strings |
+| string.invalid | Unterminated strings |
+| string.quote | `'` and `"` characters |
+| tag | Virtual keys |
+| white | |
\ No newline at end of file
diff --git a/developer/docs/help/reference/errors/index.md b/developer/docs/help/reference/errors/index.md
new file mode 100644
index 00000000000..c5e8acc3680
--- /dev/null
+++ b/developer/docs/help/reference/errors/index.md
@@ -0,0 +1,4 @@
+---
+title: Errors
+redirect: ../messages
+---
diff --git a/developer/docs/help/reference/file-layout.md b/developer/docs/help/reference/file-layout.md
new file mode 100644
index 00000000000..e466d7c05b0
--- /dev/null
+++ b/developer/docs/help/reference/file-layout.md
@@ -0,0 +1,131 @@
+---
+title: Keyman Developer structure for standard keyboard source file folders
+---
+
+Keyman Developer generates keyboard project files in a standard layout. [Keyman
+Cloud](/developer/keyboards/) uses this file layout. It is highly recommended to
+adhere to this file layout.
+
+## About the source files
+
+When you create and build a project, you'll end up with the following structure.
+Some files may be not be present, depending on the targets you specify.
+
+```plain
+sample
+│ HISTORY.md
+│ LICENSE.md
+│ README.md
+│ sample.kpj
+│ sample.kpj.user
+│
+├───build
+| sample.keyboard_info
+│ sample.kmp
+│ sample.kmx
+│ sample.kvk
+│ sample.js
+│
+└───source
+ readme.htm
+ sample.keyman-touch-layout
+ sample.kmn
+ sample.kps
+ sample.kvks
+ welcome.htm
+```
+
+Now that probably seems like a whole lot of files! But each file has a purpose.
+
+### Root folder
+
+`sample.kpj`
+: The main project file. This contains references to all the components:
+ keyboards, models, and packages.
+
+`sample.kpj.user`
+
+: A user preference file. You can safely delete it at any time (you'll lose some
+ remembered settings from Keyman Developer, but nothing consequential) and it
+ should not be shared in a git repository.
+
+Some of these files are metadata files: information about your keyboard project
+that will help you and others maintain your keyboard project in the future.
+
+For example, you may wish to make your keyboard project into a git repository
+and push it an online public git host such as GitHub or GitLab.
+
+`README.md`
+: Provides an introduction to the keyboard project when others stumble across
+ it. This file is in [Markdown](https://commonmark.org) format.
+
+`LICENSE.md`
+: Explains the rights that you give to others (by default, it will be the MIT
+ license -- which is what we require for Keyman Cloud -- but you can change
+ that to any license you wish). This file is in
+ [Markdown](https://commonmark.org) format.
+
+`HISTORY.md`
+: A place to record changes you make to the project over time. This file is in
+[Markdown](https://commonmark.org) format.
+
+* Note: Keyman Developer 16 included a .keyboard_info file in this folder. This
+ file is no longer required, as the required metadata can be generated from
+ the keyboard source files instead.
+
+### Source folder
+
+The files in the source folder are more fully described in the Keyman Developer
+[user guide](../..).
+
+[`source/sample.kmn`](/developer/language)
+: Your keyboard source. It will compile to a `.kmx` (desktop targets) or a `.js`
+ (web / touch targets).
+
+[`source/sample.kps`](../reference/file-types/kps)
+: The package source, which is used to create a compressed `.kmp` archive of the
+ files needed for distribution.
+
+`source/readme.htm`
+: Introductory web page for end users of your keyboard, shown before they
+ install it, so they know what it is used for.
+
+`source/welcome.htm`
+: A web page which describes how to use your keyboard, ideally with examples.
+
+[`source/sample.keyman-touch-layout`](../reference/file-types/keyman-touch-layout)
+: This touch layout file description is in JSON format and most easily visually
+ edited with the Keyman Developer touch layout editor. This file is optional;
+ remove the reference from the keyboard source if you don't wish to use it.
+
+[`source/sample.kvks`](../reference/file-types/kvks):
+: The on screen keyboard template for desktop platforms. It is in XML format and
+ most easily visually edited with the Keyman Developer on screen keyboard
+ editor. This file is optional; remove the reference from the keyboard source
+ if you don't wish to use it.
+
+### Build folder
+
+All of the files in the build folder are generated by the `kmc` compiler. None
+of these files should be included in a git repository.
+
+`build/sample.keyboard_info`
+: A [metadata file](/developer/cloud/keyboard_info) detailing the
+ keyboard's origin, version, requirements, and capabilities.
+ This file is built automatically by the kmc compiler. Please see
+ [.keyboard_info specification](/developer/cloud/keyboard_info) for more
+ details.
+
+`build/sample.kmp`
+: The installable package file -- installable in all Keyman end user products
+
+`build/sample.js`
+: The keyboard compiled to Javascript for use with KeymanWeb
+
+`build/sample.kvk`
+: The compiled on screen keyboard. This intermediate file should not be
+ distributed; it is included in the compiled package.
+
+`build/sample.kmx`
+: The compiled keyboard. This intermediate file should not be distributed; it is
+ included in the compiled package.
diff --git a/developer/docs/help/reference/file-types/ico.md b/developer/docs/help/reference/file-types/ico.md
new file mode 100644
index 00000000000..33331318781
--- /dev/null
+++ b/developer/docs/help/reference/file-types/ico.md
@@ -0,0 +1,20 @@
+---
+title: ICO files
+---
+
+Used by:
+: Keyman Desktop ,
+ Keyman Developer .
+
+Description:
+: An .ICO file is a standard Windows
+ [icon](https://en.wikipedia.org/wiki/ICO_(file_format)) file.
+
+Details:
+: An .ICO file is an icon which is used in the user interface to
+ indicate that the keyboard is active. See the [bitmap
+ store](../../../language/reference/bitmap)
+
+Distributed with keyboard:
+: An .ICO file is associated with a specific keyboard. A generic
+ keyboard icon will be displayed if no icon is specified.
diff --git a/developer/docs/help/reference/file-types/index.md b/developer/docs/help/reference/file-types/index.md
new file mode 100644
index 00000000000..a4a47591bf4
--- /dev/null
+++ b/developer/docs/help/reference/file-types/index.md
@@ -0,0 +1,45 @@
+---
+title: File Types
+---
+
+[ICO files](ico)
+: Keyboard icon files (binary file format)
+
+[KMN files](kmn)
+: Keyboard source files (text file format)
+
+[KMP files](kmp)
+: Keyboard package files (zip file format)
+
+[KMX files](kmx)
+: Keyboard files (binary file format)
+
+[KPJ files](kpj)
+: Keyman Developer Project files (XML file format)
+
+[KPS files](kps)
+: Keyboard package source files (XML file format)
+
+[KVK files](kvk)
+: Visual keyboard files (binary file format)
+
+[KVKS files](kvks)
+: Visual keyboard source files (XML file format)
+
+[MODEL.JS files](model-js)
+: Lexical model files (text file format)
+
+[MODEL.TS files](model-ts)
+: Lexical model definition files (text file format)
+
+[TSV files](tsv)
+: Word list source files (tab-separated values file format)
+
+[TTF files](ttf)
+: TrueType font files (binary file format)
+
+[keyman-touch-layout files](keyman-touch-layout)
+: Keyman touch layout source files (JSON file format)
+
+[XML files](xml)
+: LDML keyboard source files (XML file format)
diff --git a/developer/docs/help/reference/file-types/keyman-touch-layout.md b/developer/docs/help/reference/file-types/keyman-touch-layout.md
new file mode 100644
index 00000000000..8fa7312808e
--- /dev/null
+++ b/developer/docs/help/reference/file-types/keyman-touch-layout.md
@@ -0,0 +1,847 @@
+---
+title: keyman-touch-layout files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .keyman-touch-layout file is a JSON format file that describes a
+ keyboard layout for touch devices.
+
+Details:
+: Referenced by a Keyman keyboard ([.KMN](kmn)) and compiles into the
+ target keyboard .js file.
+
+Distributed with keyboard:
+: This is a keyboard development file and should not be distributed
+ with your package.
+
+---
+
+## Sample JSON
+
+This cut-down sample shows just a single key, with the key cap "ឆ", and a single longpress key under it with the key cap "ឈ".
+
+```json
+{
+ "phone": {
+ "displayUnderlying": false,
+ "font": "Khmer Busra Kbd",
+ "fontsize": "0.8em",
+ "layer": [ {
+ "id": "default",
+ "row": [ {
+ "id": 1,
+ "key": [ {
+ "id": "K_Q", "text": "ឆ",
+ "sk": [ { "text": "ឈ", "id": "K_Q", "layer": "shift" } ]
+ } ]
+ } ]
+ } ]
+ }
+}
+```
+
+
+
+
+
+## Key properties
+
+For each visual key, the appearance and behaviour is determined by a number of
+properties:
+
+### Key code
+
+Each key must be given an identifying key code which is unique to the key layer.
+Key codes by and large correspond to the virtual key codes used when creating a
+keyboard program for a desktop keyboard, and should start with `K_`, for keys
+mapped to standard Keyman virtual key names, e.g. `K_HYPHEN`, and `T_` or `U_`
+for user-defined names, e.g. `T_ZZZ`. If keyboard rules exist matching the key
+code in context, then the output from the key will be determined by the
+processing of those rules. It is usually best to include explicit rules to
+manage the output from each key, but if no rules matching the key code are
+included in the keyboard program, and the key code matches the pattern
+`U_xxxx[_yyyy...]` (where `xxxx` and
+`yyyy` are 4 to 6-digit hex strings), then the Unicode characters
+`U+xxxx` and `U+yyyy` will be output. As of Keyman 15, you
+can use more than one Unicode character value in the id (earlier versions
+permitted only one). The key code is always required, and a default code will
+usually be generated automatically by Keyman Developer.
+
+- `K_xxxx` is used for a standard Keyman Desktop key name, e.g.
+ `K_W`, `K_ENTER`. You cannot make up your own `K_xxxx` names.
+ Many of the `K_` ids have overloaded output behaviour, for instance, if no
+ rule is matched for `K_W`, Keyman will output 'w' when it is touched. The
+ standard key names are listed in [Virtual Keys and Virtual Character
+ Keys](/developer/language/guide/virtual-keys "Virtual Keys and Virtual
+Character Keys"). Typically, you would use only the "common" virtual key
+ codes.
+
+- `T_xxxx` is used for any user defined names, e.g. `T_SCHWA`. If you wanted
+ to use it, `T_ENTER` would also be valid. If no rule matches it, the key
+ will have no output behaviour.
+
+- `U_####[_####]` is used as a shortcut for a key that will output those
+ Unicode values, if no rule matches it. This is similar to the overloaded
+ behaviour for `K_` ids. Thus `####` must be valid Unicode characters. E.g.
+ `U_0259` would generate a schwa if no rule matches. It is still valid to
+ have a rule such as `+ [U_0259] > ...`
+
+As noted above, some `K_xxxx` codes emit characters, if no rule is defined.
+There are also some codes which have special functions:
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_ENTER`
+ Submit a form, or add a new line (multi-line); the key action may vary depending on the situation.
+
+
+ `K_BKSP`
+ Delete back a single character. This key, if held down, will repeat. It is the only key code which triggers
+ repeat behavior.
+
+
+ `K_LOPT`
+ Open the language menu (aka Globe key).
+
+
+ `K_ROPT`
+ Hide the on screen keyboard.
+
+
+ `K_TAB`, `K_TABBACK`, `K_TABFWD`
+ Move to next or previous element in a form. Note that these key functions are normally
+ implemented outside the touch layout, so should not typically be used. `K_TAB` will go to previous
+ element if used with the `shift` modifier.
+
+
+
+
+Any key can be used to switch keyboard layers (see
+[`nextlayer`](#toc-nextlayer)), but the following layer-switching key codes have
+been added for switching to some commonly used secondary layers. Note that these
+keys have no specific meaning; you must still set the `nextlayer` property on
+the key.
+
+
+
+
+ Identifier
+ Meaning
+
+
+
+
+ `K_NUMERALS`
+ Switch to a numeric layer
+
+
+ `K_SYMBOLS`
+ Switch to a symbol layer
+
+
+ `K_CURRENCIES`
+ Switch to a currency layer
+
+
+ `K_SHIFTED`
+ Switch to a shift layer
+
+
+ `K_ALTGR`
+ Switch to a right-alt layer (desktop compatibility)
+
+
+
+
+### Key text
+
+The key text is simply the character (or characters) that you want to appear on
+the key cap. This will usually be the same as the characters generated when the
+key is touched, unless contextual rules are used to generate output according to
+a multi-key sequence, as will be true for the GFF Amharic keyboard. Unicode
+characters can be specified either as a string using a target font or using the
+standard hex notation `\uxxxx`. This may be sometimes more convenient, for
+example, for characters from an uninstalled font, or for diacritic characters
+that do not render well alone.
+
+A number of special text labels are recognized as identifying special purpose
+keys, such as Shift, Backspace, Enter, etc., for which icons are more
+appropriately used than a text label. A special font including these icons is
+included with Keyman and automatically embedded and used in any web page using
+Keyman. The list of icons in the font will probably be extended in future, but
+for now the following special labels are recognized:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Shift*`
+
+ Select Shift layer (inactive). Use on the Shift key to indicate that it switches to the shift layer.
+
+
+ `*Shifted*`
+
+ Select Shift layer (active). Use on the Shift key on the shift layer to switch back to the default layer.
+
+
+ `*ShiftLock*`
+
+ Switch to Caps layer (inactive). Not commonly used; generally double-tap on Shift key is used to access the
+ caps layer.
+
+
+ `*ShiftedLock*`
+
+ Switch to Caps layer (active). Use on the Shift key on the caps layer to switch back to the default layer.
+
+
+
+ `*Enter*`
+ or
+ Return or Enter key (shape determined by writing system direction)
+
+
+ `*LTREnter*`
+
+ Return or Enter key (left-to-right script shape)
+
+
+ `*RTLEnter*`
+
+ Return or Enter key (right-to-left script shape)
+
+
+ `*BkSp*`
+ or
+ Backspace key (shape determined by writing system direction)
+
+
+ `*LTRBkSp*`
+
+ Backspace key (left-to-right script shape)
+
+
+ `*RTLBkSp*`
+
+ Backspace key (right-to-left script shape)
+
+
+ `*Menu*`
+
+ Globe key; display the language menu. Use on the `K_LOPT` key.
+
+
+ `*Hide*`
+
+ Hide the on screen keyboard. Use on the `K_ROPT` key.
+
+
+ `*ABC*`
+
+ Select alphabetic layer (Uppercase)
+
+
+ `*abc*`
+
+ Select alphabetic layer (Lowercase)
+
+
+ `*123*`
+
+ Select the numeric layer
+
+
+ `*Symbol*`
+
+ Select the symbol layer
+
+
+ `*Currency*`
+
+ Select the currency symbol layer
+
+
+ `*ZWNJ*`
+ (iOS) or (Android)
+ Zero Width Non Joiner (shape determined by current platform)
+
+
+ `*ZWNJiOS*`
+
+ Zero Width Non Joiner (iOS style shape)
+
+
+ `*ZWNJAndroid*`
+
+ Zero Width Non Joiner (Android style shape)
+
+
+ `*ZWNJGeneric*`
+
+ Zero Width Non Joiner (not platform-specific)
+
+
+ `*Sp*`
+
+ Regular space
+
+
+ `*NBSp*`
+
+ No-Break Space
+
+
+ `*NarNBSp*`
+
+ Narrow No-Break Space
+
+
+ `*EnQ*`
+
+ En Quad
+
+
+ `*EmQ*`
+
+ Em Quad
+
+
+ `*EnSp*`
+
+ En Space
+
+
+ `*EmSp*`
+
+ Em Space
+
+
+ `*PunctSp*`
+
+ Punctuation Space
+
+
+ `*ThSp*`
+
+ Thin Space
+
+
+ `*HSp*`
+
+ Hair Space
+
+
+ `*ZWSp*`
+
+ Zero Width Space
+
+
+ `*ZWJ*`
+
+ Zero Width Joiner
+
+
+ `*WJ*`
+
+ Word Joiner
+
+
+ `*CGJ*`
+
+ Combining Grapheme Joiner
+
+
+ `*LTRM*`
+
+ Left-to-right Mark
+
+
+ `*RTLM*`
+
+ Right-to-left Mark
+
+
+ `*SH*`
+
+ Soft Hyphen
+
+
+ `*HTab*`
+
+ Horizontal Tabulation
+
+
+
+
+
+The following additional symbols are also available, but intended for working
+with legacy desktop layouts, and not recommended for general use:
+
+
+
+
+ Text String
+ Key Cap
+ Key Purpose
+
+
+
+
+ `*Tab*`
+
+ Move to next input element in tab order
+
+
+ `*TabLeft*`
+
+ Move to previous input element in tab order
+
+
+ `*Caps*`
+
+ Select caps layer (legacy)
+
+
+ `*AltGr*`
+
+ Select AltGr (Right-Alt) layer (desktop layout compatibility)
+
+
+ `*Alt*`
+
+ Select Alt layer (desktop layout compatibility)
+
+
+ `*Ctrl*`
+
+ Select Ctrl layer (desktop layout compatibility)
+
+
+ `*LAlt*`
+
+ Select Left-Alt layer (desktop layout compatibility)
+
+
+ `*RAlt*`
+
+ Select Right-Alt layer (desktop layout compatibility)
+
+
+ `*LCtrl*`
+
+ Select Left-Ctrl layer (desktop layout compatibility)
+
+
+ `*RCtrl*`
+
+ Select Right-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrl*`
+
+ Select Left-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*RAltCtrl*`
+
+ Select Right-Alt-Ctrl layer (desktop layout compatibility)
+
+
+ `*LAltCtrlShift*`
+
+ Select Left-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RAltCtrlShift*`
+
+ Select Right-Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltShift*`
+
+ Select Alt-Shift layer (desktop layout compatibility)
+
+
+ `*CtrlShift*`
+
+ Select Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*AltCtrlShift*`
+
+ Select Alt-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*LAltShift*`
+
+ Select Left-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*RAltShift*`
+
+ Select Right-Alt-Shift layer (desktop layout compatibility)
+
+
+ `*LCtrlShift*`
+
+ Select Left-Ctrl-Shift layer (desktop layout compatibility)
+
+
+ `*RCtrlShift*`
+
+ Select Right-Ctrl-Shift layer (desktop layout compatibility)
+
+
+
+
+### Key type
+
+The general appearance of each key is determined by the key type, which is
+selected (in Keyman Developer) from a drop-down list. While generally behavior
+is not impacted by the key type, Spacer keys cannot be selected.
+
+
+
+
+ Key Type
+ Value
+ Meaning
+
+
+
+
+ Default
+ `0`
+ Any normal key that emits a character
+
+
+ Special
+ `1`
+ The frame keys such as Shift, Enter, BkSp.
+
+
+ Special (active)
+ `2`
+ A frame key which is currently active, such as the Shift key on the shift layer.
+
+
+ Deadkey
+ `8`
+ Does not impact behavior, but colors the key differently to indicate it has a special function, such as a
+ desktop-style deadkey.
+
+
+ Blank
+ `9`
+ A blank key, which may be used to maintain a layout shape. Usually colored differently. Does not impact
+ behavior.
+
+
+ Spacer
+ `10`
+ Does not render the key, but leaves a same-sized gap in its place. The key cannot be selected.
+
+
+
+
+The colour, shading and borders of each key type is actually set by a style
+sheet which can be customized by the page developer.
+
+### font-family
+
+If a different font is required for a particular key text, the `font-family`
+name can be specified. The font used to display icons for the special keys (as
+mentioned above) does not need to be specified, as it will be automatically
+applied to a key that uses any of the special key text labels.
+
+### font-size
+
+If a particular key cap text requires a different font size from the default for
+the layout, it should be specified in em units. This can be helpful if a the key
+text is either an unusually large character or, alternatively, a word or string
+of several characters that would not normally fit on the key.
+
+### width
+
+The layout is scaled to fit the widest row of keys in the device width, assuming
+a default key width of 100 units. Keys that are to be wider or narrower than the
+default width should have width specified as a percentage of the default width.
+For any key row that is narrower than the widest row, the width of the last key
+in the row will be automatically increased to align the right hand side of the
+key with the key with the right edge of the keyboard. However, where this is not
+wanted, a "spacer" key can be inserted to leave a visible space instead. As
+shown in the above layouts, where the spacer key appears on the designer screen
+as a narrow key, but will not be visible in actual use.
+
+### pad
+
+Padding to the left of each key can be adjusted, and specified as a percentage
+of the default key width. If not specified, a standard padding of 5% of the key
+width is used between adjacent keys.
+
+### layer
+
+To simplify correspondence with desktop keyboards and avoid the need for using a
+separate keyboard mapping program, touch layout keys can specify a desktop
+keyboard layer that the keystroke should be interpreted as coming from. Layer
+names of `shift`, `ctrl`, `alt`, `ctrlshift`, `altshift`, `ctrlalt` and
+`ctrlaltshift` can be used to simulate use of the appropriate modifier keys when
+processing rules.
+
+### nextlayer
+
+The virtual keys `K_SHIFT`, `K_CONTROL`, `K_MENU`, etc. are normally used to
+switch to another key layer, which is implied by the key code. The left and
+right variants of those key codes, and also additional layer-switching keys
+mentioned above (`K_NUMERALS`, `K_SYMBOLS`, `K_CURRENCIES`, `K_ALTGR`) can also
+be used to automatically switch to the appropriate key layer instead of
+outputting a character. However, it is sometimes useful for a key to output a
+character first, then switch to a new layer, for example, switching back to the
+default keyboard layer after a punctuation key on a secondary layer had been
+used. Specifying the `nextlayer` for a key allows a different key layer to be
+selected automatically following the output of the key. Of course, that can be
+manually overridden by switching to a different layer if preferred.
+
+Another way the `nextlayer` property can be used is for a non-standard layer
+switching key. So, for example, for the GFF Amharic keyboard phone layout,
+switching back to the base layer uses a `T_ALPHA` key code, in which `nextlayer`
+is set as default. In this case, it is also necessary to add a rule to the
+keyboard program:
+
+```keyman
++ [T_ALPHA] > nul
+```
+
+to ensure that the key's scan code is ignored by the keyboard mapping.
+
+When a key in a touch layout definition includes a **Next Layer** control, this
+takes precedence over setting layer via the
+[`layer`](/developer/language/reference/layer) store (as the **Next Layer**
+control is applied once the rule has finished processing).
+
+### subkey
+
+Arrays of longpress 'subkeys' or pop-up keys can be defined for any key, and
+will appear momentarily after the key is touched if not immediately released.
+This provides a major advantage over physical desktop keyboards in that many
+more keys can be made available from a single layer, without cluttering up the
+basic appearance of the layout. For the GFF Amharic keyboard, we have already
+noted how such subkey arrays are used to manage the extra keys that, on the
+desktop keyboard, would appear in the shift layer. But they are also used to
+provide another way to enter the two different types of each syllable-initial
+vowels (glottal or pharyngeal), as a visual alternative to pressing the key
+twice.
+
+The same properties that are defined for standard keys can also be specified for
+each subkey except that the width of each key in a subkey array will always be
+the same as the width of the key that causes the subkeys to be shown, and key
+spacing always uses the default padding value.
+
+The GFF Amharic keyboard, like many others, is mnemonic, so it is useful to also
+display the standard key cap letter that would appear on the key of a desktop
+keyboard. This is enabled globally in the On-Screen layout editor and applies to
+both the On-Screen keyboard and touch layouts.
+
+## JSON Schema
+
+```json
+{
+ "$schema": "http://json-schema.org/schema#",
+ "$ref": "#/definitions/touch-layout",
+ "$comment": "Version: 16.0",
+ "description": "A Keyman Touch Layout file, per version 16.0, clean spec, no legacy data or types",
+
+ "definitions": {
+ "touch-layout": {
+ "type": "object",
+ "properties": {
+ "tablet": { "$ref": "#/definitions/platform" },
+ "phone": { "$ref": "#/definitions/platform" },
+ "desktop": { "$ref": "#/definitions/platform" }
+ },
+ "minProperties": 1,
+ "additionalProperties": false
+ },
+
+ "platform": {
+ "type": "object",
+ "properties": {
+ "font": { "$ref": "#/definitions/font-spec" },
+ "fontsize": { "$ref": "#/definitions/fontsize-spec" },
+ "layer": { "$ref": "#/definitions/layers" },
+ "displayUnderlying": { "type": "boolean" },
+ "defaultHint": { "type": "string", "enum": ["none","dot","longpress","multitap","flick","flick-n","flick-ne","flick-e","flick-se","flick-s","flick-sw","flick-w","flick-nw"] }
+ },
+ "required": ["layer"],
+ "additionalProperties": false
+ },
+
+ "layers": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/layer" },
+ "minItems": 1
+ },
+
+ "layer": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/layer-id" },
+ "row": { "$ref": "#/definitions/rows" }
+ },
+ "required": ["id", "row"],
+ "additionalProperties": false
+ },
+
+ "layer-id": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9_-]+$"
+ },
+
+ "rows": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/row" },
+ "minItems": 1
+ },
+
+ "row": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/row-id" },
+ "key": { "$ref": "#/definitions/keys" }
+ },
+ "required": ["id", "key"],
+ "additionalProperties": false
+ },
+
+ "row-id": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 100
+ },
+
+ "keys": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/key" },
+ "minItems": 1
+ },
+
+ "key": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/key-id" },
+ "text": { "type": "string" },
+ "layer": { "$ref": "#/definitions/layer-id" },
+ "nextlayer": { "$ref": "#/definitions/layer-id" },
+ "font": { "$ref": "#/definitions/font-spec" },
+ "fontsize": { "$ref": "#/definitions/fontsize-spec" },
+ "sp": { "$ref": "#/definitions/key-sp" },
+ "pad": { "$ref" : "#/definitions/key-pad" },
+ "width": { "$ref" : "#/definitions/key-width" },
+ "sk": { "$ref": "#/definitions/subkeys" },
+ "flick": { "$ref": "#/definitions/flick" },
+ "multitap": { "$ref": "#/definitions/subkeys" },
+ "hint": { "type": "string" }
+ },
+ "anyOf": [
+ {"required": ["id"]},
+ {"required": ["sp"]},
+ {"required": ["sk"]},
+ {"required": ["flick"]},
+ {"required": ["multitap"]}
+ ],
+ "additionalProperties": false
+ },
+
+ "key-id": {
+ "type": "string",
+ "pattern": "^[TKUtku]_[a-zA-Z0-9_]+$"
+ },
+
+ "key-sp": {
+ "type": "integer",
+ "enum": [0, 1, 2, 8, 9, 10]
+ },
+
+ "key-pad": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 100000
+ },
+
+ "key-width": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 100000
+ },
+
+ "subkeys": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/subkey" },
+ "minItems": 1
+ },
+
+ "subkey": {
+ "type": "object",
+ "properties": {
+ "id": { "$ref": "#/definitions/key-id" },
+ "text": { "type": "string" },
+ "layer": { "$ref": "#/definitions/layer-id" },
+ "nextlayer": { "$ref": "#/definitions/layer-id" },
+ "font": { "$ref": "#/definitions/font-spec" },
+ "fontsize": { "$ref": "#/definitions/fontsize-spec" },
+ "sp": { "$ref": "#/definitions/key-sp" },
+ "pad": { "$ref" : "#/definitions/key-pad" },
+ "width": { "$ref" : "#/definitions/key-width" }
+ },
+ "required": ["id"],
+ "additionalProperties": false
+ },
+
+ "flick": {
+ "type": "object",
+ "patternProperties": {
+ "^(n|s|e|w|ne|nw|se|sw)$": { "$ref": "#/definitions/subkey" }
+ },
+ "minProperties": 1,
+ "additionalProperties": false
+ },
+
+ "font-spec": {
+ "type": "string"
+ },
+
+ "fontsize-spec": {
+ "type": "string"
+ }
+ }
+}
+```
diff --git a/developer/docs/help/reference/file-types/kmn.md b/developer/docs/help/reference/file-types/kmn.md
new file mode 100644
index 00000000000..0bf386607ce
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kmn.md
@@ -0,0 +1,21 @@
+---
+title: KMN files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KMN file is a keyboard source file. This holds all the code used
+ by a Keyman Desktop keyboard, in plain text.
+
+Details:
+: Compiles into a Keyman keyboard ([.KMX](kmx)) file.
+
+Distributed with keyboard:
+: This is a keyboard development file and should only be distributed
+ with your package if you want to make the keyboard source code
+ available.
+
+Reference:
+: [Keyman keyboard language](/developer/language/)
diff --git a/developer/docs/help/reference/file-types/kmp.md b/developer/docs/help/reference/file-types/kmp.md
new file mode 100644
index 00000000000..034de23b4d5
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kmp.md
@@ -0,0 +1,39 @@
+---
+title: KMP files
+---
+
+Used by:
+: All Keyman and custom Keyman Engine products can use keyboard
+ packages.
+: Keyman for Android and Keyman for iPhone and iPad can use lexical
+ model packages.
+
+Description:
+: A .KMP file is a Keyman Package file for distributing keyboards or
+ lexical models.
+
+Details:
+: A .KMP file is compiled from a Keyman Package source file
+ ([.KPS](kps)) using the Package Editor in
+ Keyman Developer . Normal contents
+ of a Keyman keyboard Package are one or more keyboards with fonts,
+ documentation, and On Screen Keyboard ([.KVK](kvk)) files. Keyman
+ Developer will also include a [metadata](metadata) file in the
+ package. Lexical model packages contain one lexical model instead of
+ keyboards.
+: .kmp file format has registered MIME type
+ [`application/vnd.keyman.kmp+zip`](https://www.iana.org/assignments/media-types/application/vnd.keyman.kmp+zip).
+
+Distributed with keyboard:
+: A Keyman keyboard Package file (.KMP) can include keyboard files
+ ([.KMX](kmx)/.JS), fonts, documentation, and On Screen Keyboard
+ ([.KVK](kvk)) files. Do not include source files ([.KMN](kmn)/.KVKS)
+ in the Package. The Keyman Package is normally distributed instead
+ of the plain keyboard file ([.KMX](kmx)) in order to include the
+ extra files.
+
+Distributed with lexical model:
+: A Keyman lexical model Package file (.KMP) includes one lexical
+ model file ([.MODEL.JS)](model-js), and documentation files. Do not
+ include lexical model source files ([.MODEL.TS](model-ts) or
+ [.TSV](tsv)) in the Package.
diff --git a/developer/docs/help/reference/file-types/kmx.md b/developer/docs/help/reference/file-types/kmx.md
new file mode 100644
index 00000000000..062c18a2a9d
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kmx.md
@@ -0,0 +1,28 @@
+---
+title: KMX files
+---
+
+Used by:
+: Keyman Core in Keyman for Windows ,
+ Keyman for macOS , and
+ Keyman for Linux .
+
+Description:
+: A .KMX file is a compiled keyboard file.
+
+Details:
+: A .KMX file is compiled in Keyman
+ Developer from the Keyman source file ([.KMN](kmn)) and icon
+ for the keyboard (.ICO/.BMP). Note that .KMX files are not used for
+ Keyman for Android and
+ Keyman for iPhone and iPad .
+: .KMX file format has registered MIME type
+ [`application/vnd.keyman.kmx`](https://www.iana.org/assignments/media-types/application/vnd.keyman.kmx).
+
+Distributed with keyboard:
+: This is the keyboard file. It must be distributed with your package
+ to allow people to type using the keyboard you have developed.
+
+Reference:
+* [KMX binary file format specification](https://github.com/keymanapp/keyman/blob/master/docs/file-formats/kmx-file-format.md)
+* [KMX+ binary file format specification](https://github.com/keymanapp/keyman/blob/master/docs/file-formats/kmx-plus-file-format.md)
diff --git a/developer/docs/help/reference/file-types/kpj.md b/developer/docs/help/reference/file-types/kpj.md
new file mode 100644
index 00000000000..f990fb98198
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kpj.md
@@ -0,0 +1,17 @@
+---
+title: KPJ files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KPJ file is a Keyman Developer Project file.
+
+Details:
+: .KPJ files are used in Keyman
+ Developer to organise keyboards and other files currently in
+ development.
+
+Distributed with keyboard:
+: No. This is a development file and should not be distributed.
diff --git a/developer/docs/help/reference/file-types/kps.md b/developer/docs/help/reference/file-types/kps.md
new file mode 100644
index 00000000000..ef4095d9aee
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kps.md
@@ -0,0 +1,27 @@
+---
+title: KPS files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KPS file is a Keyman package source file.
+
+Details:
+: A .KPS file is created using the Package Editor in
+ Keyman Developer . It specifies what
+ files are to be included in the package. It is compiled into a
+ Keyman Package file ([.KMP](kmp)).
+
+Distributed with keyboard:
+: No. This is a development file and should not be distributed.
+
+## File format
+
+The optional `` section of the file can be included to
+customise the text in the bootstrap installer. The default strings are
+found in the file:
+[strings.xml](https://github.com/keymanapp/keyman/blob/stable-14.0/windows/src/desktop/setup/locale/en/strings.xml).
+You can add your own strings for a given package which are used when
+compiling as a bundled installer.
diff --git a/developer/docs/help/reference/file-types/kvk.md b/developer/docs/help/reference/file-types/kvk.md
new file mode 100644
index 00000000000..77039aa4c1b
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kvk.md
@@ -0,0 +1,21 @@
+---
+title: KVK files
+---
+
+Used by:
+: Keyman Desktop .
+
+Description:
+: A .KVK file is a Keyman On Screen Keyboard file.
+
+Details:
+: A .KVK file is created in Keyman
+ Developer from the Keyman On Screen Keyboard source file
+ ([.KVKS](kvks)). Web, Android and iOS keyboards, when compiled,
+ embed the .KVK content into the output file, but desktop keyboards
+ (Windows and macOS) require the .KVK file to be included with the
+ keyboard at distribution time.
+
+Distributed with keyboard:
+: A .KVK file is associated with a specific keyboard, and must be
+ distributed with that keyboard to be usable.
diff --git a/developer/docs/help/reference/file-types/kvks.md b/developer/docs/help/reference/file-types/kvks.md
new file mode 100644
index 00000000000..cefd6f6643a
--- /dev/null
+++ b/developer/docs/help/reference/file-types/kvks.md
@@ -0,0 +1,18 @@
+---
+title: KVKS files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .KVKS file is a Keyman On Screen Keyboard source file.
+
+Details:
+: A .KVKS file is edited with the Keyboard Editor in
+ Keyman Developer during the
+ keyboard creation process. The file is XML format with UTF-8
+ encoding.
+
+Distributed with keyboard:
+: No. This is a development file and should not be distributed.
diff --git a/developer/docs/help/reference/file-types/metadata.md b/developer/docs/help/reference/file-types/metadata.md
new file mode 100644
index 00000000000..8a3e50deaec
--- /dev/null
+++ b/developer/docs/help/reference/file-types/metadata.md
@@ -0,0 +1,294 @@
+---
+title: Keyman Package Metadata files
+---
+
+Used by:
+: Keyman applications on all platforms except web.
+
+Description:
+: The files `kmp.inf` and `kmp.json` are metadata for a Keyman package file.
+
+Details:
+
+: When Keyman Developer compiles a Keyman package source file ([.kps](kps)), it
+ creates and automatically adds `kmp.inf` and `kmp.json` into the Keyman
+ Package file ([.kmp](kmp)).
+
+ Keyman on all platforms now use `kmp.json` to install the keyboards
+ ([.kmx](kmx)) in the package.
+
+ Older versions of Keyman Desktop for Windows use `kmp.inf`, a parallel format
+ of the package metadata. Note: the lexical model compiler does not add
+ `kmp.inf`, and new functionality is not included. Future versions of Keyman
+ Developer will stop supporting `kmp.inf`.
+
+Package purposes:
+
+: A Keyman Package file can be used to distribute keyboards (most common),
+ lexical models, or user interface localizations for Keyman Desktop.
+
+KMP.JSON
+--------
+
+### The package object
+
+The kmp.json file is a base object described as
+
+`system`
+
+: `Object`
+
+ [`System`](#obj-system) object.
+
+`options`
+
+: `Object`
+
+ An [`Options`](#obj-options) object.
+
+`startMenu`
+
+: `Object`
+
+ [`Start Menu`](#obj-startMenu) object.
+
+`info`
+
+: `Object`
+
+ [`Info`](#obj-info) object.
+
+`files`
+
+: `Object`
+
+ Array of objects, each containing a name and description of each file in in
+ the package
+
+`keyboards`
+
+: `Object`
+
+ Array of [`Keyboard`](#obj-keyboard) objects.
+
+`lexicalModels`
+
+: `Object`
+
+ Array of [`LexicalModel`](#obj-lexicalModel) objects.
+
+### The System object
+
+The `System` object is used by Keyman Desktop to install keyboards
+
+`keymanDeveloperVersion`
+
+: `string`
+
+ The version of Keyman Developer used to create the package file. If undefined,
+ use `'0.0.0.0'`
+
+`fileVersion,`
+
+: `string`
+
+### The Options object
+
+The `Options` object is used by Keyman Desktop to install keyboards
+
+`readmeFile`
+
+: `string`
+
+ A reference to the HTML file in the package for the keyboard package readme,
+ presented before the package is installed.
+
+`graphicFile`
+
+: `string`
+
+ A reference to an image file, .png or .jpeg recommended formats, associated
+ with the keyboard package, and shown at package installation time.
+
+`welcomeFile`
+
+: `string`
+
+ A reference to the HTML file containing keyboard package documentation,
+ available through the help interfaces in Keyman, and shown once after the
+ package is successfully installed. In the past, this file was always called
+ welcome.htm.
+
+`licenseFile`
+
+: `string`
+
+ A HTML file containing the package license.
+
+### The Start Menu object
+
+The `StartMenu` object is used by Keyman Desktop to install windows
+
+`folder`
+
+: `string`
+
+ The folder that Keyman Desktop will create
+
+`items`
+
+: `Array`
+
+ An array of [Item](#obj-item) objects
+
+### The Item Object
+
+The `Item` object
+
+`name`
+
+: `string`
+
+ The item name
+
+`filename`
+
+: `string`
+
+ The filename of the item
+
+`location`
+
+: `string`
+
+ The location for Keyman Desktop to place the item
+
+### The Info object
+
+The `Info` object describes the Keyman package
+
+`name`
+
+: `string`
+
+ The Keyman package name
+
+`version`
+
+: `string`
+
+ The version number of the package in dotted number format. Defaults to `'1.0'` if missing
+
+`copyright`
+
+: `string` optional
+
+ Copyright information
+
+`author`
+
+: `string` optional
+
+ The Keyman package author and email address
+
+`website`
+
+: `string` optional
+
+ Description and URL for additional Keyboard package documentation
+
+### The Keyboard object
+
+The `Keyboard` object describes an individual keyboard in the Keyman package. A package cannot contain both lexical models and keyboards.
+
+`name`
+
+: `string`
+
+ Name of keyboard
+
+`id`
+
+: `string`
+
+ ID of the keyboard, always matches the filename of the keyboard
+
+`rtl`
+
+: `boolean` optional
+
+ `true` if the keyboard targets a right-to-left script. `false` if absent.
+
+`version`
+
+: `string`
+
+ version number of the keyboard in dotted number format. Defaults to `'1.0'` if missing
+
+`languages`
+
+: `Array`
+
+ An array of [`Language`](#obj-language) objects linked to the keyboard.
+
+`displayFont`
+
+: `string` optional
+
+ The filename of the font for input fields (and OSK, if `oskFont` is not present).
+
+`oskFont`
+
+: `string` optional
+
+ The filename of the font for the OSK
+
+### The Language object
+
+The `Language` object describes the language that can be typed with the keyboard
+
+`name`
+
+: `string`
+
+ The name of the language
+
+`id`
+
+: `string`
+
+ [BCP 47 language code](../bcp-47)
+
+### The LexicalModel object
+
+The `LexicalModel` object describes an individual model in the Keyman package. A package cannot contain both lexical models and keyboards.
+
+`name`
+
+: `string`
+
+ Name of model
+
+`id`
+
+: `string`
+
+ ID of the model, always matches the filename of the model
+
+`rtl`
+
+: `boolean` optional
+
+ `true` if the model targets a right-to-left script. `false` if absent.
+
+`version`
+
+: `string`
+
+ version number of the model in dotted number format.
+
+`languages`
+
+: `Array`
+
+ An array of [`Language`](#obj-language) objects linked to the model.
diff --git a/developer/docs/help/reference/file-types/model-js.md b/developer/docs/help/reference/file-types/model-js.md
new file mode 100644
index 00000000000..e8f3cbb2c4b
--- /dev/null
+++ b/developer/docs/help/reference/file-types/model-js.md
@@ -0,0 +1,23 @@
+---
+title: MODEL.JS files
+---
+
+Used by:
+: Keyman for Android and
+ Keyman for iPhone and iPad .
+
+Description:
+: A .MODEL.JS file is a compiled lexical model file.
+
+Details:
+: A .MODEL.JS file is compiled in Keyman
+ Developer from the Keyman lexical model source
+ ([.MODEL.TS](model-ts)) file and ([.TSV](tsv)) wordlist. Note that
+ .MODEL.JS files are only currently supported in
+ Keyman for Android and
+ Keyman for iPhone and iPad .
+
+Distributed with lexical model:
+: This is the lexical model file. It must be distributed as a part of
+ your package to allow people to use the lexical model you have
+ developed.
diff --git a/developer/docs/help/reference/file-types/model-ts.md b/developer/docs/help/reference/file-types/model-ts.md
new file mode 100644
index 00000000000..98447d0283e
--- /dev/null
+++ b/developer/docs/help/reference/file-types/model-ts.md
@@ -0,0 +1,210 @@
+---
+title: MODEL.TS files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .MODEL.TS file is a [lexical model](../../guides/lexical-models)
+ definition source file. This holds all the code used by a Keyman
+ lexical model, in plain text.
+
+Details:
+: A .MODEL.TS file is written in the
+ [TypeScript](https://www.typescriptlang.org/) language.
+ Keyman Developer compiles this
+ Keyman lexical model source file which can also reference a
+ ([.TSV](tsv)) wordlist to make a lexical model
+ ([.MODEL.JS](model-js)) file.
+
+## Reference
+
+This is a small [TypeScript](https://www.typescriptlang.org/) source
+code file that tells us where to find the word list file, as well as
+gives us the option to tell the compiler a little bit more about our
+language’s spelling system or *orthography*.
+
+## The model definition template
+
+**Keyman Developer** will provide you with a model definition similar to
+the following.
+
+``` typescript
+/*
+ sencoten 1.0 generated from template.
+
+ This is a minimal lexical model source that uses a tab delimited wordlist.
+ See documentation online at https://help.keyman.com/developer/ for
+ additional parameters.
+*/
+
+
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ wordBreaker: {
+ use: 'default',
+ },
+ sources: ['wordlist.tsv'],
+};
+export default source;
+```
+
+Let's step through this file, line-by-line.
+
+On the first line, we're declaring the source code of a new lexical
+model.
+
+``` typescript
+const source: LexicalModelSource = {
+```
+
+On the second line, we're saying the lexical model will use the
+`trie-1.0` format. The `trie` format creates a lexical model from one or
+more word lists; the `trie` structures the lexical model such that it
+can predict through thousands of words very quickly.
+
+``` typescript
+ format: 'trie-1.0',
+```
+
+On lines 3–5, we're specifying the word breaking algorithm that we want
+to use. Keyman supplies a default algorithm that conforms to the rules
+expected for many Latin-script languages.
+
+``` typescript
+ wordBreaker: {
+ use: 'default',
+ },
+```
+
+On the sixth line, we're telling the `trie` where to find our wordlist.
+
+``` typescript
+ sources: ['wordlist.tsv'],
+```
+
+The seventh line marks the termination of the lexical model source code.
+If we specify any customizations, they **must** be declared above this
+line:
+
+``` typescript
+};
+```
+
+The eighth line is necessary to allow external applications to read the
+lexical model source code.
+
+``` typescript
+export default source;
+```
+
+## Customizing our lexical model
+
+The template, as described in the previous section, is a good starting
+point, and may be all you need for you language. However, most language
+require a few customizations. The `trie` model supports the following
+customizations:
+
+word breaking
+: How to determine when words start and end in the writing system.
+
+search term to key
+: How and when to ignore accents and lettercase
+
+### Word breaking
+
+The `trie` family of lexical models needs to know what a word is in
+running text. In language using the Latin script—like, English, French,
+and SENĆOŦEN—finding words is easy. Words are separated by spaces or
+punctuation. The actual rules for where to find words can get quite
+tricky to describe, but Keyman implements the [Unicode Standard Annex
+\#29 §4.1 Default Word Boundary
+Specification](https://unicode.org/reports/tr29/#Word_Boundaries) which
+works well for most languages. If the default doesn't *quite* work for
+your language, you can [tweak
+it](../../guides/lexical-models/advanced/word-breaker#join).
+
+However, in languages written in other scripts—especially East Asian
+scripts like Chinese, Japanese, Khmer, Lao, and Thai—there are no
+obvious break in between words. For these languages, there must be
+special rules for determining when words start and stop. This is what a
+word breaking function is responsible for. It
+is a little bit of code that looks at some text to determine where the
+words are.
+
+### Search term to key
+
+To look up words quickly, the `trie` model creates a
+search key that takes the latest word (as determined by the
+[word breaking](#toc-word-breaking) and converts it into a “regular”
+form. The purpose of this “regular” form is to make searching for a word
+work, regardless of things such as **accents**, **diacritics**,
+**lettercase**, and minor **spelling variations**. The ”regular” form is
+called the key . Typically, the key is always in
+lowercase, and lacks all accents and diacritics. For example, the key of
+“naïve" is "naive" and the key of Canada is “canada”.
+
+The form of the word that is stored is “regularized” through the use of
+a key function , which you can define in
+TypeScript code.
+
+The key function takes a string, the raw search term, and returns a
+string, being the “regular” key. As an example, consider the **default
+key function**; that is, the key function that is used if you do not
+specify one:
+
+```
+searchTermToKey: function (term) {
+ // Use this pattern to remove common diacritical marks (accents).
+ // See: https://www.compart.com/en/unicode/block/U+0300
+ const COMBINING_DIACRITICAL_MARKS = /[\u0300-\u036f]/g;
+
+ // Lowercase each letter in the string INDIVIDUALLY.
+ // Why individually? Some languages have context-sensitive lowercasing
+ // rules (e.g., Greek), which we would like to avoid.
+ // So we convert the string into an array of code points (Array.from(term)),
+ // convert each individual code point to lowercase (.map(c => c.toLowerCase())),
+ // and join the pieces back together again (.join(''))
+ let lowercasedTerm = Array.from(term).map(c => c.toLowerCase()).join('');
+
+ // Once it's lowercased, we convert it to NFKD normalization form
+ // This does many things, such as:
+ //
+ // - separating characters from their accents/diacritics
+ // e.g., "ï" -> "i" + "¨" (U+0308)
+ // - converting lookalike characters to a canonical ("regular") form
+ // e.g., ";" -> ";" (yes, those are two completely different characters -- U+037E and U+003B!)
+ // - converting "compatible" characters to their canonical ("regular") form
+ // e.g., "𝔥𝔢𝔩𝔩𝔬" -> "hello"
+ let normalizedTerm = lowercasedTerm.normalize('NFKD');
+
+ // Now, using the pattern defined above, replace each accent and diacritic with the
+ // empty string. This effectively removes all accents and diacritics!
+ //
+ // e.g., "i" + "¨" (U+0308) -> "i"
+ let termWithoutDiacritics = normalizedTerm.replace(COMBINING_DIACRITICAL_MARKS, '');
+
+ // The resultant key is lowercased, and has no accents or diacritics.
+ return termWithoutDiacritics;
+},
+```
+
+This should be sufficient for most Latin-based writing systems. However,
+there are cases, such as with SENĆOŦEN, where some characters do not
+decompose into a base letter and a diacritic. In this case, it is
+necessary to write your own key function.
+
+## Version History
+
+Keyman 12
+: **Added**: the `.model.ts` file type.
+
+Keyman 13
+: *No changes.*
+
+Keyman 14
+: **Added**: an alternative syntax for specifying word breakers:
+ `wordBreaker: { 'use': ... }`.
+: **Added**: specify which characters should be used to join with word
+ breakers: `wordBreaker: { 'joinWordsAt': ... }`.
diff --git a/developer/docs/help/reference/file-types/tsv.md b/developer/docs/help/reference/file-types/tsv.md
new file mode 100644
index 00000000000..b926ed95725
--- /dev/null
+++ b/developer/docs/help/reference/file-types/tsv.md
@@ -0,0 +1,60 @@
+---
+title: TSV files
+---
+
+Used by:
+: Keyman Developer and
+ Lexical Model compiler .
+
+Description:
+: A .tsv file or a tab-separated values file
+ contains a word list. This word list is used to predict and correct
+ words using the predictive text functionality.
+
+Details:
+: A .tsv file is a plain-text file containing of tabular data.
+ Spreadsheet programs such as Microsoft Excel and Google Sheets can
+ export into TSV format. TSVs can also be programmatically generated
+ from other data sources. For advanced users, see [File
+ Format](#file-format) for more details.
+
+Distributed with lexical model:
+: No. This is a development file and should not be distributed.
+
+## File format
+
+**For advanced users only**: this documentation is intended for users
+who wish to develop their own word list exporters. Most users can use
+existing word list exporters.
+
+The **lexical model compiler** expects word lists to abide by the
+following **tab-separated values** (TSV) format:
+
+- the file is a **UTF-8** encoded text file
+- newlines are either **LF** or **CRLF**
+- the file **MAY** start with the UTF-8 **byte-order mark** (BOM);
+ that is, if the first three bytes of the file are `EF BB BF`, these
+ will be interpreted as the BOM and will be ignored.
+- the file either consists of a **comment** or an **entry**
+- **comment** lines MUST start with the `#` character on the very
+ first column
+- **entries** are one to three columns, separated by the (horizontal)
+ **tab character** (U+0009)
+- column 1 (**REQUIRED**): the **word form**: can have any character
+ except tab, CR, or LF. Surrounding whitespace characters are
+ trimmed. Quote characters (`'` or `"`) are **NOT** required to
+ surround the text and are **NOT** parsed in any special manner.
+- column 2 (*optional*): the **count**: a non-negative integer
+ specifying how many times this entry has appeared in the corpus.
+ Blank means ‘indeterminate’, and is treated as if the word exists in
+ the corpus, but will be predicted at the lowest possible priority.
+- column 3 (*optional*): **comment**: an informative comment, ignored
+ by this tool.
+
+Source:
+[build-trie.js@029fb7c8](https://github.com/keymanapp/keyman/blob/029fb7c822c5a5619eaca845ecd2e5a2497d3056/developer/js/lexical-model-compiler/build-trie.ts#L21-L40)
+
+## Additional notes
+
+Exporting a spreadsheet from Google Sheets as a TSV will produce
+properly formatted output.
diff --git a/developer/docs/help/reference/file-types/ttf.md b/developer/docs/help/reference/file-types/ttf.md
new file mode 100644
index 00000000000..870a5201788
--- /dev/null
+++ b/developer/docs/help/reference/file-types/ttf.md
@@ -0,0 +1,22 @@
+---
+title: TTF files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .ttf file is a TrueType font file. This file is distributed with a
+ keyboard package to be used as a keyboard font or display font. This
+ file is optional; many languages and keyboards do not need to
+ include a font because there will be a font supplied by the
+ operating system.
+
+Details:
+: A .ttf file is a binary file. It is included using the Package
+ Editor in Keyman Developer. Keyman Developer does not create font
+ files.
+
+Distributed with keyboard:
+: This is a font file. It should be distributed with your keyboard
+ package to allow people to see the keyboard output characters.
diff --git a/developer/docs/help/reference/file-types/xml.md b/developer/docs/help/reference/file-types/xml.md
new file mode 100644
index 00000000000..78d24749528
--- /dev/null
+++ b/developer/docs/help/reference/file-types/xml.md
@@ -0,0 +1,18 @@
+---
+title: XML files
+---
+
+Used by:
+: Keyman Developer .
+
+Description:
+: A .XML file can be many different things. For Keyman Developer, XML files
+ are typically LDML keyboards which are authored in XML. This file holds all
+ the code used by a LDML format keyboard, in XML format.
+
+Details:
+: Compiles into a Keyman keyboard ([.KMX](kmx)) file.
+
+Distributed with keyboard:
+: This is a keyboard development file and should not usually be distributed
+ with your package.
diff --git a/developer/docs/help/reference/index.md b/developer/docs/help/reference/index.md
new file mode 100644
index 00000000000..04ba82a125e
--- /dev/null
+++ b/developer/docs/help/reference/index.md
@@ -0,0 +1,13 @@
+---
+title: Keyman Developer Reference
+---
+
+* [Keyman keyboard language guide and reference](/developer/language)
+* [Command line compiler - kmc](kmc)
+* [Compiler messages](messages)
+* [Keyman keyboard source file layout](file-layout)
+* [File types](file-types)
+* [BCP 47 language codes](bcp-47)
+* [Tools](tools)
+* [Custom editor themes](editor-themes)
+* [User settings](user-settings)
\ No newline at end of file
diff --git a/developer/docs/help/reference/kmc/api/index.md b/developer/docs/help/reference/kmc/api/index.md
new file mode 100644
index 00000000000..e39a04f8aa6
--- /dev/null
+++ b/developer/docs/help/reference/kmc/api/index.md
@@ -0,0 +1,43 @@
+---
+title: kmc API Reference
+---
+
+kmc is implemented in Typescript as a set of compiler modules. Each module has a
+public API, documented in README.md in the corresponding repository folder.
+
+This section is intended for software developers who want to integrate kmc into
+their own toolchains. For documentation on using `kmc`, see [kmc command line
+compiler](../cli).
+
+# Modules
+
+Module Name | NPM Package | GitHub Source | Description
+-------------------------------------------------|-------------------------------------------------------|------------------------------------|---------------
+[kmc](../cli) | [@keymanapp/kmc][kmc-npm] | [GitHub][kmc-github] | The official command-line interface for all of the various compilers in Keyman Developer.
+[kmc-analyze](../../api/kmc-analyze) | [@keymanapp/kmc-analyze][kmc-analyze-npm] | [GitHub][kmc-analyze-github] | Provides Keyman keyboard analysis tools.
+[kmc-keyboard-info](../../api/kmc-keyboard-info) | [@keymanapp/kmc-keyboard-info][kmc-keyboard-info-npm] | [GitHub][kmc-keyboard-info-github] | Builds a .keyboard_info file from a Keyman keyboard project.
+[kmc-kmn](../../api/kmc-kmn) | [@keymanapp/kmc-kmn][kmc-kmn-npm] | [GitHub][kmc-kmn-github] | Builds .kmn keyboards into .kmx binary keyboard files.
+[kmc-ldml](../../api/kmc-ldml) | [@keymanapp/kmc-ldml][kmc-ldml-npm] | [GitHub][kmc-ldml-github] | Builds LDML .xml keyboards into Keyman .kmx binary keyboard files.
+[kmc-model](../../api/kmc-model) | [@keymanapp/kmc-model][kmc-model-npm] | [GitHub][kmc-model-github] | Builds .model.ts lexical models into .model.js files.
+[kmc-model-info](../../api/kmc-model-info) | [@keymanapp/kmc-model-info][kmc-model-info-npm] | [GitHub][kmc-model-info-github] | Builds a .model_info file from a Keyman lexical model project.
+[kmc-package](../../api/kmc-package) | [@keymanapp/kmc-package][kmc-package-npm] | [GitHub][kmc-package-github] | Builds .kps Keyman package source files into binary .kmp Keyman package files.
+
+[kmc-npm]: https://npmjs.com/package/@keymanapp/kmc
+[kmc-analyze-npm]: https://npmjs.com/package/@keymanapp/kmc-analyze
+[kmc-keyboard-info-npm]: https://npmjs.com/package/@keymanapp/kmc-keyboard-info
+[kmc-kmn-npm]: https://npmjs.com/package/@keymanapp/kmc-kmn
+[kmc-ldml-npm]: https://npmjs.com/package/@keymanapp/kmc-ldml
+[kmc-model-npm]: https://npmjs.com/package/@keymanapp/kmc-model
+[kmc-model-info-npm]: https://npmjs.com/package/@keymanapp/kmc-model-info
+[kmc-package-npm]: https://npmjs.com/package/@keymanapp/kmc-package
+[common-types-npm]: https://npmjs.com/package/@keymanapp/common-types
+
+[kmc-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc
+[kmc-analyze-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-analyze
+[kmc-keyboard-info-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-keyboard-info
+[kmc-kmn-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-kmn
+[kmc-ldml-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-ldml
+[kmc-model-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-model
+[kmc-model-info-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-model-info
+[kmc-package-github]: https://github.com/keymanapp/keyman/tree/master/developer/src/kmc-package
+[common-types-github]: https://github.com/keymanapp/keyman/tree/master/common/web/types
diff --git a/developer/docs/help/reference/kmc/cli/get-started.md b/developer/docs/help/reference/kmc/cli/get-started.md
new file mode 100644
index 00000000000..9147eebda25
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/get-started.md
@@ -0,0 +1,164 @@
+---
+title: Get started with kmc
+---
+
+This package provides a command-line interface to the Keyman Developer compiler
+toolchain, `kmc`.
+
+## Install kmc
+
+`kmc` is available as:
+* a part of Keyman Developer (Windows)
+* an npm package, and
+* a zip download
+
+Hint: Unlike previous versions of Keyman Developer, version 17 of kmc does not
+require WINE to run the command line tools on Linux or macOS.
+
+### Keyman Developer integration (Windows only)
+
+kmc is included with a default installation of Keyman Developer, including a
+runtime of node.js, and will be on the system path by default. No additional
+configuration or installation is required.
+
+### npm (Windows, macOS, and Linux)
+
+kmc is also available as an npm package,
+[@keymanapp/kmc](https://npmjs.com/package/@keymanapp/kmc).
+
+You'll need [node.js](https://nodejs.org/), version 18.0 or later.
+
+```shell
+npm install -g @keymanapp/kmc
+```
+
+### Zip download (Windows, macOS, and Linux)
+
+kmc is also available as a zip download from
+[keyman.com/developer/download](https://keyman.com/developer/download),
+or can be installed from the command line (`curl` and `unzip` required):
+
+```shell
+# To build keyboards and packages:
+mkdir kmc
+cd kmc
+# hint: the download is currently called 'kmcomp', although the
+# compiler is now called 'kmc'.
+curl -L https://keyman.com/go/download/kmcomp -o kmc.zip
+unzip kmc.zip
+# Optionally, add kmc to your PATH
+```
+
+## The five minute quick start
+
+### 1. Download a sample keyboard project
+
+
+
+We'll download a sample project from GitHub for Khmer. If you do not have the
+command-line git tools installed, you can visit the [repository
+website](https://github.com/keyman-keyboards/khmer_angkor) and download it as a
+zip file instead.
+
+```shell
+git clone https://github.com/keyman-keyboards/khmer_angkor
+```
+
+This will have created a new folder called `khmer_angkor/`.
+
+### 2. Build the project
+
+Now, we'll build our keyboard project with kmc.
+
+```shell
+cd khmer_angkor
+kmc build .
+```
+
+And... that's it! We'll now have a compiled keyboard and package in the `build/`
+subfolder. The file `build/khmer_angkor.kmp` can be installed into any of the
+Keyman apps, and `build/khmer_angkor.js` can be added to KeymanWeb.
+
+### 3. Install the keyboard
+
+**Windows**: we can install this keyboard using [`kmshell`][windows-install-keyboard]:
+
+```cmd
+"%ProgramFiles(x86)%\Keyman\Keyman Desktop\kmshell" -i build\khmer_angkor.kmp -s
+```
+
+Alternatively you can double-click the .kmp package file in Windows Explorer to
+install it.
+
+**Linux**: we'd use the following
+[`km-package-install`][linux-install-keyboard]
+command:
+
+```shell
+km-package-install -f build/khmer_angkor.kmp
+```
+
+**macOS**: open Keyman Configuration and drop the package khmer_angkor.kmp file
+onto the Keyman Configuration window.
+
+**Android**: send khmer_angkor.kmp to your Android device, and install it from the
+hamburger menu in the Keyman app.
+
+**iOS**: send khmer_angkor.kmp to your iOS device, and install it from the
+hamburger menu in the Keyman app.
+
+**Web**: copy khmer_angkor.js to your website, then [load it with KeymanWeb][load-keymanweb-keyboard]:
+
+```js
+keyman.addKeyboards({
+ id:'khmer_angkor', // The keyboard's unique identification code.
+ name:'Khmer Angkor', // The keyboard's user-readable name.
+ language:{
+ id:'km', // A BCP 47 code uniquely identifying the language.
+ name:'Khmer' // The language's name.
+ },
+ filename:'./khmer-angkor.js',
+});
+```
+
+## File layout
+
+See [file layout][file-layout] for details on the standard source file layout
+that Keyman Developer works best with.
+
+## Reference and Examples
+
+### kmc - command line compiler
+
+[kmc][kmc] is the command line compiler. You can use it to compile
+all Keyman files.
+
+The most common command will be `kmc build`:
+
+`kmc build project.kpj`
+: Compile all components of a keyboard or model project named `project.kpj`
+KMComp will respect the path settings within the project file. This is the
+recommended way to build, as it will build keyboards, models and packages all in
+one step. You can also call `kmc build ` to build the project in the
+referenced folder, e.g. `kmc build .`.
+
+* [kmc reference][kmc]
+
+### KMConvert
+
+[KMConvert](../../../context/kmconvert) generates keyboards and models from templates,
+and converts keyboard layouts between different formats.
+
+The [New Project dialog](../../../context/new-project) in Keyman Developer provides a
+graphical version of the most common functionality in KMConvert.
+
+* [KMConvert reference](../../../context/kmconvert)
+
+**Note:** KMConvert is currently a Windows executable, and will be integrated
+into kmc in an upcoming version of Keyman.
+
+[kmc]: ./reference
+[file-layout]: ../../file-layout
+[load-keymanweb-keyboard]: /developer/engine/web/current-version/guide/adding-keyboards
+[linux-install-keyboard]: /products/linux/current-version/reference/km-package-install
+[windows-install-keyboard]: /knowledge-base/98
diff --git a/developer/docs/help/reference/kmc/cli/index.md b/developer/docs/help/reference/kmc/cli/index.md
new file mode 100644
index 00000000000..808e053a2f8
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/index.md
@@ -0,0 +1,7 @@
+---
+title: Command line compiler - kmc
+---
+
+* [Get started with kmc](get-started)
+* [kmc command line reference](reference)
+* [Migrating from kmcomp to kmc](kmcomp-migration)
diff --git a/developer/docs/help/reference/kmc/cli/kmcomp-migration.md b/developer/docs/help/reference/kmc/cli/kmcomp-migration.md
new file mode 100644
index 00000000000..c864e1bc901
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/kmcomp-migration.md
@@ -0,0 +1,125 @@
+---
+title: Migrating from kmcomp to kmc
+---
+
+`kmcomp` was the command-line compiler for Keyman Developer through version
+16.0. Version 17.0 replaces `kmcomp` with `kmc`.
+
+The lexical model command-line tooling, `kmlmc`, `kmlmp`, and `kmlmi`, are all
+still present in version 17, but are deprecated, as the same tasks can be
+performed with `kmc`.
+
+## Benefits
+
+* Unlike `kmcomp`, which was a Windows-only console application, `kmc` is a
+ cross-platform application, running on Windows, Linux, and macOS, built in
+ node.js.
+* `kmc` parameters have been redesigned to be more consistent, and easily
+ extensible for new functionality.
+* `kmc` can run in a batch mode that is up to 100x faster than `kmcomp`.
+* `kmc` can be easily integrated into GitHub Actions and other continuous
+ integration tooling.
+
+## Compiling a project
+
+To compile a project, `kmcomp` used a command line such as:
+
+```bash
+kmcomp path\to\my_keyboard.kpj
+```
+
+With `kmc`, you would use:
+
+```bash
+kmc build path/to/my_keyboard.kpj
+# or you can specify just the folder:
+kmc build path/to/
+# If you are in the same folder as the .kpj:
+kmc build .
+```
+
+This is the recommended way of using `kmc` -- always build an entire project,
+rather than just the keyboard.
+
+## Compiling a keyboard
+
+To compile a keyboard, `kmcomp` builds used command lines such as:
+
+```bash
+# building a .kmx
+kmcomp my_keyboard.kmn my_keyboard.kmx
+# building a .js (for KeymanWeb)
+kmcomp my_keyboard.kmn my_keyboard.js
+```
+
+With `kmc`, you would use:
+
+```bash
+# building all outputs
+kmc build my_keyboard.kmn
+# or to specify output files (.js inferred from .kmx, and always built)
+kmc build my_keyboard.kmn -o my_keyboard.kmx
+```
+
+## Compiling a package
+
+To compile a package, `kmcomp` builds used command lines such as:
+
+```bash
+kmcomp my_keyboard.kps
+```
+
+With `kmc`, you would use:
+
+```bash
+kmc build my_keyboard.kps
+# to specify output file or path
+kmc build my_keyboard.kps -o path/to/my_keyboard.kmp
+```
+
+## Map of parameters
+
+The following table lists common parameters in `kmcomp`, and the corresponding
+parameter in `kmc`:
+
+kmcomp | kmc | notes
+-----------------------|---------------------------------------|-------------
+`-h`, `-help` | `-h`, `--help` | Note that `kmc --help` can be used for further detail on subcommands, e.g. `kmc build --help`
+`-s` | `-l warn`, `--log-level warn` | Silent option, suppresses informational and hint-level messages
+`-ss` | `-l silent`, `--log-level silent` | Super-silent option, suppresses all messages, except fatal internal errors
+`-nologo` | not required | `kmc` does not emit a compiler description
+`-c` | N/A | `kmc` does not currently support the `clean` command, this will be supported in future versions
+`-d` | `-d`, `--debug` | Include debug information in output files
+`-w` | `-w`, `--compiler-warnings-as-errors` | Causes warnings to fail the build; overrides project-level option
+`-cfc` | not required | Filename convention checks are now standard hints/warnings in the build
+`-t` | N/A | Build specified target from .kpj, not supported in kmc
+`-color` | `--color` | Force colorization for log messages
+`-no-color` | `--no-color` | No colorization for log messages
+`-no-compiler-version` | `--no-compiler-version` | Exclude compiler version metadata from output files
+
+## Compiling a lexical model
+
+`kmlmc` and `kmlmp` were separate tools in earlier versions of Keyman Developer,
+for compiling lexical models and lexical model packages. They have both been
+replaced with `kmc`.
+
+Old method, using kmlmc and kmlmp:
+
+```bash
+kmlmc file.model.ts
+# or specifying output filename
+kmlmc -o output/path/file.model.js file.model.ts
+kmlmp file.model.kps
+```
+
+New method, using kmc:
+
+```bash
+# recommended, build the model project:
+kmc build .
+# building single files:
+kmc build file.model.ts
+kmc build file.model.ts -o output/path/file.model.js
+kmc build file.model.kps
+```
+
diff --git a/developer/docs/help/reference/kmc/cli/messages/index.md b/developer/docs/help/reference/kmc/cli/messages/index.md
new file mode 100644
index 00000000000..1655e0f4888
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/messages/index.md
@@ -0,0 +1,6 @@
+---
+title: kmc Compiler Messages
+---
+
+This section lists messages that the kmc compiler may generate in use, describes
+circumstances that cause the messages to arise, and how to resolve them.
diff --git a/developer/docs/help/reference/kmc/cli/reference.md b/developer/docs/help/reference/kmc/cli/reference.md
new file mode 100644
index 00000000000..763d5ad71f8
--- /dev/null
+++ b/developer/docs/help/reference/kmc/cli/reference.md
@@ -0,0 +1,577 @@
+---
+title: kmc command line reference
+---
+
+kmc is the command line compiler toolset for Keyman 17.0 and later versions.
+In Keyman Developer, it is located in `%ProgramFiles(x86)%\Keyman\Keyman Developer`.
+kmc is also available as an npm module for Windows, macOS and Linux developers:
+
+```
+npm install -g @keymanapp/kmc
+```
+
+kmc does more than just compile keyboards. It builds packages, lexical models,
+projects, keyboards, Windows installers, and more. It provides analysis tools
+for keyboard data.
+
+kmc will be extended to generate new keyboard and lexical model projects,
+support renaming or cloning of existing projects, importing keyboard data from
+other formats, and running keyboard unit tests.
+
+kmc [replaces kmcomp](kmcomp-migration) from earlier versions of Keyman Developer.
+
+The following parameters are available:
+
+## `kmc` commands
+
+`kmc build [infile...]`, `kmc build file [infile...]`
+
+: Compile one or more Keyman files. Takes Keyman keyboard source files, and
+ compiles them into the binary formats used by the Keyman apps. Supports
+ building:
+ * Keyman projects (.kpj or folder)
+ * Keyman keyboards (.kmn)
+ * LDML keyboards (.xml)
+ * Keyboard packages - bundles of keyboard files, fonts, documentation for
+ distribution (.kps)
+ * Lexical models (.model.ts)
+
+ The `file` subcommand is the default command for `kmc build`, so can be
+ omitted.
+
+ File lists can be referenced with @filelist.txt. If no input file is supplied,
+ kmc will attempt to build a project file in the current folder.
+
+`kmc build ldml-test-data`
+
+: Converts LDML keyboard test .xml file to .json.
+
+`kmc build windows-package-installer`
+
+: Builds a .exe installer for a keyboard, together with the Keyman installer,
+ for Windows only.
+
+`kmc analyze osk-char-use [infile...]`
+
+: Analyze on screen keyboard files for character usage
+
+`kmc analyze osk-rewrite-from-char-use -m mapping-file [infile...]`
+
+: Rewrites On Screen Keyboard files from source mapping
+
+`kmc generate keyman-keyboard [options] `
+
+: Generate a .kmn keyboard project
+
+`kmc generate ldml-keyboard [options] `
+
+: Generate an LDML .xml keyboard project
+
+`kmc generate lexical-model [options] `
+
+: Generate a wordlist lexical model project
+
+`kmc copy origin -o target`
+
+: Copy and rename a keyboard project
+
+`kmc message [message...]`
+
+: Describes one or more compiler messages in greater detail
+
+## `kmc` global options
+
+`-h`, `--help`
+
+: Display help on kmc; note that `kmc --help` can be used for further detail on
+ subcommands, e.g. `kmc build --help`
+
+`-V`, `--version`
+
+: Prints the version number of kmc
+
+`--no-error-reporting`, `--error-reporting`
+
+: Enable or disable error reporting to keyman.com, overriding [user
+ settings](../../user-settings). Error reporting is for fatal errors in the
+ compiler, and not errors in compiled files. No user data is sent in error
+ reports, although some filenames and paths may be present in the diagnostic
+ data attached to the report.
+
+`-l `, `--log-level `
+
+: Controls the level of logging to console for messages relating to the
+ compilation process. The options are:
+ * `silent`: suppresses all messages (except fatal internal errors)
+ * `error`: only emits compilation errors
+ * `warn`: only emits compilation errors and warnings
+ * `hint`: emits compilation errors, warnings, and hints
+ * `info` (default): emits compilation errors, warnings, hints, and
+ informational messages
+ * `debug`: emits all compilation messages, plus internal debug messages
+
+ Logging of specific [messages](messages) can be controlled with `--message`, `-m`.
+
+ Note that when warnings are treated as errors
+ (`--compiler-warnings-as-errors`, `-w`), they will still be logged as
+ warnings, so suppressing warnings while using this flag may be confusing.
+
+## `kmc build` options
+
+`--color`, `--no-color`
+
+: Controls colorization for log messages, using ANSI color controls. If both of
+ these settings are omitted, kmc will attempt to detect from console, and will
+ use colorization for interactive terminals, and no colorization when
+ redirection is being used.
+
+`-d`, `--debug`
+
+: Include debug information in output files. Debug information is used for
+ interactive debugging of .kmx files within the Keyman Developer IDE. This
+ flag also produces pretty printed .js files for web keyboards, making
+ interactive debugging of web keyboards simpler.
+
+`-w`, `--compiler-warnings-as-errors` vs `-W`, `--no-compiler-warnings-as-errors`
+
+: Controls whether or not warnings fail the build; overrides project-level
+ warnings-as-errors option. Most compiler warnings are an indication that
+ something is not right in the source code, even though the compiler can
+ produce a result. This strict compilation mode helps to ensure that problems
+ are caught early, and is recommended.
+
+`-m `, `--message `
+
+: Adjusts the severity of info, hint or warning messages. Error and fatal error
+ messages can not be adjusted. Message severity can be adjusted to:
+ * `disable` (default): suppresses the message altogether
+ * `info`: converts the message to an informational severity
+ * `hint`: converts the message to a hint severity
+ * `warn`: converts the message to a warning severity
+ * `error`: raises the message to an error severity
+
+ This may be used to suppress a warning message that would otherwise fail the
+ build, if used in conjunction with `-w`, `--compiler-warnings-as-errors`
+
+ This option may be repeated to adjust multiple messages. The `-m` option must
+ be specified each time.
+
+`--no-compiler-version`
+
+: Excludes compiler version metadata from output. This is helpful for producing
+ files that will be identical regardless of the compiler version, for
+ regression testing.
+
+`--no-warn-deprecated-code`
+
+: Turns off warnings (CWARN_HeaderStatementIsDeprecated,
+ CWARN_LanguageHeadersDeprecatedInKeyman10) for deprecated code styles
+
+`--log-format `
+
+: Output log format. The available options are:
+ * `formatted` (default): emits log messages in a human-readable format
+ * `tsv`: emits log messages in UTF-8 tab-separated format. This format will be
+ stable across versions of kmc. The format has the following fields:
+ * filename
+ * line number
+ * severity
+ * code
+ * message
+
+`-o `, `--out-file `
+
+: Overrides the default path and filename for the output file(s). Note that
+ some compilers emit multiple files, in which case, the output filenames
+ will vary by file extension.
+
+## `kmc build file` additional options
+
+`--for-publishing`
+
+: Verifies that project meets @keymanapp repository requirements. This also
+ causes a .keyboard-info or .model-info file to be emitted when compiling the
+ project (which can also be controlled at a project level with the
+ `skipMetadataFiles` option). This option is only valid for compiling projects.
+
+### Examples
+
+```shell
+kmc build project.kpj
+```
+
+Compile all components of a keyboard or model project named `project.kpj`.
+kmc will respect the path settings within the project file. This is the
+recommended way to build, as it will build keyboards, models and packages all in
+one step. You can also call `kmc build ` to build the project in the
+referenced folder, e.g. `kmc build .`.
+
+```shell
+kmc build keyboard.kmn
+```
+
+Compile a keyboard file to a .kmx (desktop targets) and/or .js (web/touch
+targets). If an output file is not specified, writes to the same folder as the
+keyboard.
+
+```shell
+kmc build package.kps
+```
+
+Compile a package file to a .kmp (all targets). All included keyboards must
+already be compiled.
+
+
+## `kmc build windows-package-installer` additional options
+
+`--msi `
+
+: Full path of keymandesktop.msi to bundle into the installer. This file can be
+ downloaded from https://downloads.keyman.com/windows/stable (/version).
+
+`--exe `
+
+: Location of setup.exe. This file can be downloaded from
+ https://downloads.keyman.com/windows/stable (/version).
+
+`--license `
+
+: Location of license.txt for Keyman for Windows.
+
+`--title-image [titleImageFilename]`
+
+: Location of title image file. This should be a .png, .jpg, or .bmp file which
+ replaces the standard 'Keyman for Windows' image in the bootstrap installer.
+
+`--app-name [applicationName]`
+
+: Installer property: name of the application to be installed (default: "Keyman")
+
+`--start-disabled`
+
+: Installer property: do not enable keyboards after installation completes
+
+`--start-with-configuration`
+
+: Installer property: start Keyman Configuration after installation completes
+
+### Examples
+
+#### Windows, command prompt (all one line)
+
+```bat
+kmc build windows-package-installer .\khmer_angkor.kps
+ --msi "C:\Program Files (x86)\Common Files\Keyman\Cached Installer Files\keymandesktop.msi"
+ --exe .\setup-redist.exe --license .\LICENSE.md --out-file .\khmer.exe
+```
+
+#### Bash (Linux, WSL, macOS, etc)
+
+```shell
+kmc build windows-package-installer \
+ ./khmer_angkor/source/khmer_angkor.kps \
+ --msi ./redist/keymandesktop.msi \
+ --exe ./redist/setup-redist.exe \
+ --license ./redist/LICENSE.md \
+ --out-file ./khmer.exe
+```
+
+Note: paths shown above may vary.
+
+## `kmc analyze osk-char-use` options
+
+`-b, --base`
+
+: First PUA codepoint to use, in hexadecimal (default F100)
+
+`--include-counts`
+
+: Include number of times each character is referenced (default: false)
+
+`--strip-dotted-circle`
+
+: Strip U+25CC (dotted circle base) from results (default: false)
+
+`-m, --mapping-file `
+
+: Result file to write to (.json, .md, or .txt)
+
+`-i, --input-mapping-file `
+
+: Merge result file with existing mapping file. If supplied, existing
+ codepoint mappings will be kept, to ensure that updated fonts are
+ backwardly compatible with deployed keyboards. The
+ `--include-counts` flag will be set according to the format of
+ the input mapping file.
+
+For more information on the purpose of `analyze osk-char-use` and
+`analyze rewrite-osk-from-char-use`, see
+[`&displayMap`](/developer/language/reference/displaymap).
+
+## `kmc analyze osk-rewrite-from-char-use` options
+
+`-m, --mapping-file `
+
+: JSON mapping file to read from.
+
+For more information on the purpose of `analyze osk-char-use` and
+`analyze rewrite-osk-from-char-use`, see
+[`&displayMap`](/developer/language/reference/displaymap).
+
+## `kmc generate keyman-keyboard` options
+
+Generates a new keyboard project with .kmn format keyboard. An ID must be
+specified for the output of the project, and an output folder (`-o`). A new
+folder with the keyboard ID as its name will be created under the output folder,
+and the files in that folder will follow the
+[standard file layout](../../file-layout).
+
+`-t, --target `
+
+: Target platforms for the project. Use the values from
+ [`&targets`](/developer/language/reference/targets). Multiple targets may be
+ specified, each prefixed with `-t`, or space-separated, surrounded by
+ quotation marks (e.g. `-t windows -t linux` or `-t "windows linux"`) (default:
+ `any`)
+
+`-o, --out-path `
+
+: Specifies the parent folder for the project; the folder may already exist. The
+ project will be generated in a new folder named with the ID of the project
+ under this path, and that project folder must not exist.
+
+`-n, --name `
+
+: Keyboard descriptive name, used in the
+ [`&name` store](/developer/language/reference/name) (default: the ID of the
+ project)
+
+`-c, --copyright `
+
+: [`©right`](/developer/language/reference/copyright) holder for the
+ project. Do not include the '(C)' or '©' prefixes (default: the author
+ of the keyboard)
+
+`-v, --version `
+
+: [`&version`](/developer/language/reference/version) of the keyboard, (default:
+ "1.0").
+
+`-L, --language-tag `
+
+: A BCP-47 language tag with which the keyboard is associated. More than one
+ tag may be specified, with each tag prefixed with `-L` (default: no languages).
+ The tags are referenced in the package metadata.
+
+`-a, --author `
+
+: The name of keyboard author (default: blank)
+
+`-i, --icon`
+
+: Include a generated icon. The icon will be a 16x16 pixel box with the first
+ letters of the first language tag (default: true, include an icon)
+
+`-d, --description `
+
+: A short description of the project, in Markdown. (default: keyboard name)
+
+## `kmc generate ldml-keyboard` options
+
+Generates a new keyboard project with LDML .xml format keyboard. An ID must be
+specified for the output of the project, and an output folder (`-o`). A new
+folder with the keyboard ID as its name will be created under the output folder,
+and the files in that folder will follow the
+[standard file layout](../../file-layout).
+
+`-t, --target `
+
+: Target platforms for the project. Use the values from
+ [`&targets`](/developer/language/reference/targets). Multiple targets may be
+ specified, each prefixed with `-t`, or space-separated, surrounded by
+ quotation marks (e.g. `-t windows -t linux` or `-t "windows linux"`) (default:
+ `any`)
+
+`-o, --out-path `
+
+: Specifies the parent folder for the project; the folder may already exist. The
+ project will be generated in a new folder named with the ID of the project
+ under this path, and that project folder must not exist.
+
+`-n, --name `
+
+: Keyboard descriptive name, referenced in the keyboard ``
+
+: Copyright holder for the project. Do not include the '(C)' or '©'
+ prefixes (default: the author of the keyboard)
+
+`-v, --version `
+
+: Version of the keyboard, referenced in the keyboard ``
+
+: A BCP-47 language tag with which the keyboard is associated. More than one
+ tag may be specified, with each tag prefixed with `-L` (default: no languages).
+ The first tag is referenced in the keyboard ``
+
+: The name of keyboard author, referenced in ``
+
+: A short description of the project, in Markdown. (default: keyboard name)
+
+## `kmc generate lexical-model` options
+
+Generates a new lexical model project with a TSV wordlist. An ID must be
+specified for the output of the project, and an output folder (`-o`). The ID
+must match the [`author.bcp47.uniq`](/developer/lexical-models) naming pattern.
+A new folder with the model ID as its name will be created under the output
+folder, and the files in that folder will follow the
+[standard file layout](../../file-layout).
+
+`-o, --out-path `
+
+: Specifies the parent folder for the project; the folder may already exist. The
+ project will be generated in a new folder named with the ID of the project
+ under this path, and that project folder must not exist.
+
+`-n, --name `
+
+: Lexical model descriptive name, referenced in the package metadata (default:
+ the ID of the project)
+
+`-c, --copyright `
+
+: Copyright holder for the project. Do not include the '(C)' or '©'
+ prefixes (default: the author of the model)
+
+`-v, --version `
+
+: Version of the lexical model, referenced in the package metadata (defaults to
+ "1.0")
+
+`-L, --language-tag `
+
+: A BCP-47 language tag with which the model is associated. More than one
+ tag may be specified, with each tag prefixed with `-L` (default: no languages).
+ The tags are referenced in the package metadata.
+
+`-a, --author `
+
+: The name of model author, referenced in package metadata (default: blank)
+
+`-d, --description `
+
+: A short description of the project, in Markdown. (default: lexical model name)
+
+## `kmc copy` options
+
+Copies a keyboard or lexical model project, renaming files matching the original
+project ID according to the output filename. Can copy projects from the
+following sources:
+
+* A .kpj file, e.g. `./keyboards/khmer_angkor/khmer_angkor.kpj`, or `./sil.km.cnd.kpj`
+* A local folder containing a .kpj file with a matching base name, e.g.
+ `./keyboards/khmer_angkor` (which contains `khmer_angkor.kpj`), or
+ `./models/sil.km.cnd/` (which contains `sil.km.cnd.kpj`)
+* A cloud keyboard, or cloud lexical model, e.g. `cloud:khmer_angkor`. This
+ retrieves the current source from the Keyman Cloud, which is in the GitHub
+ repository keymanapp/keyboards or keymanapp/lexical-models. The project type
+ will be determined by the id pattern -- either a lexical model
+ `author.bcp47.uniq` id pattern, or a keyboard id pattern (where period `.` is
+ not permitted)
+* A GitHub repository or subfolder within a repository that matches the Keyman
+ keyboard/model repository layout. For example,
+ `https://github.com/keyman-keyboards/khmer_angkor/tree/main/khmer_angkor.kpj`
+
+`-o, --out-path `
+
+: The target folder to write the copied project. The folder must not exist.
+ The folder basename will become the ID of the new project, so the .kpj,
+ .kps, .kmn and similar files will be renamed to match that ID.
+
+`-n, --dry-run`
+
+: Show what would happen, without making changes
+
+### File copying, renaming, and structure rules
+
+The **origin** project folder is the one that contains the .kpj file. When a
+project is copied, referenced files are reorganized into the
+[recommended Keyman project folder structure](../../file-layout). (Note the
+difference between **origin** and `/source`: `/source` is a normal subfolder
+in the recommended Keyman project folder structure).
+
+The destination project is called the **target**.
+
+* The **id** of the project and files can be updated during the copy. The
+ **origin id** is the basename of the **origin** project file. The
+ **target id** is supplied as the `-o` parameter, and becomes both the name of
+ the output folder, and its basename becomes the basename of the **target**
+ project. If other files use the same basename, they will also be updated.
+* All source-type files explicitly referenced in **origin** .kpj will be copied
+ to **target** `/source`, and references will be updated if the filename
+ changes. These are the source-type files:
+ * .kmn keyboard source
+ * .xml LDML keyboard source
+ * .kps package source
+ * .model.ts model source
+* Files referenced by source-type files will be copied to **target** folder
+ structure, if they are also in the **origin** project folder. If the files are
+ outside the **origin** folder, then relative references will be updated.
+* File references in .html and other files are not tracked.
+* For version 1.0 projects, only files explicitly referenced in the project or
+ the source-type files are copied.
+* For version 2.0 projects, all other files in the **origin** folder and
+ subfolders will also be copied to **target**, in the same relative location as
+ they were found in the **origin**. Files which have a **origin id** basename
+ will also be renamed to use the **target id** basename (be aware that this
+ could break untracked references).
+* If a referenced file does not exist, for example the compiled files referenced
+ in a .kps file may not be present, the references will still be updated
+ following the rules above.
+* Unreferenced files in the **origin** project's `build/` folder will not be
+ copied.
+* .kpj.user files will not be copied.
+* .kpj options will be updated to use fixed `source/` and `build/` folders.
+
+## `kmc message` options
+
+One or more message identifiers can be specified for text or json formats.
+
+Message identifiers are 5 hex digit codes, optonally preceded by `KM`, for
+example `KM02001`.
+
+For Markdown format, all messages are always emitted, to `out-path`, and no
+messages can be specified on the command line, with one file per message, and
+index files also generated. The Markdown mode is used to generate the online
+documentation on help.keyman.com.
+
+`-f, --format `
+
+: Output format, one of:
+ * `text`: plain text output to console or file, of specified messages
+ * `json`: JSON formatted to console or file, of specified messages
+ * `markdown`: Markdown formatted text output, to a folder, of all messages
+
+`-o, --out-path `
+
+: Output folder name for Markdown format (required for Markdown), or optional
+ output filename for text and json formats.
+
+`-a, --all-messages`
+
+: Emit descriptions for all messages (text, json formats)
+
diff --git a/developer/docs/help/reference/kmc/index.md b/developer/docs/help/reference/kmc/index.md
new file mode 100644
index 00000000000..1630554982b
--- /dev/null
+++ b/developer/docs/help/reference/kmc/index.md
@@ -0,0 +1,15 @@
+---
+title: Command line compiler kmc
+---
+
+kmc is the Keyman Developer command line compiler interface.
+
+# Using kmc
+
+* [Get started using kmc](cli/get-started)
+* [Migrating from kmcomp to kmc](cli/kmcomp-migration)
+
+# kmc Reference
+
+* [kmc Command Line Reference](cli/reference)
+* [kmc module API Reference](api)
diff --git a/developer/docs/help/reference/messages/common-types.commontypesmessages.md b/developer/docs/help/reference/messages/common-types.commontypesmessages.md
new file mode 100644
index 00000000000..38baeb6e522
--- /dev/null
+++ b/developer/docs/help/reference/messages/common-types.commontypesmessages.md
@@ -0,0 +1,13 @@
+---
+title: Compiler Messages Reference for @keymanapp/common-types
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM01001](km01001) | `ERROR_SchemaValidationError` | Error validating LDML XML file: <param>: <param>: <param> <param>
+[KM01002](km01002) | `ERROR_ImportInvalidBase` | Import element with base <param> is unsupported\. Only cldr is supported\.
+[KM01003](km01003) | `ERROR_ImportInvalidPath` | Import element with invalid path <param>: expected the form '45/\*\.xml
+[KM01004](km01004) | `ERROR_ImportReadFail` | Import could not read data with path <param>: expected the form '45/\*\.xml'
+[KM01005](km01005) | `ERROR_ImportWrongRoot` | Invalid import file <param>: expected <param> as root element\.
+[KM01006](km01006) | `ERROR_ImportMergeFail` | Problem importing <param>: not sure how to handle non\-array <param>\.<param>
+[KM01007](km01007) | `ERROR_TestDataUnexpectedArray` | Problem reading test data: expected single <param> element, found multiple
diff --git a/developer/docs/help/reference/messages/index.md b/developer/docs/help/reference/messages/index.md
new file mode 100644
index 00000000000..907ca597f65
--- /dev/null
+++ b/developer/docs/help/reference/messages/index.md
@@ -0,0 +1,14 @@
+---
+title: Compiler Messages Reference
+---
+
+* [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages)
+* [common-types.CommonTypesMessages](common-types.commontypesmessages)
+* [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages)
+* [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages)
+* [kmc-package.CompilerMessages](kmc-package.compilermessages)
+* [kmc.InfrastructureMessages](kmc.infrastructuremessages)
+* [kmc-analyze.AnalyzerMessages](kmc-analyze.analyzermessages)
+* [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages)
+* [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages)
+* [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages)
diff --git a/developer/docs/help/reference/messages/km00001.md b/developer/docs/help/reference/messages/km00001.md
new file mode 100644
index 00000000000..0a904083131
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00001.md
@@ -0,0 +1,11 @@
+---
+title: KM00001: HINT_NormalizationDisabled
+---
+
+| | |
+|------------|---------- |
+| Message | normalization=disabled is not recommended\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_NormalizationDisabled` |
+
+
diff --git a/developer/docs/help/reference/messages/km00002.md b/developer/docs/help/reference/messages/km00002.md
new file mode 100644
index 00000000000..8d40e1af756
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00002.md
@@ -0,0 +1,11 @@
+---
+title: KM00002: ERROR_InvalidLocale
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid BCP 47 locale form '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidLocale` |
+
+
diff --git a/developer/docs/help/reference/messages/km00003.md b/developer/docs/help/reference/messages/km00003.md
new file mode 100644
index 00000000000..10d25857963
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00003.md
@@ -0,0 +1,11 @@
+---
+title: KM00003: ERROR_HardwareLayerHasTooManyRows
+---
+
+| | |
+|------------|---------- |
+| Message | 'hardware' layer has too many rows |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_HardwareLayerHasTooManyRows` |
+
+
diff --git a/developer/docs/help/reference/messages/km00004.md b/developer/docs/help/reference/messages/km00004.md
new file mode 100644
index 00000000000..662a2b0fcd3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00004.md
@@ -0,0 +1,11 @@
+---
+title: KM00004: ERROR_RowOnHardwareLayerHasTooManyKeys
+---
+
+| | |
+|------------|---------- |
+| Message | Row \#<param> on 'hardware' <param> layer for modifier none has too many keys |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_RowOnHardwareLayerHasTooManyKeys` |
+
+
diff --git a/developer/docs/help/reference/messages/km00005.md b/developer/docs/help/reference/messages/km00005.md
new file mode 100644
index 00000000000..1f6a6a2573b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00005.md
@@ -0,0 +1,11 @@
+---
+title: KM00005: ERROR_KeyNotFoundInKeyBag
+---
+
+| | |
+|------------|---------- |
+| Message | Key '<param>' in position \#<param> on row \#<param> of layer <param>, form '<param>' not found in key bag |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_KeyNotFoundInKeyBag` |
+
+
diff --git a/developer/docs/help/reference/messages/km00006.md b/developer/docs/help/reference/messages/km00006.md
new file mode 100644
index 00000000000..93fe0a33099
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00006.md
@@ -0,0 +1,11 @@
+---
+title: KM00006: HINT_OneOrMoreRepeatedLocales
+---
+
+| | |
+|------------|---------- |
+| Message | After minimization, one or more locales is repeated and has been removed |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_OneOrMoreRepeatedLocales` |
+
+
diff --git a/developer/docs/help/reference/messages/km00007.md b/developer/docs/help/reference/messages/km00007.md
new file mode 100644
index 00000000000..8199e9ba278
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00007.md
@@ -0,0 +1,11 @@
+---
+title: KM00007: ERROR_InvalidFile
+---
+
+| | |
+|------------|---------- |
+| Message | The source file has an invalid structure: <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km00008.md b/developer/docs/help/reference/messages/km00008.md
new file mode 100644
index 00000000000..11fa5bba0ef
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00008.md
@@ -0,0 +1,11 @@
+---
+title: KM00008: HINT_LocaleIsNotMinimalAndClean
+---
+
+| | |
+|------------|---------- |
+| Message | Locale '<param>' is not minimal or correctly formatted and should be '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_LocaleIsNotMinimalAndClean` |
+
+
diff --git a/developer/docs/help/reference/messages/km00009.md b/developer/docs/help/reference/messages/km00009.md
new file mode 100644
index 00000000000..144f9049916
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00009.md
@@ -0,0 +1,11 @@
+---
+title: KM00009: ERROR_InvalidScanCode
+---
+
+| | |
+|------------|---------- |
+| Message | Form '<param>' has invalid/unknown scancodes '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidScanCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000a.md b/developer/docs/help/reference/messages/km0000a.md
new file mode 100644
index 00000000000..7396bd7e0f2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000a.md
@@ -0,0 +1,11 @@
+---
+title: KM0000A: WARN_CustomForm
+---
+
+| | |
+|------------|---------- |
+| Message | Custom <form id="<param>"> element\. Key layout may not be as expected\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `WARN_CustomForm` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000b.md b/developer/docs/help/reference/messages/km0000b.md
new file mode 100644
index 00000000000..0fd4457bec6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000b.md
@@ -0,0 +1,11 @@
+---
+title: KM0000B: ERROR_GestureKeyNotFoundInKeyBag
+---
+
+| | |
+|------------|---------- |
+| Message | Key '<param>' not found in key bag, referenced from other '<param>' in <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_GestureKeyNotFoundInKeyBag` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000d.md b/developer/docs/help/reference/messages/km0000d.md
new file mode 100644
index 00000000000..7ab2b13d3cc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000d.md
@@ -0,0 +1,11 @@
+---
+title: KM0000D: ERROR_InvalidVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Version number '<param>' must be a semantic version format string\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0000e.md b/developer/docs/help/reference/messages/km0000e.md
new file mode 100644
index 00000000000..e14df1843cc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0000e.md
@@ -0,0 +1,11 @@
+---
+title: KM0000E: ERROR_MustBeAtLeastOneLayerElement
+---
+
+| | |
+|------------|---------- |
+| Message | The source file must contain at least one layer element\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MustBeAtLeastOneLayerElement` |
+
+
diff --git a/developer/docs/help/reference/messages/km00010.md b/developer/docs/help/reference/messages/km00010.md
new file mode 100644
index 00000000000..9f0dfbab729
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00010.md
@@ -0,0 +1,11 @@
+---
+title: KM00010: ERROR_DisplayIsRepeated
+---
+
+| | |
+|------------|---------- |
+| Message | display has more than one display entry\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DisplayIsRepeated` |
+
+
diff --git a/developer/docs/help/reference/messages/km00011.md b/developer/docs/help/reference/messages/km00011.md
new file mode 100644
index 00000000000..6f26e3ecf87
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00011.md
@@ -0,0 +1,11 @@
+---
+title: KM00011: ERROR_KeyMissingToGapOrSwitch
+---
+
+| | |
+|------------|---------- |
+| Message | key id='<param>' must have either output=, gap=, or layerId=\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_KeyMissingToGapOrSwitch` |
+
+
diff --git a/developer/docs/help/reference/messages/km00012.md b/developer/docs/help/reference/messages/km00012.md
new file mode 100644
index 00000000000..6f2789649d0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00012.md
@@ -0,0 +1,11 @@
+---
+title: KM00012: ERROR_ExcessHardware
+---
+
+| | |
+|------------|---------- |
+| Message | layers formId=<param>: Can only have one non\-'touch' element |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_ExcessHardware` |
+
+
diff --git a/developer/docs/help/reference/messages/km00013.md b/developer/docs/help/reference/messages/km00013.md
new file mode 100644
index 00000000000..4795d8683c7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00013.md
@@ -0,0 +1,11 @@
+---
+title: KM00013: ERROR_InvalidHardware
+---
+
+| | |
+|------------|---------- |
+| Message | layers has invalid value formId=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidHardware` |
+
+
diff --git a/developer/docs/help/reference/messages/km00014.md b/developer/docs/help/reference/messages/km00014.md
new file mode 100644
index 00000000000..64d65a96da0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00014.md
@@ -0,0 +1,11 @@
+---
+title: KM00014: ERROR_InvalidModifier
+---
+
+| | |
+|------------|---------- |
+| Message | layer has invalid modifiers='<param>' on layer id=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidModifier` |
+
+
diff --git a/developer/docs/help/reference/messages/km00015.md b/developer/docs/help/reference/messages/km00015.md
new file mode 100644
index 00000000000..1102e70a94b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00015.md
@@ -0,0 +1,11 @@
+---
+title: KM00015: ERROR_MissingFlicks
+---
+
+| | |
+|------------|---------- |
+| Message | key id=<param> refers to missing flickId=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingFlicks` |
+
+
diff --git a/developer/docs/help/reference/messages/km00016.md b/developer/docs/help/reference/messages/km00016.md
new file mode 100644
index 00000000000..b6c3a6afb14
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00016.md
@@ -0,0 +1,11 @@
+---
+title: KM00016: ERROR_DuplicateVariable
+---
+
+| | |
+|------------|---------- |
+| Message | duplicate variables: id=<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DuplicateVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km00018.md b/developer/docs/help/reference/messages/km00018.md
new file mode 100644
index 00000000000..a323ee5c648
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00018.md
@@ -0,0 +1,11 @@
+---
+title: KM00018: ERROR_InvalidTransformsType
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transforms types: '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidTransformsType` |
+
+
diff --git a/developer/docs/help/reference/messages/km00019.md b/developer/docs/help/reference/messages/km00019.md
new file mode 100644
index 00000000000..21f25ec3c00
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00019.md
@@ -0,0 +1,11 @@
+---
+title: KM00019: ERROR_DuplicateTransformsType
+---
+
+| | |
+|------------|---------- |
+| Message | Duplicate transforms types: '<param>' |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DuplicateTransformsType` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001a.md b/developer/docs/help/reference/messages/km0001a.md
new file mode 100644
index 00000000000..5d2eae21429
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001a.md
@@ -0,0 +1,11 @@
+---
+title: KM0001A: ERROR_MixedTransformGroup
+---
+
+| | |
+|------------|---------- |
+| Message | transformGroup cannot contain both reorder and transform elements |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MixedTransformGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001b.md b/developer/docs/help/reference/messages/km0001b.md
new file mode 100644
index 00000000000..8ddb410a580
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001b.md
@@ -0,0 +1,11 @@
+---
+title: KM0001B: ERROR_EmptyTransformGroup
+---
+
+| | |
+|------------|---------- |
+| Message | transformGroup must have either reorder or transform elements |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_EmptyTransformGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001c.md b/developer/docs/help/reference/messages/km0001c.md
new file mode 100644
index 00000000000..3338ca0251a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001c.md
@@ -0,0 +1,11 @@
+---
+title: KM0001C: ERROR_MissingStringVariable
+---
+
+| | |
+|------------|---------- |
+| Message | Reference to undefined string variable: $\{<param>\} |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingStringVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001d.md b/developer/docs/help/reference/messages/km0001d.md
new file mode 100644
index 00000000000..d24fa058f93
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001d.md
@@ -0,0 +1,11 @@
+---
+title: KM0001D: ERROR_MissingSetVariable
+---
+
+| | |
+|------------|---------- |
+| Message | Reference to undefined set variable: $\[<param>\] |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingSetVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001e.md b/developer/docs/help/reference/messages/km0001e.md
new file mode 100644
index 00000000000..591f53d60ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001e.md
@@ -0,0 +1,11 @@
+---
+title: KM0001E: ERROR_MissingUnicodeSetVariable
+---
+
+| | |
+|------------|---------- |
+| Message | Reference to undefined UnicodeSet variable: $\[<param>\] |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingUnicodeSetVariable` |
+
+
diff --git a/developer/docs/help/reference/messages/km0001f.md b/developer/docs/help/reference/messages/km0001f.md
new file mode 100644
index 00000000000..03ba8a63ee4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0001f.md
@@ -0,0 +1,11 @@
+---
+title: KM0001F: ERROR_NeedSpacesBetweenSetVariables
+---
+
+| | |
+|------------|---------- |
+| Message | Need spaces between set variables: <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_NeedSpacesBetweenSetVariables` |
+
+
diff --git a/developer/docs/help/reference/messages/km00020.md b/developer/docs/help/reference/messages/km00020.md
new file mode 100644
index 00000000000..fa31310c59f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00020.md
@@ -0,0 +1,11 @@
+---
+title: KM00020: ERROR_CantReferenceSetFromUnicodeSet
+---
+
+| | |
+|------------|---------- |
+| Message | Illegal use of set variable from within UnicodeSet: $\[<param>\] |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_CantReferenceSetFromUnicodeSet` |
+
+
diff --git a/developer/docs/help/reference/messages/km00021.md b/developer/docs/help/reference/messages/km00021.md
new file mode 100644
index 00000000000..7a51803a081
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00021.md
@@ -0,0 +1,11 @@
+---
+title: KM00021: ERROR_MissingMarkers
+---
+
+| | |
+|------------|---------- |
+| Message | Markers used for matching but not defined: <param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_MissingMarkers` |
+
+
diff --git a/developer/docs/help/reference/messages/km00022.md b/developer/docs/help/reference/messages/km00022.md
new file mode 100644
index 00000000000..df332561348
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00022.md
@@ -0,0 +1,11 @@
+---
+title: KM00022: ERROR_DisplayNeedsToOrId
+---
+
+| | |
+|------------|---------- |
+| Message | display needs output= or keyId=, but not both |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_DisplayNeedsToOrId` |
+
+
diff --git a/developer/docs/help/reference/messages/km00023.md b/developer/docs/help/reference/messages/km00023.md
new file mode 100644
index 00000000000..358ea1c1ef4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00023.md
@@ -0,0 +1,11 @@
+---
+title: KM00023: HINT_PUACharacters
+---
+
+| | |
+|------------|---------- |
+| Message | File contains <param> PUA character\(s\), including Illegal \(U\+NAN\) |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_PUACharacters` |
+
+
diff --git a/developer/docs/help/reference/messages/km00024.md b/developer/docs/help/reference/messages/km00024.md
new file mode 100644
index 00000000000..262df5019ea
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00024.md
@@ -0,0 +1,11 @@
+---
+title: KM00024: WARN_UnassignedCharacters
+---
+
+| | |
+|------------|---------- |
+| Message | File contains <param> unassigned character\(s\), including Illegal \(U\+NAN\) |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `WARN_UnassignedCharacters` |
+
+
diff --git a/developer/docs/help/reference/messages/km00025.md b/developer/docs/help/reference/messages/km00025.md
new file mode 100644
index 00000000000..e3946bbc528
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00025.md
@@ -0,0 +1,11 @@
+---
+title: KM00025: ERROR_IllegalCharacters
+---
+
+| | |
+|------------|---------- |
+| Message | File contains <param> illegal character\(s\), including Illegal \(U\+NAN\) |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalCharacters` |
+
+
diff --git a/developer/docs/help/reference/messages/km00026.md b/developer/docs/help/reference/messages/km00026.md
new file mode 100644
index 00000000000..ebeeed31772
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00026.md
@@ -0,0 +1,11 @@
+---
+title: KM00026: HINT_CharClassImplicitDenorm
+---
+
+| | |
+|------------|---------- |
+| Message | File has character classes which span non\-NFD character\(s\), including Illegal \(U\+NAN\)\. These will not match any text\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `HINT_CharClassImplicitDenorm` |
+
+
diff --git a/developer/docs/help/reference/messages/km00027.md b/developer/docs/help/reference/messages/km00027.md
new file mode 100644
index 00000000000..990b9b01a3c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00027.md
@@ -0,0 +1,11 @@
+---
+title: KM00027: WARN_CharClassExplicitDenorm
+---
+
+| | |
+|------------|---------- |
+| Message | File has character classes which include non\-NFD characters\(s\), including Illegal \(U\+NAN\)\. These will not match any text\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `WARN_CharClassExplicitDenorm` |
+
+
diff --git a/developer/docs/help/reference/messages/km00028.md b/developer/docs/help/reference/messages/km00028.md
new file mode 100644
index 00000000000..5f46870d57a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00028.md
@@ -0,0 +1,11 @@
+---
+title: KM00028: ERROR_UnparseableReorderSet
+---
+
+| | |
+|------------|---------- |
+| Message | Illegal UnicodeSet "<param>" in reorder "<param> |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_UnparseableReorderSet` |
+
+
diff --git a/developer/docs/help/reference/messages/km00030.md b/developer/docs/help/reference/messages/km00030.md
new file mode 100644
index 00000000000..43eba550c7c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00030.md
@@ -0,0 +1,11 @@
+---
+title: KM00030: ERROR_InvalidQuadEscape
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid escape "\\u0000"\. Hint: Use "\\u\{<param>\}" |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_InvalidQuadEscape` |
+
+
diff --git a/developer/docs/help/reference/messages/km00f00.md b/developer/docs/help/reference/messages/km00f00.md
new file mode 100644
index 00000000000..6077048b531
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f00.md
@@ -0,0 +1,11 @@
+---
+title: KM00F00: ERROR_UnparseableTransformFrom
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": "<param>" |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_UnparseableTransformFrom` |
+
+
diff --git a/developer/docs/help/reference/messages/km00f01.md b/developer/docs/help/reference/messages/km00f01.md
new file mode 100644
index 00000000000..f2af03199ce
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f01.md
@@ -0,0 +1,11 @@
+---
+title: KM00F01: ERROR_IllegalTransformDollarsign
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": Unescaped dollar\-sign \($\) is not valid transform syntax\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalTransformDollarsign` |
+
+**Hint**: Use `\$` to match a literal dollar-sign.
diff --git a/developer/docs/help/reference/messages/km00f02.md b/developer/docs/help/reference/messages/km00f02.md
new file mode 100644
index 00000000000..51cbc4b4521
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f02.md
@@ -0,0 +1,11 @@
+---
+title: KM00F02: ERROR_TransformFromMatchesNothing
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transfom from="<param>": Matches an empty string\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_TransformFromMatchesNothing` |
+
+
diff --git a/developer/docs/help/reference/messages/km00f03.md b/developer/docs/help/reference/messages/km00f03.md
new file mode 100644
index 00000000000..ac4f22bc7d7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f03.md
@@ -0,0 +1,11 @@
+---
+title: KM00F03: ERROR_IllegalTransformPlus
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": Unescaped plus \(\+\) is not valid transform syntax\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalTransformPlus` |
+
+**Hint**: Use `\+` to match a literal plus.
diff --git a/developer/docs/help/reference/messages/km00f04.md b/developer/docs/help/reference/messages/km00f04.md
new file mode 100644
index 00000000000..2132866bd87
--- /dev/null
+++ b/developer/docs/help/reference/messages/km00f04.md
@@ -0,0 +1,11 @@
+---
+title: KM00F04: ERROR_IllegalTransformAsterisk
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid transform from="<param>": Unescaped asterisk \(\*\) is not valid transform syntax\. |
+| Module | [kmc-ldml.CompilerMessages](kmc-ldml.compilermessages) |
+| Identifier | `ERROR_IllegalTransformAsterisk` |
+
+**Hint**: Use `\*` to match a literal asterisk.
diff --git a/developer/docs/help/reference/messages/km01001.md b/developer/docs/help/reference/messages/km01001.md
new file mode 100644
index 00000000000..a4440ecb017
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01001.md
@@ -0,0 +1,11 @@
+---
+title: KM01001: ERROR_SchemaValidationError
+---
+
+| | |
+|------------|---------- |
+| Message | Error validating LDML XML file: <param>: <param>: <param> <param> |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_SchemaValidationError` |
+
+
diff --git a/developer/docs/help/reference/messages/km01002.md b/developer/docs/help/reference/messages/km01002.md
new file mode 100644
index 00000000000..63338ff9775
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01002.md
@@ -0,0 +1,11 @@
+---
+title: KM01002: ERROR_ImportInvalidBase
+---
+
+| | |
+|------------|---------- |
+| Message | Import element with base <param> is unsupported\. Only cldr is supported\. |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportInvalidBase` |
+
+
diff --git a/developer/docs/help/reference/messages/km01003.md b/developer/docs/help/reference/messages/km01003.md
new file mode 100644
index 00000000000..a8ce37a80e0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01003.md
@@ -0,0 +1,11 @@
+---
+title: KM01003: ERROR_ImportInvalidPath
+---
+
+| | |
+|------------|---------- |
+| Message | Import element with invalid path <param>: expected the form '45/\*\.xml |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportInvalidPath` |
+
+
diff --git a/developer/docs/help/reference/messages/km01004.md b/developer/docs/help/reference/messages/km01004.md
new file mode 100644
index 00000000000..279786f9b6d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01004.md
@@ -0,0 +1,11 @@
+---
+title: KM01004: ERROR_ImportReadFail
+---
+
+| | |
+|------------|---------- |
+| Message | Import could not read data with path <param>: expected the form '45/\*\.xml' |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportReadFail` |
+
+
diff --git a/developer/docs/help/reference/messages/km01005.md b/developer/docs/help/reference/messages/km01005.md
new file mode 100644
index 00000000000..2d032c4ed5c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01005.md
@@ -0,0 +1,11 @@
+---
+title: KM01005: ERROR_ImportWrongRoot
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid import file <param>: expected <param> as root element\. |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportWrongRoot` |
+
+
diff --git a/developer/docs/help/reference/messages/km01006.md b/developer/docs/help/reference/messages/km01006.md
new file mode 100644
index 00000000000..1e04d8c403c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01006.md
@@ -0,0 +1,11 @@
+---
+title: KM01006: ERROR_ImportMergeFail
+---
+
+| | |
+|------------|---------- |
+| Message | Problem importing <param>: not sure how to handle non\-array <param>\.<param> |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_ImportMergeFail` |
+
+
diff --git a/developer/docs/help/reference/messages/km01007.md b/developer/docs/help/reference/messages/km01007.md
new file mode 100644
index 00000000000..e9a0dc89f77
--- /dev/null
+++ b/developer/docs/help/reference/messages/km01007.md
@@ -0,0 +1,11 @@
+---
+title: KM01007: ERROR_TestDataUnexpectedArray
+---
+
+| | |
+|------------|---------- |
+| Message | Problem reading test data: expected single <param> element, found multiple |
+| Module | [common-types.CommonTypesMessages](common-types.commontypesmessages) |
+| Identifier | `ERROR_TestDataUnexpectedArray` |
+
+
diff --git a/developer/docs/help/reference/messages/km02001.md b/developer/docs/help/reference/messages/km02001.md
new file mode 100644
index 00000000000..4532a816790
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02001.md
@@ -0,0 +1,11 @@
+---
+title: KM02001: INFO_EndOfFile
+---
+
+| | |
+|------------|---------- |
+| Message | \(no error \- reserved code\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `INFO_EndOfFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02002.md b/developer/docs/help/reference/messages/km02002.md
new file mode 100644
index 00000000000..3b346a77449
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02002.md
@@ -0,0 +1,11 @@
+---
+title: KM02002: FATAL_BadCallParams
+---
+
+| | |
+|------------|---------- |
+| Message | CompileKeyboardFile was called with bad parameters |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_BadCallParams` |
+
+
diff --git a/developer/docs/help/reference/messages/km02004.md b/developer/docs/help/reference/messages/km02004.md
new file mode 100644
index 00000000000..c583d5ef786
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02004.md
@@ -0,0 +1,11 @@
+---
+title: KM02004: FATAL_CannotAllocateMemory
+---
+
+| | |
+|------------|---------- |
+| Message | Out of memory |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_CannotAllocateMemory` |
+
+
diff --git a/developer/docs/help/reference/messages/km02005.md b/developer/docs/help/reference/messages/km02005.md
new file mode 100644
index 00000000000..4713ec22cc4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02005.md
@@ -0,0 +1,11 @@
+---
+title: KM02005: ERROR_InfileNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot find the input file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InfileNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km02007.md b/developer/docs/help/reference/messages/km02007.md
new file mode 100644
index 00000000000..4690c3c53e7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02007.md
@@ -0,0 +1,11 @@
+---
+title: KM02007: FATAL_UnableToWriteFully
+---
+
+| | |
+|------------|---------- |
+| Message | Unable to write the file completely |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_UnableToWriteFully` |
+
+
diff --git a/developer/docs/help/reference/messages/km02008.md b/developer/docs/help/reference/messages/km02008.md
new file mode 100644
index 00000000000..31eddb1e375
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02008.md
@@ -0,0 +1,11 @@
+---
+title: KM02008: ERROR_CannotReadInfile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot read the input file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotReadInfile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02009.md b/developer/docs/help/reference/messages/km02009.md
new file mode 100644
index 00000000000..d1e3ec2155b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02009.md
@@ -0,0 +1,11 @@
+---
+title: KM02009: FATAL_SomewhereIGotItWrong
+---
+
+| | |
+|------------|---------- |
+| Message | Internal error: contact Keyman |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_SomewhereIGotItWrong` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200a.md b/developer/docs/help/reference/messages/km0200a.md
new file mode 100644
index 00000000000..c75422a406a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200a.md
@@ -0,0 +1,11 @@
+---
+title: KM0200A: ERROR_InvalidToken
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid token found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidToken` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200b.md b/developer/docs/help/reference/messages/km0200b.md
new file mode 100644
index 00000000000..41e821d0184
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200b.md
@@ -0,0 +1,11 @@
+---
+title: KM0200B: ERROR_InvalidBegin
+---
+
+| | |
+|------------|---------- |
+| Message | A "begin unicode" statement is required to compile a KeymanWeb keyboard |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidBegin` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200c.md b/developer/docs/help/reference/messages/km0200c.md
new file mode 100644
index 00000000000..8567272ba11
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200c.md
@@ -0,0 +1,11 @@
+---
+title: KM0200C: ERROR_InvalidName
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'name' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidName` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200d.md b/developer/docs/help/reference/messages/km0200d.md
new file mode 100644
index 00000000000..41ac2e2b613
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200d.md
@@ -0,0 +1,11 @@
+---
+title: KM0200D: ERROR_InvalidVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'version' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200e.md b/developer/docs/help/reference/messages/km0200e.md
new file mode 100644
index 00000000000..0853617cc4a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200e.md
@@ -0,0 +1,11 @@
+---
+title: KM0200E: ERROR_InvalidLanguageLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'language' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLanguageLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km0200f.md b/developer/docs/help/reference/messages/km0200f.md
new file mode 100644
index 00000000000..f894cd6f49b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0200f.md
@@ -0,0 +1,11 @@
+---
+title: KM0200F: ERROR_LayoutButNoLanguage
+---
+
+| | |
+|------------|---------- |
+| Message | Layout command found but no language command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_LayoutButNoLanguage` |
+
+
diff --git a/developer/docs/help/reference/messages/km02010.md b/developer/docs/help/reference/messages/km02010.md
new file mode 100644
index 00000000000..b5cb984cf47
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02010.md
@@ -0,0 +1,11 @@
+---
+title: KM02010: ERROR_InvalidLayoutLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'layout' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLayoutLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02011.md b/developer/docs/help/reference/messages/km02011.md
new file mode 100644
index 00000000000..07658b237ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02011.md
@@ -0,0 +1,11 @@
+---
+title: KM02011: ERROR_NoVersionLine
+---
+
+| | |
+|------------|---------- |
+| Message | No version line found for file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NoVersionLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02012.md b/developer/docs/help/reference/messages/km02012.md
new file mode 100644
index 00000000000..fa208fab3ea
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02012.md
@@ -0,0 +1,11 @@
+---
+title: KM02012: ERROR_InvalidGroupLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'group' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidGroupLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02013.md b/developer/docs/help/reference/messages/km02013.md
new file mode 100644
index 00000000000..924407d9e97
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02013.md
@@ -0,0 +1,11 @@
+---
+title: KM02013: ERROR_InvalidStoreLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'store' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidStoreLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02014.md b/developer/docs/help/reference/messages/km02014.md
new file mode 100644
index 00000000000..6d010dc536d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02014.md
@@ -0,0 +1,11 @@
+---
+title: KM02014: ERROR_InvalidCodeInKeyPartOfRule
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid command or code found in key part of rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCodeInKeyPartOfRule` |
+
+
diff --git a/developer/docs/help/reference/messages/km02015.md b/developer/docs/help/reference/messages/km02015.md
new file mode 100644
index 00000000000..01006037b26
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02015.md
@@ -0,0 +1,11 @@
+---
+title: KM02015: ERROR_InvalidDeadkey
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'deadkey' or 'dk' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidDeadkey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02016.md b/developer/docs/help/reference/messages/km02016.md
new file mode 100644
index 00000000000..603122cd62d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02016.md
@@ -0,0 +1,11 @@
+---
+title: KM02016: ERROR_InvalidValue
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid value in extended string |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidValue` |
+
+
diff --git a/developer/docs/help/reference/messages/km02017.md b/developer/docs/help/reference/messages/km02017.md
new file mode 100644
index 00000000000..74a0a5c5dfc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02017.md
@@ -0,0 +1,11 @@
+---
+title: KM02017: ERROR_ZeroLengthString
+---
+
+| | |
+|------------|---------- |
+| Message | A string of zero characters was found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ZeroLengthString` |
+
+
diff --git a/developer/docs/help/reference/messages/km02018.md b/developer/docs/help/reference/messages/km02018.md
new file mode 100644
index 00000000000..662e2a7b544
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02018.md
@@ -0,0 +1,11 @@
+---
+title: KM02018: ERROR_TooManyIndexToKeyRefs
+---
+
+| | |
+|------------|---------- |
+| Message | Too many index commands refering to key string |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_TooManyIndexToKeyRefs` |
+
+
diff --git a/developer/docs/help/reference/messages/km02019.md b/developer/docs/help/reference/messages/km02019.md
new file mode 100644
index 00000000000..6ff35e690a6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02019.md
@@ -0,0 +1,11 @@
+---
+title: KM02019: ERROR_UnterminatedString
+---
+
+| | |
+|------------|---------- |
+| Message | Unterminated string in line |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnterminatedString` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201a.md b/developer/docs/help/reference/messages/km0201a.md
new file mode 100644
index 00000000000..c5be97b8103
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201a.md
@@ -0,0 +1,11 @@
+---
+title: KM0201A: ERROR_StringInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | extend string illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_StringInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201b.md b/developer/docs/help/reference/messages/km0201b.md
new file mode 100644
index 00000000000..142b6b5f9a0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201b.md
@@ -0,0 +1,11 @@
+---
+title: KM0201B: ERROR_AnyInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'any' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_AnyInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201c.md b/developer/docs/help/reference/messages/km0201c.md
new file mode 100644
index 00000000000..353337f6789
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201c.md
@@ -0,0 +1,11 @@
+---
+title: KM0201C: ERROR_InvalidAny
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'any' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidAny` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201d.md b/developer/docs/help/reference/messages/km0201d.md
new file mode 100644
index 00000000000..e998ebfd8f8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201d.md
@@ -0,0 +1,11 @@
+---
+title: KM0201D: ERROR_StoreDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Store referenced does not exist |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_StoreDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201e.md b/developer/docs/help/reference/messages/km0201e.md
new file mode 100644
index 00000000000..f6af856370a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201e.md
@@ -0,0 +1,11 @@
+---
+title: KM0201E: ERROR_BeepInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'beep' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_BeepInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0201f.md b/developer/docs/help/reference/messages/km0201f.md
new file mode 100644
index 00000000000..30c1123411b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0201f.md
@@ -0,0 +1,11 @@
+---
+title: KM0201F: ERROR_IndexInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'index' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_IndexInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02020.md b/developer/docs/help/reference/messages/km02020.md
new file mode 100644
index 00000000000..ab2c0c7177f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02020.md
@@ -0,0 +1,11 @@
+---
+title: KM02020: ERROR_InvalidIndex
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'index' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidIndex` |
+
+
diff --git a/developer/docs/help/reference/messages/km02021.md b/developer/docs/help/reference/messages/km02021.md
new file mode 100644
index 00000000000..4082b601640
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02021.md
@@ -0,0 +1,11 @@
+---
+title: KM02021: ERROR_OutsInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'outs' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_OutsInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02022.md b/developer/docs/help/reference/messages/km02022.md
new file mode 100644
index 00000000000..10887e79098
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02022.md
@@ -0,0 +1,11 @@
+---
+title: KM02022: ERROR_InvalidOuts
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'outs' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidOuts` |
+
+
diff --git a/developer/docs/help/reference/messages/km02024.md b/developer/docs/help/reference/messages/km02024.md
new file mode 100644
index 00000000000..bee175522c1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02024.md
@@ -0,0 +1,11 @@
+---
+title: KM02024: ERROR_ContextInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'context' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ContextInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02025.md b/developer/docs/help/reference/messages/km02025.md
new file mode 100644
index 00000000000..74183960bcb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02025.md
@@ -0,0 +1,11 @@
+---
+title: KM02025: ERROR_InvalidUse
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'use' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidUse` |
+
+
diff --git a/developer/docs/help/reference/messages/km02026.md b/developer/docs/help/reference/messages/km02026.md
new file mode 100644
index 00000000000..3f23dc452bd
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02026.md
@@ -0,0 +1,11 @@
+---
+title: KM02026: ERROR_GroupDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Group does not exist |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_GroupDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km02027.md b/developer/docs/help/reference/messages/km02027.md
new file mode 100644
index 00000000000..62339e8f9c2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02027.md
@@ -0,0 +1,11 @@
+---
+title: KM02027: ERROR_VirtualKeyNotAllowedHere
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual key is not allowed here |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeyNotAllowedHere` |
+
+
diff --git a/developer/docs/help/reference/messages/km02028.md b/developer/docs/help/reference/messages/km02028.md
new file mode 100644
index 00000000000..cf0861c63be
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02028.md
@@ -0,0 +1,11 @@
+---
+title: KM02028: ERROR_InvalidSwitch
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'switch' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSwitch` |
+
+
diff --git a/developer/docs/help/reference/messages/km02029.md b/developer/docs/help/reference/messages/km02029.md
new file mode 100644
index 00000000000..4033972752b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02029.md
@@ -0,0 +1,11 @@
+---
+title: KM02029: ERROR_NoTokensFound
+---
+
+| | |
+|------------|---------- |
+| Message | No tokens found in line |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NoTokensFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202a.md b/developer/docs/help/reference/messages/km0202a.md
new file mode 100644
index 00000000000..163d7072039
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202a.md
@@ -0,0 +1,11 @@
+---
+title: KM0202A: ERROR_InvalidLineContinuation
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid line continuation |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLineContinuation` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202b.md b/developer/docs/help/reference/messages/km0202b.md
new file mode 100644
index 00000000000..72457a2dc4f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202b.md
@@ -0,0 +1,11 @@
+---
+title: KM0202B: ERROR_LineTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Line too long |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_LineTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202c.md b/developer/docs/help/reference/messages/km0202c.md
new file mode 100644
index 00000000000..9e5260a5eed
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202c.md
@@ -0,0 +1,11 @@
+---
+title: KM0202C: ERROR_InvalidCopyright
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'copyright' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCopyright` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202d.md b/developer/docs/help/reference/messages/km0202d.md
new file mode 100644
index 00000000000..523b8123df4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202d.md
@@ -0,0 +1,11 @@
+---
+title: KM0202D: ERROR_CodeInvalidInThisSection
+---
+
+| | |
+|------------|---------- |
+| Message | This line is invalid in this section of the file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CodeInvalidInThisSection` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202e.md b/developer/docs/help/reference/messages/km0202e.md
new file mode 100644
index 00000000000..1d9acf621e0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202e.md
@@ -0,0 +1,11 @@
+---
+title: KM0202E: ERROR_InvalidMessage
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'message' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidMessage` |
+
+
diff --git a/developer/docs/help/reference/messages/km0202f.md b/developer/docs/help/reference/messages/km0202f.md
new file mode 100644
index 00000000000..606967be92f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0202f.md
@@ -0,0 +1,11 @@
+---
+title: KM0202F: ERROR_InvalidLanguageName
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'languagename' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidLanguageName` |
+
+
diff --git a/developer/docs/help/reference/messages/km02030.md b/developer/docs/help/reference/messages/km02030.md
new file mode 100644
index 00000000000..5c912beafae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02030.md
@@ -0,0 +1,11 @@
+---
+title: KM02030: ERROR_InvalidBitmapLine
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid 'bitmaps' command |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidBitmapLine` |
+
+
diff --git a/developer/docs/help/reference/messages/km02031.md b/developer/docs/help/reference/messages/km02031.md
new file mode 100644
index 00000000000..b73d1a59ea4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02031.md
@@ -0,0 +1,11 @@
+---
+title: KM02031: ERROR_CannotReadBitmapFile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot open the bitmap or icon file for reading |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotReadBitmapFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02032.md b/developer/docs/help/reference/messages/km02032.md
new file mode 100644
index 00000000000..a555afd4d6c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02032.md
@@ -0,0 +1,11 @@
+---
+title: KM02032: ERROR_IndexDoesNotPointToAny
+---
+
+| | |
+|------------|---------- |
+| Message | An index\(\) in the output does not have a corresponding any\(\) statement |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_IndexDoesNotPointToAny` |
+
+
diff --git a/developer/docs/help/reference/messages/km02033.md b/developer/docs/help/reference/messages/km02033.md
new file mode 100644
index 00000000000..7980984fff2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02033.md
@@ -0,0 +1,11 @@
+---
+title: KM02033: ERROR_ReservedCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A reserved character was found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ReservedCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km02034.md b/developer/docs/help/reference/messages/km02034.md
new file mode 100644
index 00000000000..d5a1a6f8390
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02034.md
@@ -0,0 +1,11 @@
+---
+title: KM02034: ERROR_InvalidCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A character was found that is outside the valid Unicode range \(U\+0000 \- U\+10FFFF\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km02035.md b/developer/docs/help/reference/messages/km02035.md
new file mode 100644
index 00000000000..e8141d6387e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02035.md
@@ -0,0 +1,11 @@
+---
+title: KM02035: ERROR_InvalidCall
+---
+
+| | |
+|------------|---------- |
+| Message | The 'call' command is invalid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidCall` |
+
+
diff --git a/developer/docs/help/reference/messages/km02036.md b/developer/docs/help/reference/messages/km02036.md
new file mode 100644
index 00000000000..ab056bf0a27
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02036.md
@@ -0,0 +1,11 @@
+---
+title: KM02036: ERROR_CallInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | 'call' command is illegal in virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CallInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02037.md b/developer/docs/help/reference/messages/km02037.md
new file mode 100644
index 00000000000..9b15dc2d7f0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02037.md
@@ -0,0 +1,11 @@
+---
+title: KM02037: ERROR_CodeInvalidInKeyStore
+---
+
+| | |
+|------------|---------- |
+| Message | The command is invalid inside a store that is used in a key part of the rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CodeInvalidInKeyStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02038.md b/developer/docs/help/reference/messages/km02038.md
new file mode 100644
index 00000000000..2b4e35050b3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02038.md
@@ -0,0 +1,11 @@
+---
+title: KM02038: ERROR_CannotLoadIncludeFile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot load the included file: it is either invalid or does not exist |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotLoadIncludeFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km02039.md b/developer/docs/help/reference/messages/km02039.md
new file mode 100644
index 00000000000..5b9a6cbe0db
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02039.md
@@ -0,0 +1,11 @@
+---
+title: KM02039: ERROR_60FeatureOnly_EthnologueCode
+---
+
+| | |
+|------------|---------- |
+| Message | EthnologueCode system store requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_EthnologueCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203a.md b/developer/docs/help/reference/messages/km0203a.md
new file mode 100644
index 00000000000..02e84c70f92
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203a.md
@@ -0,0 +1,11 @@
+---
+title: KM0203A: ERROR_60FeatureOnly_MnemonicLayout
+---
+
+| | |
+|------------|---------- |
+| Message | MnemonicLayout functionality requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_MnemonicLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203b.md b/developer/docs/help/reference/messages/km0203b.md
new file mode 100644
index 00000000000..3dff8c79869
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203b.md
@@ -0,0 +1,11 @@
+---
+title: KM0203B: ERROR_60FeatureOnly_OldCharPosMatching
+---
+
+| | |
+|------------|---------- |
+| Message | OldCharPosMatching system store requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_OldCharPosMatching` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203c.md b/developer/docs/help/reference/messages/km0203c.md
new file mode 100644
index 00000000000..270b19f3f9f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203c.md
@@ -0,0 +1,11 @@
+---
+title: KM0203C: ERROR_60FeatureOnly_NamedCodes
+---
+
+| | |
+|------------|---------- |
+| Message | Named character constants requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_NamedCodes` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203d.md b/developer/docs/help/reference/messages/km0203d.md
new file mode 100644
index 00000000000..bc67492e361
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203d.md
@@ -0,0 +1,11 @@
+---
+title: KM0203D: ERROR_60FeatureOnly_Contextn
+---
+
+| | |
+|------------|---------- |
+| Message | Context\(n\) requires VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_Contextn` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203e.md b/developer/docs/help/reference/messages/km0203e.md
new file mode 100644
index 00000000000..4f4e91b205b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203e.md
@@ -0,0 +1,11 @@
+---
+title: KM0203E: ERROR_501FeatureOnly_Call
+---
+
+| | |
+|------------|---------- |
+| Message | Call\(\) requires VERSION 5\.01 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_501FeatureOnly_Call` |
+
+
diff --git a/developer/docs/help/reference/messages/km0203f.md b/developer/docs/help/reference/messages/km0203f.md
new file mode 100644
index 00000000000..c9348072966
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0203f.md
@@ -0,0 +1,11 @@
+---
+title: KM0203F: ERROR_InvalidNamedCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid named code constant |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidNamedCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km02040.md b/developer/docs/help/reference/messages/km02040.md
new file mode 100644
index 00000000000..6d6884f63af
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02040.md
@@ -0,0 +1,11 @@
+---
+title: KM02040: ERROR_InvalidSystemStore
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid system store name found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSystemStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02044.md b/developer/docs/help/reference/messages/km02044.md
new file mode 100644
index 00000000000..9d64c33fb04
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02044.md
@@ -0,0 +1,11 @@
+---
+title: KM02044: ERROR_60FeatureOnly_VirtualCharKey
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual character keys require VERSION 6\.0 or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_60FeatureOnly_VirtualCharKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02045.md b/developer/docs/help/reference/messages/km02045.md
new file mode 100644
index 00000000000..ff5f5707a6a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02045.md
@@ -0,0 +1,11 @@
+---
+title: KM02045: ERROR_VersionAlreadyIncluded
+---
+
+| | |
+|------------|---------- |
+| Message | Only one VERSION or store\(version\) line allowed in a source file\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VersionAlreadyIncluded` |
+
+
diff --git a/developer/docs/help/reference/messages/km02046.md b/developer/docs/help/reference/messages/km02046.md
new file mode 100644
index 00000000000..2e7458348ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02046.md
@@ -0,0 +1,11 @@
+---
+title: KM02046: ERROR_70FeatureOnly
+---
+
+| | |
+|------------|---------- |
+| Message | This feature requires store\(version\) '7\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_70FeatureOnly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02047.md b/developer/docs/help/reference/messages/km02047.md
new file mode 100644
index 00000000000..4c7d6446ef8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02047.md
@@ -0,0 +1,11 @@
+---
+title: KM02047: ERROR_80FeatureOnly
+---
+
+| | |
+|------------|---------- |
+| Message | This feature requires store\(version\) '8\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_80FeatureOnly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02048.md b/developer/docs/help/reference/messages/km02048.md
new file mode 100644
index 00000000000..e5f84c82e69
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02048.md
@@ -0,0 +1,11 @@
+---
+title: KM02048: ERROR_InvalidInVirtualKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | This statement is not valid in a virtual key section |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidInVirtualKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02049.md b/developer/docs/help/reference/messages/km02049.md
new file mode 100644
index 00000000000..ead8d86ad6d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02049.md
@@ -0,0 +1,11 @@
+---
+title: KM02049: ERROR_InvalidIf
+---
+
+| | |
+|------------|---------- |
+| Message | The if\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidIf` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204a.md b/developer/docs/help/reference/messages/km0204a.md
new file mode 100644
index 00000000000..8b5878c7aa2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204a.md
@@ -0,0 +1,11 @@
+---
+title: KM0204A: ERROR_InvalidReset
+---
+
+| | |
+|------------|---------- |
+| Message | The reset\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidReset` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204b.md b/developer/docs/help/reference/messages/km0204b.md
new file mode 100644
index 00000000000..aa837630090
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204b.md
@@ -0,0 +1,11 @@
+---
+title: KM0204B: ERROR_InvalidSet
+---
+
+| | |
+|------------|---------- |
+| Message | The set\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSet` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204c.md b/developer/docs/help/reference/messages/km0204c.md
new file mode 100644
index 00000000000..9cedcbd3250
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204c.md
@@ -0,0 +1,11 @@
+---
+title: KM0204C: ERROR_InvalidSave
+---
+
+| | |
+|------------|---------- |
+| Message | The save\(\) statement is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidSave` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204d.md b/developer/docs/help/reference/messages/km0204d.md
new file mode 100644
index 00000000000..a71ea826f65
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204d.md
@@ -0,0 +1,11 @@
+---
+title: KM0204D: ERROR_InvalidEthnologueCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid ethnologuecode format |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidEthnologueCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204e.md b/developer/docs/help/reference/messages/km0204e.md
new file mode 100644
index 00000000000..22c4c97da4e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204e.md
@@ -0,0 +1,11 @@
+---
+title: KM0204E: FATAL_CannotCreateTempfile
+---
+
+| | |
+|------------|---------- |
+| Message | Cannot create temp file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_CannotCreateTempfile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0204f.md b/developer/docs/help/reference/messages/km0204f.md
new file mode 100644
index 00000000000..846eff8be96
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0204f.md
@@ -0,0 +1,11 @@
+---
+title: KM0204F: ERROR_90FeatureOnly_IfSystemStores
+---
+
+| | |
+|------------|---------- |
+| Message | if\(store\) requires store\(version\) '9\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnly_IfSystemStores` |
+
+
diff --git a/developer/docs/help/reference/messages/km02050.md b/developer/docs/help/reference/messages/km02050.md
new file mode 100644
index 00000000000..facdd2272a2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02050.md
@@ -0,0 +1,11 @@
+---
+title: KM02050: ERROR_IfSystemStore_NotFound
+---
+
+| | |
+|------------|---------- |
+| Message | System store in if\(\) not found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_IfSystemStore_NotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km02051.md b/developer/docs/help/reference/messages/km02051.md
new file mode 100644
index 00000000000..3eda67010db
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02051.md
@@ -0,0 +1,11 @@
+---
+title: KM02051: ERROR_90FeatureOnly_SetSystemStores
+---
+
+| | |
+|------------|---------- |
+| Message | set\(store\) requires store\(version\) '9\.0' or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnly_SetSystemStores` |
+
+
diff --git a/developer/docs/help/reference/messages/km02052.md b/developer/docs/help/reference/messages/km02052.md
new file mode 100644
index 00000000000..caaa5e7235e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02052.md
@@ -0,0 +1,11 @@
+---
+title: KM02052: ERROR_SetSystemStore_NotFound
+---
+
+| | |
+|------------|---------- |
+| Message | System store in set\(\) not found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_SetSystemStore_NotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km02053.md b/developer/docs/help/reference/messages/km02053.md
new file mode 100644
index 00000000000..1e2558d4c87
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02053.md
@@ -0,0 +1,11 @@
+---
+title: KM02053: ERROR_90FeatureOnlyVirtualKeyDictionary
+---
+
+| | |
+|------------|---------- |
+| Message | Custom virtual key names require store\(version\) '9\.0' |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyVirtualKeyDictionary` |
+
+
diff --git a/developer/docs/help/reference/messages/km02054.md b/developer/docs/help/reference/messages/km02054.md
new file mode 100644
index 00000000000..c14ba1f9490
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02054.md
@@ -0,0 +1,11 @@
+---
+title: KM02054: ERROR_NotSupportedInKeymanWebContext
+---
+
+| | |
+|------------|---------- |
+| Message | Statement '<param>' is not currently supported in context for web and touch targets |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NotSupportedInKeymanWebContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km02055.md b/developer/docs/help/reference/messages/km02055.md
new file mode 100644
index 00000000000..081447004e3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02055.md
@@ -0,0 +1,11 @@
+---
+title: KM02055: ERROR_NotSupportedInKeymanWebOutput
+---
+
+| | |
+|------------|---------- |
+| Message | Statement '<param>' is not currently supported in output for web and touch targets |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NotSupportedInKeymanWebOutput` |
+
+
diff --git a/developer/docs/help/reference/messages/km02056.md b/developer/docs/help/reference/messages/km02056.md
new file mode 100644
index 00000000000..4e7a9e6e03b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02056.md
@@ -0,0 +1,11 @@
+---
+title: KM02056: ERROR_NotSupportedInKeymanWebStore
+---
+
+| | |
+|------------|---------- |
+| Message | '<param>' is not currently supported in store '<param>' when used by any or index for web and touch targets |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NotSupportedInKeymanWebStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02057.md b/developer/docs/help/reference/messages/km02057.md
new file mode 100644
index 00000000000..b74ebea922a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02057.md
@@ -0,0 +1,11 @@
+---
+title: KM02057: ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual character keys not currently supported in KeymanWeb |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb` |
+
+
diff --git a/developer/docs/help/reference/messages/km02058.md b/developer/docs/help/reference/messages/km02058.md
new file mode 100644
index 00000000000..b6b63eae273
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02058.md
@@ -0,0 +1,11 @@
+---
+title: KM02058: ERROR_VirtualKeysNotValidForMnemonicLayouts
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual keys are not valid for mnemonic layouts |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeysNotValidForMnemonicLayouts` |
+
+
diff --git a/developer/docs/help/reference/messages/km02059.md b/developer/docs/help/reference/messages/km02059.md
new file mode 100644
index 00000000000..a100e56abef
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02059.md
@@ -0,0 +1,11 @@
+---
+title: KM02059: ERROR_InvalidTouchLayoutFile
+---
+
+| | |
+|------------|---------- |
+| Message | Touch layout file <param> is not valid |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidTouchLayoutFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205a.md b/developer/docs/help/reference/messages/km0205a.md
new file mode 100644
index 00000000000..625b46b4b9a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205a.md
@@ -0,0 +1,11 @@
+---
+title: KM0205A: ERROR_TouchLayoutInvalidIdentifier
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on "<param>", layer "<param>" has an invalid identifier\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_TouchLayoutInvalidIdentifier` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205b.md b/developer/docs/help/reference/messages/km0205b.md
new file mode 100644
index 00000000000..e09dc68039b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205b.md
@@ -0,0 +1,11 @@
+---
+title: KM0205B: ERROR_InvalidKeyCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid key identifier "<param>" |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidKeyCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205c.md b/developer/docs/help/reference/messages/km0205c.md
new file mode 100644
index 00000000000..563f7650e9d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205c.md
@@ -0,0 +1,11 @@
+---
+title: KM0205C: ERROR_90FeatureOnlyLayoutFile
+---
+
+| | |
+|------------|---------- |
+| Message | Touch layout file reference requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyLayoutFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205d.md b/developer/docs/help/reference/messages/km0205d.md
new file mode 100644
index 00000000000..67620cfbc98
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205d.md
@@ -0,0 +1,11 @@
+---
+title: KM0205D: ERROR_90FeatureOnlyKeyboardVersion
+---
+
+| | |
+|------------|---------- |
+| Message | KeyboardVersion system store requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyKeyboardVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205e.md b/developer/docs/help/reference/messages/km0205e.md
new file mode 100644
index 00000000000..12316dac807
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205e.md
@@ -0,0 +1,11 @@
+---
+title: KM0205E: ERROR_KeyboardVersionFormatInvalid
+---
+
+| | |
+|------------|---------- |
+| Message | KeyboardVersion format is invalid, expecting dot\-separated integers |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_KeyboardVersionFormatInvalid` |
+
+
diff --git a/developer/docs/help/reference/messages/km0205f.md b/developer/docs/help/reference/messages/km0205f.md
new file mode 100644
index 00000000000..0c79b8ba4b7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0205f.md
@@ -0,0 +1,11 @@
+---
+title: KM0205F: ERROR_ContextExHasInvalidOffset
+---
+
+| | |
+|------------|---------- |
+| Message | context\(\) statement has offset out of range |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ContextExHasInvalidOffset` |
+
+
diff --git a/developer/docs/help/reference/messages/km02060.md b/developer/docs/help/reference/messages/km02060.md
new file mode 100644
index 00000000000..c505c6d3f7c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02060.md
@@ -0,0 +1,11 @@
+---
+title: KM02060: ERROR_90FeatureOnlyEmbedCSS
+---
+
+| | |
+|------------|---------- |
+| Message | Embedding CSS requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyEmbedCSS` |
+
+
diff --git a/developer/docs/help/reference/messages/km02061.md b/developer/docs/help/reference/messages/km02061.md
new file mode 100644
index 00000000000..61240ea35fc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02061.md
@@ -0,0 +1,11 @@
+---
+title: KM02061: ERROR_90FeatureOnlyTargets
+---
+
+| | |
+|------------|---------- |
+| Message | TARGETS system store requires store\(version\) '9\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_90FeatureOnlyTargets` |
+
+
diff --git a/developer/docs/help/reference/messages/km02062.md b/developer/docs/help/reference/messages/km02062.md
new file mode 100644
index 00000000000..6b6bc4b2cae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02062.md
@@ -0,0 +1,11 @@
+---
+title: KM02062: ERROR_ContextAndIndexInvalidInMatchNomatch
+---
+
+| | |
+|------------|---------- |
+| Message | context and index statements cannot be used in a match or nomatch statement |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ContextAndIndexInvalidInMatchNomatch` |
+
+
diff --git a/developer/docs/help/reference/messages/km02063.md b/developer/docs/help/reference/messages/km02063.md
new file mode 100644
index 00000000000..a947616a008
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02063.md
@@ -0,0 +1,11 @@
+---
+title: KM02063: ERROR_140FeatureOnlyContextAndNotAnyWeb
+---
+
+| | |
+|------------|---------- |
+| Message | For web and touch platforms, context\(\) statement referring to notany\(\) requires store\(version\) '14\.0'or higher |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_140FeatureOnlyContextAndNotAnyWeb` |
+
+
diff --git a/developer/docs/help/reference/messages/km02064.md b/developer/docs/help/reference/messages/km02064.md
new file mode 100644
index 00000000000..84cf5a156af
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02064.md
@@ -0,0 +1,11 @@
+---
+title: KM02064: ERROR_ExpansionMustFollowCharacterOrVKey
+---
+
+| | |
+|------------|---------- |
+| Message | An expansion must follow a character or a virtual key |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ExpansionMustFollowCharacterOrVKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02065.md b/developer/docs/help/reference/messages/km02065.md
new file mode 100644
index 00000000000..c8074db3a97
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02065.md
@@ -0,0 +1,11 @@
+---
+title: KM02065: ERROR_VKeyExpansionMustBeFollowedByVKey
+---
+
+| | |
+|------------|---------- |
+| Message | A virtual key expansion must be terminated by a virtual key |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VKeyExpansionMustBeFollowedByVKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km02066.md b/developer/docs/help/reference/messages/km02066.md
new file mode 100644
index 00000000000..874c3a62f5d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02066.md
@@ -0,0 +1,11 @@
+---
+title: KM02066: ERROR_CharacterExpansionMustBeFollowedByCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A character expansion must be terminated by a character key |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CharacterExpansionMustBeFollowedByCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km02067.md b/developer/docs/help/reference/messages/km02067.md
new file mode 100644
index 00000000000..aaacb3ad32d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02067.md
@@ -0,0 +1,11 @@
+---
+title: KM02067: ERROR_VKeyExpansionMustUseConsistentShift
+---
+
+| | |
+|------------|---------- |
+| Message | A virtual key expansion must use the same shift state for both terminators |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VKeyExpansionMustUseConsistentShift` |
+
+
diff --git a/developer/docs/help/reference/messages/km02068.md b/developer/docs/help/reference/messages/km02068.md
new file mode 100644
index 00000000000..88f83971b37
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02068.md
@@ -0,0 +1,11 @@
+---
+title: KM02068: ERROR_ExpansionMustBePositive
+---
+
+| | |
+|------------|---------- |
+| Message | An expansion must have positive difference \(i\.e\. A\-Z, not Z\-A\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ExpansionMustBePositive` |
+
+
diff --git a/developer/docs/help/reference/messages/km02069.md b/developer/docs/help/reference/messages/km02069.md
new file mode 100644
index 00000000000..c7bdf32efd6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02069.md
@@ -0,0 +1,11 @@
+---
+title: KM02069: ERROR_CasedKeysMustContainOnlyVirtualKeys
+---
+
+| | |
+|------------|---------- |
+| Message | The &CasedKeys system store must contain only virtual keys or characters found on a US English keyboard |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CasedKeysMustContainOnlyVirtualKeys` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206a.md b/developer/docs/help/reference/messages/km0206a.md
new file mode 100644
index 00000000000..7a973006f2e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206a.md
@@ -0,0 +1,11 @@
+---
+title: KM0206A: ERROR_CasedKeysMustNotIncludeShiftStates
+---
+
+| | |
+|------------|---------- |
+| Message | The &CasedKeys system store must not include shift states |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CasedKeysMustNotIncludeShiftStates` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206b.md b/developer/docs/help/reference/messages/km0206b.md
new file mode 100644
index 00000000000..d774f7c263f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206b.md
@@ -0,0 +1,11 @@
+---
+title: KM0206B: ERROR_CasedKeysNotSupportedWithMnemonicLayout
+---
+
+| | |
+|------------|---------- |
+| Message | The &CasedKeys system store is not supported with mnemonic layouts |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CasedKeysNotSupportedWithMnemonicLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206c.md b/developer/docs/help/reference/messages/km0206c.md
new file mode 100644
index 00000000000..78bb1f3ab1b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206c.md
@@ -0,0 +1,11 @@
+---
+title: KM0206C: ERROR_CannotUseReadWriteGroupFromReadonlyGroup
+---
+
+| | |
+|------------|---------- |
+| Message | Group used from a readonly group must also be readonly |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CannotUseReadWriteGroupFromReadonlyGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206d.md b/developer/docs/help/reference/messages/km0206d.md
new file mode 100644
index 00000000000..e97fa1f70dd
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206d.md
@@ -0,0 +1,11 @@
+---
+title: KM0206D: ERROR_StatementNotPermittedInReadonlyGroup
+---
+
+| | |
+|------------|---------- |
+| Message | Statement is not permitted in output of readonly group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_StatementNotPermittedInReadonlyGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206e.md b/developer/docs/help/reference/messages/km0206e.md
new file mode 100644
index 00000000000..d72ec373316
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206e.md
@@ -0,0 +1,11 @@
+---
+title: KM0206E: ERROR_OutputInReadonlyGroup
+---
+
+| | |
+|------------|---------- |
+| Message | Output is not permitted in a readonly group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_OutputInReadonlyGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km0206f.md b/developer/docs/help/reference/messages/km0206f.md
new file mode 100644
index 00000000000..304b460b550
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0206f.md
@@ -0,0 +1,11 @@
+---
+title: KM0206F: ERROR_NewContextGroupMustBeReadonly
+---
+
+| | |
+|------------|---------- |
+| Message | Group used in begin newContext must be readonly |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NewContextGroupMustBeReadonly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02070.md b/developer/docs/help/reference/messages/km02070.md
new file mode 100644
index 00000000000..fcf6cc352f4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02070.md
@@ -0,0 +1,11 @@
+---
+title: KM02070: ERROR_PostKeystrokeGroupMustBeReadonly
+---
+
+| | |
+|------------|---------- |
+| Message | Group used in begin postKeystroke must be readonly |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_PostKeystrokeGroupMustBeReadonly` |
+
+
diff --git a/developer/docs/help/reference/messages/km02071.md b/developer/docs/help/reference/messages/km02071.md
new file mode 100644
index 00000000000..fcac6322233
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02071.md
@@ -0,0 +1,11 @@
+---
+title: KM02071: ERROR_DuplicateGroup
+---
+
+| | |
+|------------|---------- |
+| Message | A group with this name has already been defined\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_DuplicateGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km02072.md b/developer/docs/help/reference/messages/km02072.md
new file mode 100644
index 00000000000..8cfa13e5c33
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02072.md
@@ -0,0 +1,11 @@
+---
+title: KM02072: ERROR_DuplicateStore
+---
+
+| | |
+|------------|---------- |
+| Message | A store with this name has already been defined\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_DuplicateStore` |
+
+
diff --git a/developer/docs/help/reference/messages/km02073.md b/developer/docs/help/reference/messages/km02073.md
new file mode 100644
index 00000000000..0a0616d84b6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02073.md
@@ -0,0 +1,11 @@
+---
+title: KM02073: ERROR_RepeatedBegin
+---
+
+| | |
+|------------|---------- |
+| Message | Begin has already been set |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_RepeatedBegin` |
+
+
diff --git a/developer/docs/help/reference/messages/km02074.md b/developer/docs/help/reference/messages/km02074.md
new file mode 100644
index 00000000000..a53219edc44
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02074.md
@@ -0,0 +1,11 @@
+---
+title: KM02074: ERROR_VirtualKeyInContext
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual keys are not permitted in context |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeyInContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km02075.md b/developer/docs/help/reference/messages/km02075.md
new file mode 100644
index 00000000000..8fbf31fbb5d
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02075.md
@@ -0,0 +1,11 @@
+---
+title: KM02075: ERROR_OutsTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Store cannot be inserted with outs\(\) as it makes the extended string too long |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_OutsTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02076.md b/developer/docs/help/reference/messages/km02076.md
new file mode 100644
index 00000000000..73a1129f326
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02076.md
@@ -0,0 +1,11 @@
+---
+title: KM02076: ERROR_ExtendedStringTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Extended string is too long |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_ExtendedStringTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02077.md b/developer/docs/help/reference/messages/km02077.md
new file mode 100644
index 00000000000..994bd6ba193
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02077.md
@@ -0,0 +1,11 @@
+---
+title: KM02077: ERROR_VirtualKeyExpansionTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual key expansion is too large |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_VirtualKeyExpansionTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02078.md b/developer/docs/help/reference/messages/km02078.md
new file mode 100644
index 00000000000..1ad85f0dd97
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02078.md
@@ -0,0 +1,11 @@
+---
+title: KM02078: ERROR_CharacterRangeTooLong
+---
+
+| | |
+|------------|---------- |
+| Message | Character range is too large and cannot be expanded |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_CharacterRangeTooLong` |
+
+
diff --git a/developer/docs/help/reference/messages/km02079.md b/developer/docs/help/reference/messages/km02079.md
new file mode 100644
index 00000000000..ccbb6aa607e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02079.md
@@ -0,0 +1,11 @@
+---
+title: KM02079: ERROR_NonBMPCharactersNotSupportedInKeySection
+---
+
+| | |
+|------------|---------- |
+| Message | Characters with codepoints over U\+FFFF are not supported in the key part of the rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_NonBMPCharactersNotSupportedInKeySection` |
+
+
diff --git a/developer/docs/help/reference/messages/km02080.md b/developer/docs/help/reference/messages/km02080.md
new file mode 100644
index 00000000000..189dc8a0572
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02080.md
@@ -0,0 +1,11 @@
+---
+title: KM02080: WARN_TooManyWarnings
+---
+
+| | |
+|------------|---------- |
+| Message | Too many warnings or errors |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TooManyWarnings` |
+
+
diff --git a/developer/docs/help/reference/messages/km02081.md b/developer/docs/help/reference/messages/km02081.md
new file mode 100644
index 00000000000..28b67aef4c7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02081.md
@@ -0,0 +1,11 @@
+---
+title: KM02081: WARN_OldVersion
+---
+
+| | |
+|------------|---------- |
+| Message | The keyboard file is an old version |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_OldVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km02082.md b/developer/docs/help/reference/messages/km02082.md
new file mode 100644
index 00000000000..5dd37cb366b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02082.md
@@ -0,0 +1,11 @@
+---
+title: KM02082: WARN_BitmapNotUsed
+---
+
+| | |
+|------------|---------- |
+| Message | The 'bitmaps' statement is obsolete and only the first bitmap referred to will be used, you should use 'bitmap'\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_BitmapNotUsed` |
+
+
diff --git a/developer/docs/help/reference/messages/km02083.md b/developer/docs/help/reference/messages/km02083.md
new file mode 100644
index 00000000000..06c5019eee3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02083.md
@@ -0,0 +1,11 @@
+---
+title: KM02083: WARN_CustomLanguagesNotSupported
+---
+
+| | |
+|------------|---------- |
+| Message | Languages over 0x1FF, 0x1F are not supported correctly by Windows\. You should use no LANGUAGE line instead\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_CustomLanguagesNotSupported` |
+
+
diff --git a/developer/docs/help/reference/messages/km02084.md b/developer/docs/help/reference/messages/km02084.md
new file mode 100644
index 00000000000..1f82992d643
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02084.md
@@ -0,0 +1,11 @@
+---
+title: KM02084: WARN_KeyBadLength
+---
+
+| | |
+|------------|---------- |
+| Message | There are too many characters in the keystroke part of the rule\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_KeyBadLength` |
+
+
diff --git a/developer/docs/help/reference/messages/km02085.md b/developer/docs/help/reference/messages/km02085.md
new file mode 100644
index 00000000000..23363e783e5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02085.md
@@ -0,0 +1,11 @@
+---
+title: KM02085: WARN_IndexStoreShort
+---
+
+| | |
+|------------|---------- |
+| Message | The store referenced in index\(\) is shorter than the store referenced in any\(\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_IndexStoreShort` |
+
+
diff --git a/developer/docs/help/reference/messages/km02086.md b/developer/docs/help/reference/messages/km02086.md
new file mode 100644
index 00000000000..db603b5087a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02086.md
@@ -0,0 +1,11 @@
+---
+title: KM02086: WARN_UnicodeInANSIGroup
+---
+
+| | |
+|------------|---------- |
+| Message | A Unicode character was found in an ANSI group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_UnicodeInANSIGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km02087.md b/developer/docs/help/reference/messages/km02087.md
new file mode 100644
index 00000000000..f4cf3fa7366
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02087.md
@@ -0,0 +1,11 @@
+---
+title: KM02087: WARN_ANSIInUnicodeGroup
+---
+
+| | |
+|------------|---------- |
+| Message | An ANSI character was found in a Unicode group |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_ANSIInUnicodeGroup` |
+
+
diff --git a/developer/docs/help/reference/messages/km02088.md b/developer/docs/help/reference/messages/km02088.md
new file mode 100644
index 00000000000..ee038ef1334
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02088.md
@@ -0,0 +1,11 @@
+---
+title: KM02088: WARN_UnicodeSurrogateUsed
+---
+
+| | |
+|------------|---------- |
+| Message | A Unicode surrogate character was found\. You should use Unicode scalar values to represent values > U\+FFFF |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_UnicodeSurrogateUsed` |
+
+
diff --git a/developer/docs/help/reference/messages/km02089.md b/developer/docs/help/reference/messages/km02089.md
new file mode 100644
index 00000000000..73632221bbe
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02089.md
@@ -0,0 +1,11 @@
+---
+title: KM02089: WARN_ReservedCharacter
+---
+
+| | |
+|------------|---------- |
+| Message | A Unicode character was found that should not be used |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_ReservedCharacter` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208a.md b/developer/docs/help/reference/messages/km0208a.md
new file mode 100644
index 00000000000..3b567389fa0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208a.md
@@ -0,0 +1,11 @@
+---
+title: KM0208A: INFO_Info
+---
+
+| | |
+|------------|---------- |
+| Message | Information |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `INFO_Info` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208b.md b/developer/docs/help/reference/messages/km0208b.md
new file mode 100644
index 00000000000..568ae0aa39a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208b.md
@@ -0,0 +1,11 @@
+---
+title: KM0208B: WARN_VirtualKeyWithMnemonicLayout
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual key used instead of virtual character key with a mnemonic layout |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_VirtualKeyWithMnemonicLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208c.md b/developer/docs/help/reference/messages/km0208c.md
new file mode 100644
index 00000000000..52c93a7a4cf
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208c.md
@@ -0,0 +1,11 @@
+---
+title: KM0208C: WARN_VirtualCharKeyWithPositionalLayout
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual character key used with a positional layout instead of mnemonic layout |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_VirtualCharKeyWithPositionalLayout` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208d.md b/developer/docs/help/reference/messages/km0208d.md
new file mode 100644
index 00000000000..18cd9c5e62f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208d.md
@@ -0,0 +1,11 @@
+---
+title: KM0208D: WARN_StoreAlreadyUsedAsOptionOrCall
+---
+
+| | |
+|------------|---------- |
+| Message | Store already used as an option or in a call statement and should not be used as a normal store |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_StoreAlreadyUsedAsOptionOrCall` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208e.md b/developer/docs/help/reference/messages/km0208e.md
new file mode 100644
index 00000000000..61f6d48d9b3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208e.md
@@ -0,0 +1,11 @@
+---
+title: KM0208E: WARN_StoreAlreadyUsedAsStoreOrCall
+---
+
+| | |
+|------------|---------- |
+| Message | Store already used as a normal store or in a call statement and should not be used as an option |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_StoreAlreadyUsedAsStoreOrCall` |
+
+
diff --git a/developer/docs/help/reference/messages/km0208f.md b/developer/docs/help/reference/messages/km0208f.md
new file mode 100644
index 00000000000..ca2313c89c6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0208f.md
@@ -0,0 +1,11 @@
+---
+title: KM0208F: WARN_StoreAlreadyUsedAsStoreOrOption
+---
+
+| | |
+|------------|---------- |
+| Message | Store already used as a normal store or as an option and should not be used in a call statement |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_StoreAlreadyUsedAsStoreOrOption` |
+
+
diff --git a/developer/docs/help/reference/messages/km02090.md b/developer/docs/help/reference/messages/km02090.md
new file mode 100644
index 00000000000..654a26a3dcf
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02090.md
@@ -0,0 +1,11 @@
+---
+title: KM02090: WARN_PunctuationInEthnologueCode
+---
+
+| | |
+|------------|---------- |
+| Message | Punctuation should not be used to separate Ethnologue codes; instead use spaces |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_PunctuationInEthnologueCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km02091.md b/developer/docs/help/reference/messages/km02091.md
new file mode 100644
index 00000000000..45c7f67bca8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02091.md
@@ -0,0 +1,11 @@
+---
+title: KM02091: WARN_TouchLayoutMissingLayer
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on platform "<param>", layer "<param>", references a missing layer "<param>" |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutMissingLayer` |
+
+
diff --git a/developer/docs/help/reference/messages/km02092.md b/developer/docs/help/reference/messages/km02092.md
new file mode 100644
index 00000000000..f81acf778a2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02092.md
@@ -0,0 +1,11 @@
+---
+title: KM02092: WARN_TouchLayoutCustomKeyNotDefined
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on platform "<param>", layer "<param>", is a custom key but has no corresponding rule in the source\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutCustomKeyNotDefined` |
+
+
diff --git a/developer/docs/help/reference/messages/km02093.md b/developer/docs/help/reference/messages/km02093.md
new file mode 100644
index 00000000000..344c56f654e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02093.md
@@ -0,0 +1,11 @@
+---
+title: KM02093: WARN_TouchLayoutMissingRequiredKeys
+---
+
+| | |
+|------------|---------- |
+| Message | Layer "<param>" on platform "<param>" is missing the required key\(s\) '<param>'\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutMissingRequiredKeys` |
+
+
diff --git a/developer/docs/help/reference/messages/km02094.md b/developer/docs/help/reference/messages/km02094.md
new file mode 100644
index 00000000000..c445037d8c4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02094.md
@@ -0,0 +1,11 @@
+---
+title: KM02094: WARN_HelpFileMissing
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be loaded: |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_HelpFileMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km02095.md b/developer/docs/help/reference/messages/km02095.md
new file mode 100644
index 00000000000..f6250d30e80
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02095.md
@@ -0,0 +1,11 @@
+---
+title: KM02095: WARN_EmbedJsFileMissing
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be loaded: |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_EmbedJsFileMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km02098.md b/developer/docs/help/reference/messages/km02098.md
new file mode 100644
index 00000000000..8ccdbd27284
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02098.md
@@ -0,0 +1,11 @@
+---
+title: KM02098: WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb
+---
+
+| | |
+|------------|---------- |
+| Message | Extended shift flags <param> are not supported in KeymanWeb |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb` |
+
+
diff --git a/developer/docs/help/reference/messages/km02099.md b/developer/docs/help/reference/messages/km02099.md
new file mode 100644
index 00000000000..17a93b9f65e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02099.md
@@ -0,0 +1,11 @@
+---
+title: KM02099: WARN_TouchLayoutUnidentifiedKey
+---
+
+| | |
+|------------|---------- |
+| Message | A key on layer "<param>" has no identifier\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutUnidentifiedKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209a.md b/developer/docs/help/reference/messages/km0209a.md
new file mode 100644
index 00000000000..e49c8800bb3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209a.md
@@ -0,0 +1,11 @@
+---
+title: KM0209A: HINT_UnreachableKeyCode
+---
+
+| | |
+|------------|---------- |
+| Message | The rule will never be matched for key <param> because its key code is never fired\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `HINT_UnreachableKeyCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209c.md b/developer/docs/help/reference/messages/km0209c.md
new file mode 100644
index 00000000000..0dd2d6ffb05
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209c.md
@@ -0,0 +1,11 @@
+---
+title: KM0209C: WARN_PlatformNotInTargets
+---
+
+| | |
+|------------|---------- |
+| Message | The specified platform is not a target platform |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_PlatformNotInTargets` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209d.md b/developer/docs/help/reference/messages/km0209d.md
new file mode 100644
index 00000000000..0ff7f4377a2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209d.md
@@ -0,0 +1,11 @@
+---
+title: KM0209D: WARN_HeaderStatementIsDeprecated
+---
+
+| | |
+|------------|---------- |
+| Message | Header statements are deprecated; use instead the equivalent system store |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_HeaderStatementIsDeprecated` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209e.md b/developer/docs/help/reference/messages/km0209e.md
new file mode 100644
index 00000000000..b3d21524d3a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209e.md
@@ -0,0 +1,11 @@
+---
+title: KM0209E: WARN_UseNotLastStatementInRule
+---
+
+| | |
+|------------|---------- |
+| Message | A rule with use\(\) statements in the output should not have other content following the use\(\) statements |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_UseNotLastStatementInRule` |
+
+
diff --git a/developer/docs/help/reference/messages/km0209f.md b/developer/docs/help/reference/messages/km0209f.md
new file mode 100644
index 00000000000..b28554e71c6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0209f.md
@@ -0,0 +1,11 @@
+---
+title: KM0209F: WARN_TouchLayoutFontShouldBeSameForAllPlatforms
+---
+
+| | |
+|------------|---------- |
+| Message | The touch layout font should be the same for all platforms\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutFontShouldBeSameForAllPlatforms` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a2.md b/developer/docs/help/reference/messages/km020a2.md
new file mode 100644
index 00000000000..e4e0530b22f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a2.md
@@ -0,0 +1,11 @@
+---
+title: KM020A2: WARN_KVKFileIsInSourceFormat
+---
+
+| | |
+|------------|---------- |
+| Message | \.kvk file should be binary but is an XML file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_KVKFileIsInSourceFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a3.md b/developer/docs/help/reference/messages/km020a3.md
new file mode 100644
index 00000000000..36e2820f4ae
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a3.md
@@ -0,0 +1,11 @@
+---
+title: KM020A3: WARN_DontMixChiralAndNonChiralModifiers
+---
+
+| | |
+|------------|---------- |
+| Message | This keyboard contains Ctrl,Alt and LCtrl,LAlt,RCtrl,RAlt sets of modifiers\. Use only one or the other set for web target\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_DontMixChiralAndNonChiralModifiers` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a4.md b/developer/docs/help/reference/messages/km020a4.md
new file mode 100644
index 00000000000..e9f0cddc180
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a4.md
@@ -0,0 +1,11 @@
+---
+title: KM020A4: WARN_MixingLeftAndRightModifiers
+---
+
+| | |
+|------------|---------- |
+| Message | Left and right modifiers should not both be used in the same rule |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_MixingLeftAndRightModifiers` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a5.md b/developer/docs/help/reference/messages/km020a5.md
new file mode 100644
index 00000000000..3cafe009836
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a5.md
@@ -0,0 +1,11 @@
+---
+title: KM020A5: WARN_LanguageHeadersDeprecatedInKeyman10
+---
+
+| | |
+|------------|---------- |
+| Message | This language header has been deprecated in Keyman 10\. Instead, add language metadata in the package file |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_LanguageHeadersDeprecatedInKeyman10` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a6.md b/developer/docs/help/reference/messages/km020a6.md
new file mode 100644
index 00000000000..30962d42fe8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a6.md
@@ -0,0 +1,11 @@
+---
+title: KM020A6: HINT_NonUnicodeFile
+---
+
+| | |
+|------------|---------- |
+| Message | Keyman Developer has detected that the file has ANSI encoding\. Consider converting this file to UTF\-8 |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `HINT_NonUnicodeFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a8.md b/developer/docs/help/reference/messages/km020a8.md
new file mode 100644
index 00000000000..1a92a9c17fc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a8.md
@@ -0,0 +1,11 @@
+---
+title: KM020A8: WARN_HotkeyHasInvalidModifier
+---
+
+| | |
+|------------|---------- |
+| Message | Hotkey has modifiers that are not supported\. Use only SHIFT, CTRL and ALT |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_HotkeyHasInvalidModifier` |
+
+
diff --git a/developer/docs/help/reference/messages/km020a9.md b/developer/docs/help/reference/messages/km020a9.md
new file mode 100644
index 00000000000..906171ba8a4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020a9.md
@@ -0,0 +1,11 @@
+---
+title: KM020A9: WARN_TouchLayoutSpecialLabelOnNormalKey
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on platform "<param>", layer "<param>" does not have the key type "Special" or "Special \(active\)" but has the label "<param>"\. This feature is only supported in Keyman 14 or later |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_TouchLayoutSpecialLabelOnNormalKey` |
+
+
diff --git a/developer/docs/help/reference/messages/km020aa.md b/developer/docs/help/reference/messages/km020aa.md
new file mode 100644
index 00000000000..19505b3984a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020aa.md
@@ -0,0 +1,11 @@
+---
+title: KM020AA: WARN_OptionStoreNameInvalid
+---
+
+| | |
+|------------|---------- |
+| Message | The option store <param> should be named with characters in the range A\-Z, a\-z, 0\-9 and \_ only\. |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_OptionStoreNameInvalid` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ab.md b/developer/docs/help/reference/messages/km020ab.md
new file mode 100644
index 00000000000..39a050f9995
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ab.md
@@ -0,0 +1,11 @@
+---
+title: KM020AB: WARN_NulNotFirstStatementInContext
+---
+
+| | |
+|------------|---------- |
+| Message | nul must be the first statement in the context |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_NulNotFirstStatementInContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ac.md b/developer/docs/help/reference/messages/km020ac.md
new file mode 100644
index 00000000000..87698b91387
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ac.md
@@ -0,0 +1,11 @@
+---
+title: KM020AC: WARN_IfShouldBeAtStartOfContext
+---
+
+| | |
+|------------|---------- |
+| Message | if, platform and baselayout should be at start of context \(after nul, if present\) |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_IfShouldBeAtStartOfContext` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ad.md b/developer/docs/help/reference/messages/km020ad.md
new file mode 100644
index 00000000000..0a5258da8f7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ad.md
@@ -0,0 +1,11 @@
+---
+title: KM020AD: WARN_KeyShouldIncludeNCaps
+---
+
+| | |
+|------------|---------- |
+| Message | Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_KeyShouldIncludeNCaps` |
+
+
diff --git a/developer/docs/help/reference/messages/km020ae.md b/developer/docs/help/reference/messages/km020ae.md
new file mode 100644
index 00000000000..15157c274b2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020ae.md
@@ -0,0 +1,11 @@
+---
+title: KM020AE: HINT_UnreachableRule
+---
+
+| | |
+|------------|---------- |
+| Message | This rule will never be matched as another rule takes precedence |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `HINT_UnreachableRule` |
+
+
diff --git a/developer/docs/help/reference/messages/km020af.md b/developer/docs/help/reference/messages/km020af.md
new file mode 100644
index 00000000000..b28458ca7d2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020af.md
@@ -0,0 +1,11 @@
+---
+title: KM020AF: WARN_VirtualKeyInOutput
+---
+
+| | |
+|------------|---------- |
+| Message | Virtual keys are not supported in output |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_VirtualKeyInOutput` |
+
+
diff --git a/developer/docs/help/reference/messages/km020c0.md b/developer/docs/help/reference/messages/km020c0.md
new file mode 100644
index 00000000000..73e90bd3dd2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020c0.md
@@ -0,0 +1,11 @@
+---
+title: KM020C0: FATAL_BufferOverflow
+---
+
+| | |
+|------------|---------- |
+| Message | The compiler memory buffer overflowed |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_BufferOverflow` |
+
+
diff --git a/developer/docs/help/reference/messages/km020c1.md b/developer/docs/help/reference/messages/km020c1.md
new file mode 100644
index 00000000000..7e82a1531f6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km020c1.md
@@ -0,0 +1,11 @@
+---
+title: KM020C1: FATAL_Break
+---
+
+| | |
+|------------|---------- |
+| Message | Compiler interrupted by user |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_Break` |
+
+
diff --git a/developer/docs/help/reference/messages/km02900.md b/developer/docs/help/reference/messages/km02900.md
new file mode 100644
index 00000000000..9b4cefdfa9a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02900.md
@@ -0,0 +1,13 @@
+---
+title: KM02900: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+Raised when KmnCompiler or one of its components has an internal
+error. If you experience this error, it should be reported to the Keyman
+team for resolution via https://github.com/keymanapp/keyman/issues/new.
diff --git a/developer/docs/help/reference/messages/km02901.md b/developer/docs/help/reference/messages/km02901.md
new file mode 100644
index 00000000000..1e9098c24e8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02901.md
@@ -0,0 +1,13 @@
+---
+title: KM02901: FATAL_MissingWasmModule
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_MissingWasmModule` |
+
+Raised when the kmcmplib component could not be instantiated. This may indicate
+a configuration or dependency issue. Make sure you are running a Javascript
+engine that supports WASM, and that use of WASM is enabled.
diff --git a/developer/docs/help/reference/messages/km02903.md b/developer/docs/help/reference/messages/km02903.md
new file mode 100644
index 00000000000..df0c0087e01
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02903.md
@@ -0,0 +1,13 @@
+---
+title: KM02903: FATAL_CallbacksNotSet
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_CallbacksNotSet` |
+
+Raised when KmnCompiler or one of its components experiences an internal
+error. If you experience this error, it should be reported to the Keyman
+team for resolution via https://github.com/keymanapp/keyman/issues/new.
diff --git a/developer/docs/help/reference/messages/km02904.md b/developer/docs/help/reference/messages/km02904.md
new file mode 100644
index 00000000000..9661ae59203
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02904.md
@@ -0,0 +1,13 @@
+---
+title: KM02904: FATAL_UnicodeSetOutOfRange
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `FATAL_UnicodeSetOutOfRange` |
+
+Raised when caller to UnicodeSet functions provides an invalid buffer. If
+you experience this error, it should be reported to the Keyman team for
+resolution via https://github.com/keymanapp/keyman/issues/new.
diff --git a/developer/docs/help/reference/messages/km02905.md b/developer/docs/help/reference/messages/km02905.md
new file mode 100644
index 00000000000..5b6982f18bb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02905.md
@@ -0,0 +1,16 @@
+---
+title: KM02905: ERROR_UnicodeSetHasStrings
+---
+
+| | |
+|------------|---------- |
+| Message | uset contains strings, not allowed |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnicodeSetHasStrings` |
+
+The provided uset uses multi-character strings, (`{}` notation, e.g.
+`[żġħ{ie}{għ}]`. ). Although full UnicodeSets support strings, LDML
+keyboards do not support multi-character strings in usets. To resolve this,
+reformat the uset to avoid the use of multi-character strings.
+
+More on uset: https://www.unicode.org/reports/tr35/tr35-keyboards.html#element-uset
diff --git a/developer/docs/help/reference/messages/km02906.md b/developer/docs/help/reference/messages/km02906.md
new file mode 100644
index 00000000000..99dd1569569
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02906.md
@@ -0,0 +1,16 @@
+---
+title: KM02906: ERROR_UnicodeSetHasProperties
+---
+
+| | |
+|------------|---------- |
+| Message | uset contains properties, not allowed |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnicodeSetHasProperties` |
+
+The provided uset uses property notation (`\p{…}` or `[:…:]`). LDML
+keyboards do not support Unicode properties in usets, because that would
+make implementations dependent on a particular version of Unicode. To
+resolve this, reformat the uset to avoid the use of properties.
+
+More on uset: https://www.unicode.org/reports/tr35/tr35-keyboards.html#element-uset
diff --git a/developer/docs/help/reference/messages/km02907.md b/developer/docs/help/reference/messages/km02907.md
new file mode 100644
index 00000000000..deae301f509
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02907.md
@@ -0,0 +1,14 @@
+---
+title: KM02907: ERROR_UnicodeSetSyntaxError
+---
+
+| | |
+|------------|---------- |
+| Message | uset had a Syntax Error while parsing |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_UnicodeSetSyntaxError` |
+
+The provided uset has a syntax error and could not be parsed. Verify the
+format of the uset against the specification.
+
+More on uset: https://www.unicode.org/reports/tr35/tr35-keyboards.html#element-uset
diff --git a/developer/docs/help/reference/messages/km02908.md b/developer/docs/help/reference/messages/km02908.md
new file mode 100644
index 00000000000..d4c4ac5dc17
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02908.md
@@ -0,0 +1,15 @@
+---
+title: KM02908: ERROR_InvalidKvksFile
+---
+
+| | |
+|------------|---------- |
+| Message | Error encountered parsing <param>: unknown error |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidKvksFile` |
+
+The .kvks file could not be parsed because it was not a valid XML file.
+There may be additional information in the error message to help you
+resolve the error.
+
+More on .kvks file format: https://help.keyman.com/developer/current-version/reference/file-types/kvks
diff --git a/developer/docs/help/reference/messages/km02909.md b/developer/docs/help/reference/messages/km02909.md
new file mode 100644
index 00000000000..96d690960d9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km02909.md
@@ -0,0 +1,14 @@
+---
+title: KM02909: WARN_InvalidVkeyInKvksFile
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid virtual key <param> found in <param> |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `WARN_InvalidVkeyInKvksFile` |
+
+The .kvks file contained a virtual key that was not supported by
+Keyman. Remove this virtual key from the .kvks file.
+
+Supported virtual keys: https://help.keyman.com/developer/language/guide/virtual-keys#common-virtual-key-codes
diff --git a/developer/docs/help/reference/messages/km0290a.md b/developer/docs/help/reference/messages/km0290a.md
new file mode 100644
index 00000000000..fb5db9ed09e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0290a.md
@@ -0,0 +1,15 @@
+---
+title: KM0290A: ERROR_InvalidDisplayMapFile
+---
+
+| | |
+|------------|---------- |
+| Message | Error encountered parsing display map <param>: unknown error |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidDisplayMapFile` |
+
+The displayMap file could not be parsed because it was not a valid JSON
+file. There may be additional information in the error message to help you
+resolve the error.
+
+More on displayMap: https://help.keyman.com/developer/language/reference/displaymap
diff --git a/developer/docs/help/reference/messages/km0290b.md b/developer/docs/help/reference/messages/km0290b.md
new file mode 100644
index 00000000000..4fdb0f7d921
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0290b.md
@@ -0,0 +1,15 @@
+---
+title: KM0290B: ERROR_InvalidKvkFile
+---
+
+| | |
+|------------|---------- |
+| Message | Error encountered loading <param>: unknown error |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_InvalidKvkFile` |
+
+The .kvk file could not be loaded because it was not a valid format. There
+may be additional information in the error message to help you resolve the
+error.
+
+More on .kvk files: https://help.keyman.com/developer/current-version/reference/file-types/kvk
diff --git a/developer/docs/help/reference/messages/km0290c.md b/developer/docs/help/reference/messages/km0290c.md
new file mode 100644
index 00000000000..97d584ca979
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0290c.md
@@ -0,0 +1,12 @@
+---
+title: KM0290C: ERROR_FileNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> was not found |
+| Module | [kmc-kmn.KmnCompilerMessages](kmc-kmn.kmncompilermessages) |
+| Identifier | `ERROR_FileNotFound` |
+
+The file was not found on the disk. Verify that you have the correct path
+to the file.
diff --git a/developer/docs/help/reference/messages/km03001.md b/developer/docs/help/reference/messages/km03001.md
new file mode 100644
index 00000000000..b988ceb558c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03001.md
@@ -0,0 +1,11 @@
+---
+title: KM03001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km03002.md b/developer/docs/help/reference/messages/km03002.md
new file mode 100644
index 00000000000..0066c30bb1a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03002.md
@@ -0,0 +1,11 @@
+---
+title: KM03002: HINT_MixedNormalizationForms
+---
+
+| | |
+|------------|---------- |
+| Message | “<param>” is not in Unicode NFC\. Automatically converting to NFC\. |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `HINT_MixedNormalizationForms` |
+
+
diff --git a/developer/docs/help/reference/messages/km03003.md b/developer/docs/help/reference/messages/km03003.md
new file mode 100644
index 00000000000..d737092f030
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03003.md
@@ -0,0 +1,11 @@
+---
+title: KM03003: HINT_DuplicateWordInSameFile
+---
+
+| | |
+|------------|---------- |
+| Message | duplicate word “<param>” found in same file; summing counts |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `HINT_DuplicateWordInSameFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km03004.md b/developer/docs/help/reference/messages/km03004.md
new file mode 100644
index 00000000000..5b468c561b5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03004.md
@@ -0,0 +1,11 @@
+---
+title: KM03004: ERROR_UnimplementedModelFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Unimplemented model format: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnimplementedModelFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km03005.md b/developer/docs/help/reference/messages/km03005.md
new file mode 100644
index 00000000000..fc972eb9e93
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03005.md
@@ -0,0 +1,11 @@
+---
+title: KM03005: ERROR_UnknownModelFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Unimplemented model format: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnknownModelFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km03006.md b/developer/docs/help/reference/messages/km03006.md
new file mode 100644
index 00000000000..24d6bc43513
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03006.md
@@ -0,0 +1,11 @@
+---
+title: KM03006: ERROR_NoDefaultExport
+---
+
+| | |
+|------------|---------- |
+| Message | Model source does have a default export\. Did you remember to write \`export default source;\`? |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_NoDefaultExport` |
+
+
diff --git a/developer/docs/help/reference/messages/km03007.md b/developer/docs/help/reference/messages/km03007.md
new file mode 100644
index 00000000000..988d8f60658
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03007.md
@@ -0,0 +1,11 @@
+---
+title: KM03007: ERROR_SearchTermToKeyMustBeExplicitlySpecified
+---
+
+| | |
+|------------|---------- |
+| Message | searchTermToKey must be explicitly specified |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_SearchTermToKeyMustBeExplicitlySpecified` |
+
+
diff --git a/developer/docs/help/reference/messages/km03008.md b/developer/docs/help/reference/messages/km03008.md
new file mode 100644
index 00000000000..761fede21a3
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03008.md
@@ -0,0 +1,11 @@
+---
+title: KM03008: ERROR_UTF16BEUnsupported
+---
+
+| | |
+|------------|---------- |
+| Message | UTF\-16BE is unsupported |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UTF16BEUnsupported` |
+
+
diff --git a/developer/docs/help/reference/messages/km03009.md b/developer/docs/help/reference/messages/km03009.md
new file mode 100644
index 00000000000..293f02e7920
--- /dev/null
+++ b/developer/docs/help/reference/messages/km03009.md
@@ -0,0 +1,11 @@
+---
+title: KM03009: ERROR_UnknownWordBreaker
+---
+
+| | |
+|------------|---------- |
+| Message | Unknown word breaker: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnknownWordBreaker` |
+
+
diff --git a/developer/docs/help/reference/messages/km0300a.md b/developer/docs/help/reference/messages/km0300a.md
new file mode 100644
index 00000000000..db2e3070c0b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0300a.md
@@ -0,0 +1,11 @@
+---
+title: KM0300A: ERROR_UnsupportedScriptOverride
+---
+
+| | |
+|------------|---------- |
+| Message | Unsupported script override: <param> |
+| Module | [kmc-model.ModelCompilerMessages](kmc-model.modelcompilermessages) |
+| Identifier | `ERROR_UnsupportedScriptOverride` |
+
+
diff --git a/developer/docs/help/reference/messages/km04001.md b/developer/docs/help/reference/messages/km04001.md
new file mode 100644
index 00000000000..c0d482c2c4e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04001.md
@@ -0,0 +1,11 @@
+---
+title: KM04001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km04002.md b/developer/docs/help/reference/messages/km04002.md
new file mode 100644
index 00000000000..d659496d1ac
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04002.md
@@ -0,0 +1,11 @@
+---
+title: KM04002: WARN_AbsolutePath
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> has an absolute path, which is not portable\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_AbsolutePath` |
+
+
diff --git a/developer/docs/help/reference/messages/km04003.md b/developer/docs/help/reference/messages/km04003.md
new file mode 100644
index 00000000000..209a2d37a31
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04003.md
@@ -0,0 +1,11 @@
+---
+title: KM04003: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km04004.md b/developer/docs/help/reference/messages/km04004.md
new file mode 100644
index 00000000000..d5b104ddfce
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04004.md
@@ -0,0 +1,11 @@
+---
+title: KM04004: ERROR_FileCouldNotBeRead
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be read: unknown error\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FileCouldNotBeRead` |
+
+
diff --git a/developer/docs/help/reference/messages/km04005.md b/developer/docs/help/reference/messages/km04005.md
new file mode 100644
index 00000000000..494c040d142
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04005.md
@@ -0,0 +1,11 @@
+---
+title: KM04005: WARN_FileIsNotABinaryKvkFile
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not appear to be a valid binary \.kvk file; this may be an old package that includes an xml\-format \.kvk file\. You must update the package to include the compiled \.kvk file in the package\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_FileIsNotABinaryKvkFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km04006.md b/developer/docs/help/reference/messages/km04006.md
new file mode 100644
index 00000000000..696815d28fc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04006.md
@@ -0,0 +1,11 @@
+---
+title: KM04006: ERROR_FollowKeyboardVersionNotAllowedForModelPackages
+---
+
+| | |
+|------------|---------- |
+| Message | FollowKeyboardVersion is not allowed in model packages |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FollowKeyboardVersionNotAllowedForModelPackages` |
+
+
diff --git a/developer/docs/help/reference/messages/km04007.md b/developer/docs/help/reference/messages/km04007.md
new file mode 100644
index 00000000000..a9709d876c2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04007.md
@@ -0,0 +1,11 @@
+---
+title: KM04007: ERROR_FollowKeyboardVersionButNoKeyboards
+---
+
+| | |
+|------------|---------- |
+| Message | FollowKeyboardVersion is set, but the package contains no keyboards |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FollowKeyboardVersionButNoKeyboards` |
+
+
diff --git a/developer/docs/help/reference/messages/km04008.md b/developer/docs/help/reference/messages/km04008.md
new file mode 100644
index 00000000000..301b6ea85f7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04008.md
@@ -0,0 +1,11 @@
+---
+title: KM04008: ERROR_KeyboardContentFileNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard <param> was listed in <Keyboards> but a corresponding \.kmx file was not found in <Files> |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_KeyboardContentFileNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km04009.md b/developer/docs/help/reference/messages/km04009.md
new file mode 100644
index 00000000000..5e2ebee13e5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04009.md
@@ -0,0 +1,11 @@
+---
+title: KM04009: ERROR_KeyboardFileNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard file <param> is not a valid \.kmx file: unknown error |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_KeyboardFileNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400a.md b/developer/docs/help/reference/messages/km0400a.md
new file mode 100644
index 00000000000..f32c195b5d9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400a.md
@@ -0,0 +1,11 @@
+---
+title: KM0400A: INFO_KeyboardFileHasNoKeyboardVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard file <param> has no &KeyboardVersion store, using default '0\.0' |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `INFO_KeyboardFileHasNoKeyboardVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400b.md b/developer/docs/help/reference/messages/km0400b.md
new file mode 100644
index 00000000000..04c3af283b4
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400b.md
@@ -0,0 +1,11 @@
+---
+title: KM0400B: ERROR_PackageCannotContainBothModelsAndKeyboards
+---
+
+| | |
+|------------|---------- |
+| Message | The package contains both lexical models and keyboards, which is not permitted\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageCannotContainBothModelsAndKeyboards` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400c.md b/developer/docs/help/reference/messages/km0400c.md
new file mode 100644
index 00000000000..e8534620b82
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400c.md
@@ -0,0 +1,11 @@
+---
+title: KM0400C: HINT_PackageShouldNotRepeatLanguages
+---
+
+| | |
+|------------|---------- |
+| Message | Two language tags in <param> <param>, '<param>' and '<param>', reduce to the same minimal tag '<param>'\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_PackageShouldNotRepeatLanguages` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400d.md b/developer/docs/help/reference/messages/km0400d.md
new file mode 100644
index 00000000000..b472719e0a9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400d.md
@@ -0,0 +1,11 @@
+---
+title: KM0400D: WARN_PackageNameDoesNotFollowLexicalModelConventions
+---
+
+| | |
+|------------|---------- |
+| Message | The package file <param> does not follow the recommended model filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), not start with a digit, and should have the structure <author>\.<bcp47>\.<uniq>\.model\.kps\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_PackageNameDoesNotFollowLexicalModelConventions` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400e.md b/developer/docs/help/reference/messages/km0400e.md
new file mode 100644
index 00000000000..6315015e312
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400e.md
@@ -0,0 +1,11 @@
+---
+title: KM0400E: WARN_PackageNameDoesNotFollowKeyboardConventions
+---
+
+| | |
+|------------|---------- |
+| Message | The package file <param> does not follow the recommended keyboard filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), and not start with a digit\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_PackageNameDoesNotFollowKeyboardConventions` |
+
+
diff --git a/developer/docs/help/reference/messages/km0400f.md b/developer/docs/help/reference/messages/km0400f.md
new file mode 100644
index 00000000000..654373479b1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0400f.md
@@ -0,0 +1,11 @@
+---
+title: KM0400F: WARN_FileInPackageDoesNotFollowFilenameConventions
+---
+
+| | |
+|------------|---------- |
+| Message | The file <param> does not follow the recommended filename conventions\. The extension should be all lower case, and the filename should include only alphanumeric characters, \-, \_, \+ and \. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_FileInPackageDoesNotFollowFilenameConventions` |
+
+
diff --git a/developer/docs/help/reference/messages/km04010.md b/developer/docs/help/reference/messages/km04010.md
new file mode 100644
index 00000000000..dae0bc2ebc7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04010.md
@@ -0,0 +1,11 @@
+---
+title: KM04010: ERROR_PackageNameCannotBeBlank
+---
+
+| | |
+|------------|---------- |
+| Message | Package name cannot be an empty string\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageNameCannotBeBlank` |
+
+
diff --git a/developer/docs/help/reference/messages/km04011.md b/developer/docs/help/reference/messages/km04011.md
new file mode 100644
index 00000000000..c1244105ce9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04011.md
@@ -0,0 +1,11 @@
+---
+title: KM04011: ERROR_KeyboardFileNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard file <param> was not found\. Has it been compiled? |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_KeyboardFileNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km04012.md b/developer/docs/help/reference/messages/km04012.md
new file mode 100644
index 00000000000..42d3197b4aa
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04012.md
@@ -0,0 +1,11 @@
+---
+title: KM04012: WARN_KeyboardVersionsDoNotMatch
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard <param> version <param> does not match keyboard <param> version <param>\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_KeyboardVersionsDoNotMatch` |
+
+
diff --git a/developer/docs/help/reference/messages/km04014.md b/developer/docs/help/reference/messages/km04014.md
new file mode 100644
index 00000000000..df8a014a85b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04014.md
@@ -0,0 +1,11 @@
+---
+title: KM04014: ERROR_LanguageTagIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | Language tag '<param>' in <param> <param> is invalid\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_LanguageTagIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km04015.md b/developer/docs/help/reference/messages/km04015.md
new file mode 100644
index 00000000000..38d6a73b3fb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04015.md
@@ -0,0 +1,11 @@
+---
+title: KM04015: HINT_LanguageTagIsNotMinimal
+---
+
+| | |
+|------------|---------- |
+| Message | Language tag '<param>' in <param> <param> is not minimal, and should be '<param>'\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_LanguageTagIsNotMinimal` |
+
+
diff --git a/developer/docs/help/reference/messages/km04016.md b/developer/docs/help/reference/messages/km04016.md
new file mode 100644
index 00000000000..5b4a6004097
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04016.md
@@ -0,0 +1,11 @@
+---
+title: KM04016: ERROR_ModelMustHaveAtLeastOneLanguage
+---
+
+| | |
+|------------|---------- |
+| Message | The lexical model <param> must have at least one language specified\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_ModelMustHaveAtLeastOneLanguage` |
+
+
diff --git a/developer/docs/help/reference/messages/km04017.md b/developer/docs/help/reference/messages/km04017.md
new file mode 100644
index 00000000000..8545103e8e0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04017.md
@@ -0,0 +1,11 @@
+---
+title: KM04017: WARN_RedistFileShouldNotBeInPackage
+---
+
+| | |
+|------------|---------- |
+| Message | The Keyman system file '<param>' should not be compiled into the package\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_RedistFileShouldNotBeInPackage` |
+
+
diff --git a/developer/docs/help/reference/messages/km04018.md b/developer/docs/help/reference/messages/km04018.md
new file mode 100644
index 00000000000..7b80134b406
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04018.md
@@ -0,0 +1,11 @@
+---
+title: KM04018: WARN_DocFileDangerous
+---
+
+| | |
+|------------|---------- |
+| Message | Microsoft Word \.doc or \.docx files \('<param>'\) are not portable\. You should instead use HTML or PDF format\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_DocFileDangerous` |
+
+
diff --git a/developer/docs/help/reference/messages/km04019.md b/developer/docs/help/reference/messages/km04019.md
new file mode 100644
index 00000000000..ba36824e99b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04019.md
@@ -0,0 +1,11 @@
+---
+title: KM04019: ERROR_PackageMustContainAModelOrAKeyboard
+---
+
+| | |
+|------------|---------- |
+| Message | Package must contain a lexical model or a keyboard\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageMustContainAModelOrAKeyboard` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401a.md b/developer/docs/help/reference/messages/km0401a.md
new file mode 100644
index 00000000000..c26768212a9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401a.md
@@ -0,0 +1,11 @@
+---
+title: KM0401A: WARN_JsKeyboardFileIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | Keyboard <param> targets touch devices but corresponding <param>\.js file is not in the package\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_JsKeyboardFileIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401b.md b/developer/docs/help/reference/messages/km0401b.md
new file mode 100644
index 00000000000..2cb156f927c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401b.md
@@ -0,0 +1,11 @@
+---
+title: KM0401B: WARN_KeyboardShouldHaveAtLeastOneLanguage
+---
+
+| | |
+|------------|---------- |
+| Message | The keyboard <param> should have at least one language specified\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `WARN_KeyboardShouldHaveAtLeastOneLanguage` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401c.md b/developer/docs/help/reference/messages/km0401c.md
new file mode 100644
index 00000000000..72c4c84404b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401c.md
@@ -0,0 +1,11 @@
+---
+title: KM0401C: HINT_JsKeyboardFileHasNoTouchTargets
+---
+
+| | |
+|------------|---------- |
+| Message | The keyboard <param> has been included for touch platforms, but does not include a touch layout\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_JsKeyboardFileHasNoTouchTargets` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401d.md b/developer/docs/help/reference/messages/km0401d.md
new file mode 100644
index 00000000000..d6a773dbed8
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401d.md
@@ -0,0 +1,11 @@
+---
+title: KM0401D: HINT_PackageContainsSourceFile
+---
+
+| | |
+|------------|---------- |
+| Message | The source file <param> should not be included in the package; instead include the compiled result\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `HINT_PackageContainsSourceFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401e.md b/developer/docs/help/reference/messages/km0401e.md
new file mode 100644
index 00000000000..c7221243e5a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401e.md
@@ -0,0 +1,11 @@
+---
+title: KM0401E: ERROR_InvalidPackageFile
+---
+
+| | |
+|------------|---------- |
+| Message | Package source file is invalid: unknown error |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_InvalidPackageFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0401f.md b/developer/docs/help/reference/messages/km0401f.md
new file mode 100644
index 00000000000..71ffb82d91a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0401f.md
@@ -0,0 +1,11 @@
+---
+title: KM0401F: ERROR_FileRecordIsMissingName
+---
+
+| | |
+|------------|---------- |
+| Message | File record in the package with description 'undefined' is missing a filename\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_FileRecordIsMissingName` |
+
+
diff --git a/developer/docs/help/reference/messages/km04020.md b/developer/docs/help/reference/messages/km04020.md
new file mode 100644
index 00000000000..94de1c5805b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04020.md
@@ -0,0 +1,11 @@
+---
+title: KM04020: ERROR_InvalidAuthorEmail
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid author email: <param> |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_InvalidAuthorEmail` |
+
+
diff --git a/developer/docs/help/reference/messages/km04021.md b/developer/docs/help/reference/messages/km04021.md
new file mode 100644
index 00000000000..37b60e128e2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km04021.md
@@ -0,0 +1,11 @@
+---
+title: KM04021: ERROR_PackageFileHasEmptyVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Package version is not following keyboard version, but the package version field is blank\. |
+| Module | [kmc-package.CompilerMessages](kmc-package.compilermessages) |
+| Identifier | `ERROR_PackageFileHasEmptyVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km05001.md b/developer/docs/help/reference/messages/km05001.md
new file mode 100644
index 00000000000..8d6217d8266
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05001.md
@@ -0,0 +1,11 @@
+---
+title: KM05001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km05002.md b/developer/docs/help/reference/messages/km05002.md
new file mode 100644
index 00000000000..72b5f230ffe
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05002.md
@@ -0,0 +1,11 @@
+---
+title: KM05002: INFO_BuildingFile
+---
+
+| | |
+|------------|---------- |
+| Message | Building <param> |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_BuildingFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05003.md b/developer/docs/help/reference/messages/km05003.md
new file mode 100644
index 00000000000..9c592343ee9
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05003.md
@@ -0,0 +1,11 @@
+---
+title: KM05003: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km05004.md b/developer/docs/help/reference/messages/km05004.md
new file mode 100644
index 00000000000..613b1f7b9a0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05004.md
@@ -0,0 +1,11 @@
+---
+title: KM05004: ERROR_FileTypeNotRecognized
+---
+
+| | |
+|------------|---------- |
+| Message | Unrecognised input file <param>, expecting <param>, or project folder |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_FileTypeNotRecognized` |
+
+
diff --git a/developer/docs/help/reference/messages/km05005.md b/developer/docs/help/reference/messages/km05005.md
new file mode 100644
index 00000000000..b8fd53b3c8e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05005.md
@@ -0,0 +1,11 @@
+---
+title: KM05005: ERROR_OutFileNotValidForProjects
+---
+
+| | |
+|------------|---------- |
+| Message | \-\-out\-file should not be specified for project builds |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutFileNotValidForProjects` |
+
+
diff --git a/developer/docs/help/reference/messages/km05006.md b/developer/docs/help/reference/messages/km05006.md
new file mode 100644
index 00000000000..65ba2240482
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05006.md
@@ -0,0 +1,11 @@
+---
+title: KM05006: INFO_FileBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | <param> built successfully\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_FileBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km05007.md b/developer/docs/help/reference/messages/km05007.md
new file mode 100644
index 00000000000..64f57f6e328
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05007.md
@@ -0,0 +1,11 @@
+---
+title: KM05007: INFO_FileNotBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | <param> failed to build\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_FileNotBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km05008.md b/developer/docs/help/reference/messages/km05008.md
new file mode 100644
index 00000000000..d0838674b32
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05008.md
@@ -0,0 +1,11 @@
+---
+title: KM05008: ERROR_InvalidProjectFile
+---
+
+| | |
+|------------|---------- |
+| Message | Project file is not valid: <param> |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_InvalidProjectFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05009.md b/developer/docs/help/reference/messages/km05009.md
new file mode 100644
index 00000000000..1874676a98a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05009.md
@@ -0,0 +1,11 @@
+---
+title: KM05009: HINT_FilenameHasDifferingCase
+---
+
+| | |
+|------------|---------- |
+| Message | File on disk '<param>' does not match case of '<param>' in source file; this is an error on platforms with case\-sensitive filesystems\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `HINT_FilenameHasDifferingCase` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500a.md b/developer/docs/help/reference/messages/km0500a.md
new file mode 100644
index 00000000000..c9dfa42d6c5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500a.md
@@ -0,0 +1,11 @@
+---
+title: KM0500A: ERROR_UnknownFileFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Unknown file format <param>; only Markdown \(\.md\), JSON \(\.json\), and Text \(\.txt\) are supported\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_UnknownFileFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500b.md b/developer/docs/help/reference/messages/km0500b.md
new file mode 100644
index 00000000000..632bccaf1b5
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500b.md
@@ -0,0 +1,11 @@
+---
+title: KM0500B: INFO_ProjectBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | Project <param> built successfully\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_ProjectBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500c.md b/developer/docs/help/reference/messages/km0500c.md
new file mode 100644
index 00000000000..9ed37722733
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500c.md
@@ -0,0 +1,11 @@
+---
+title: KM0500C: INFO_ProjectNotBuiltSuccessfully
+---
+
+| | |
+|------------|---------- |
+| Message | Project <param> failed to build\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_ProjectNotBuiltSuccessfully` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500d.md b/developer/docs/help/reference/messages/km0500d.md
new file mode 100644
index 00000000000..b3b7e0ae9e6
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500d.md
@@ -0,0 +1,11 @@
+---
+title: KM0500D: INFO_TooManyMessages
+---
+
+| | |
+|------------|---------- |
+| Message | More than <param> warnings or errors received; suppressing further messages\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_TooManyMessages` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500e.md b/developer/docs/help/reference/messages/km0500e.md
new file mode 100644
index 00000000000..1e8eecfe5c0
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500e.md
@@ -0,0 +1,11 @@
+---
+title: KM0500E: ERROR_FileTypeNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | A file of type <param> was not found in the project\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_FileTypeNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0500f.md b/developer/docs/help/reference/messages/km0500f.md
new file mode 100644
index 00000000000..b3ae8ebbef1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0500f.md
@@ -0,0 +1,11 @@
+---
+title: KM0500F: ERROR_NotAProjectFile
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> must have a \.kpj extension to be treated as a project\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_NotAProjectFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05010.md b/developer/docs/help/reference/messages/km05010.md
new file mode 100644
index 00000000000..18c0968ac6a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05010.md
@@ -0,0 +1,11 @@
+---
+title: KM05010: INFO_WarningsHaveFailedBuild
+---
+
+| | |
+|------------|---------- |
+| Message | The build failed because option "treat warnings as errors" is enabled and there are one or more warnings\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `INFO_WarningsHaveFailedBuild` |
+
+
diff --git a/developer/docs/help/reference/messages/km05011.md b/developer/docs/help/reference/messages/km05011.md
new file mode 100644
index 00000000000..f019f9e88f2
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05011.md
@@ -0,0 +1,11 @@
+---
+title: KM05011: ERROR_CannotCreateFolder
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_CannotCreateFolder` |
+
+
diff --git a/developer/docs/help/reference/messages/km05012.md b/developer/docs/help/reference/messages/km05012.md
new file mode 100644
index 00000000000..19bcaf883a7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05012.md
@@ -0,0 +1,11 @@
+---
+title: KM05012: ERROR_InvalidProjectFolder
+---
+
+| | |
+|------------|---------- |
+| Message | The folder <param> does not appear to be a Keyman Developer project\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_InvalidProjectFolder` |
+
+
diff --git a/developer/docs/help/reference/messages/km05013.md b/developer/docs/help/reference/messages/km05013.md
new file mode 100644
index 00000000000..bb5d07cf987
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05013.md
@@ -0,0 +1,11 @@
+---
+title: KM05013: ERROR_UnsupportedProjectVersion
+---
+
+| | |
+|------------|---------- |
+| Message | Project version <param> is not supported by this version of Keyman Developer\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_UnsupportedProjectVersion` |
+
+
diff --git a/developer/docs/help/reference/messages/km05014.md b/developer/docs/help/reference/messages/km05014.md
new file mode 100644
index 00000000000..3e812653143
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05014.md
@@ -0,0 +1,11 @@
+---
+title: KM05014: HINT_ProjectIsVersion10
+---
+
+| | |
+|------------|---------- |
+| Message | The project file is an older version and can be upgraded to version 17\.0 |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `HINT_ProjectIsVersion10` |
+
+
diff --git a/developer/docs/help/reference/messages/km05015.md b/developer/docs/help/reference/messages/km05015.md
new file mode 100644
index 00000000000..1807eba3d2c
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05015.md
@@ -0,0 +1,11 @@
+---
+title: KM05015: ERROR_OutFileCanOnlyBeSpecifiedWithSingleInfile
+---
+
+| | |
+|------------|---------- |
+| Message | Parameter \-\-out\-file can only be used with a single input file\. |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutFileCanOnlyBeSpecifiedWithSingleInfile` |
+
+
diff --git a/developer/docs/help/reference/messages/km05016.md b/developer/docs/help/reference/messages/km05016.md
new file mode 100644
index 00000000000..714d56acad1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05016.md
@@ -0,0 +1,11 @@
+---
+title: KM05016: ERROR_InvalidMessageFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message <param> must match format '\[KM\]\#\#\#\#\#\[:Disable\|Info\|Hint\|Warn\|Error\]' |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_InvalidMessageFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km05017.md b/developer/docs/help/reference/messages/km05017.md
new file mode 100644
index 00000000000..660f4471536
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05017.md
@@ -0,0 +1,11 @@
+---
+title: KM05017: ERROR_MessageNamespaceNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message <param> does not have a recognized namespace |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessageNamespaceNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km05018.md b/developer/docs/help/reference/messages/km05018.md
new file mode 100644
index 00000000000..8c9056d6a51
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05018.md
@@ -0,0 +1,11 @@
+---
+title: KM05018: ERROR_MessageCodeNotFound
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message undefined is not a recognized code |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessageCodeNotFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km05019.md b/developer/docs/help/reference/messages/km05019.md
new file mode 100644
index 00000000000..352928390f1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km05019.md
@@ -0,0 +1,11 @@
+---
+title: KM05019: ERROR_MessageCannotBeCoerced
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: \-\-message <param> is not of type 'info', 'hint' or 'warn', and cannot be coerced |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessageCannotBeCoerced` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501a.md b/developer/docs/help/reference/messages/km0501a.md
new file mode 100644
index 00000000000..0d1f3872233
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501a.md
@@ -0,0 +1,11 @@
+---
+title: KM0501A: ERROR_UnrecognizedMessageCode
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid parameter: message identifier '<param>' must match format '\[KM\]\#\#\#\#\#' |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_UnrecognizedMessageCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501b.md b/developer/docs/help/reference/messages/km0501b.md
new file mode 100644
index 00000000000..233e92f33c1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501b.md
@@ -0,0 +1,11 @@
+---
+title: KM0501B: ERROR_MustSpecifyMessageCode
+---
+
+| | |
+|------------|---------- |
+| Message | Must specify at least one message code or \-a for all messages |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MustSpecifyMessageCode` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501c.md b/developer/docs/help/reference/messages/km0501c.md
new file mode 100644
index 00000000000..510799f520b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501c.md
@@ -0,0 +1,11 @@
+---
+title: KM0501C: ERROR_MessagesCannotBeFilteredForMarkdownFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Messages cannot be filtered for markdown format |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_MessagesCannotBeFilteredForMarkdownFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501d.md b/developer/docs/help/reference/messages/km0501d.md
new file mode 100644
index 00000000000..c91d7897c21
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501d.md
@@ -0,0 +1,11 @@
+---
+title: KM0501D: ERROR_OutputPathMustBeSpecifiedForMarkdownFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Output path must be specified with \-o for markdown output format |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutputPathMustBeSpecifiedForMarkdownFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km0501e.md b/developer/docs/help/reference/messages/km0501e.md
new file mode 100644
index 00000000000..a4934b3856b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0501e.md
@@ -0,0 +1,11 @@
+---
+title: KM0501E: ERROR_OutputPathMustExistAndBeADirectory
+---
+
+| | |
+|------------|---------- |
+| Message | Output path <param> must exist and must be a folder |
+| Module | [kmc.InfrastructureMessages](kmc.infrastructuremessages) |
+| Identifier | `ERROR_OutputPathMustExistAndBeADirectory` |
+
+
diff --git a/developer/docs/help/reference/messages/km06001.md b/developer/docs/help/reference/messages/km06001.md
new file mode 100644
index 00000000000..31e5c8afcfb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km06001.md
@@ -0,0 +1,13 @@
+---
+title: KM06001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-analyze.AnalyzerMessages](kmc-analyze.analyzermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+Raised when an analysis components has an internal error. If you
+experience this error, it should be reported to the Keyman team for
+resolution via https://github.com/keymanapp/keyman/issues/new
diff --git a/developer/docs/help/reference/messages/km06002.md b/developer/docs/help/reference/messages/km06002.md
new file mode 100644
index 00000000000..b533ffd6675
--- /dev/null
+++ b/developer/docs/help/reference/messages/km06002.md
@@ -0,0 +1,11 @@
+---
+title: KM06002: INFO_ScanningFile
+---
+
+| | |
+|------------|---------- |
+| Message | Scanning <param> file <param> |
+| Module | [kmc-analyze.AnalyzerMessages](kmc-analyze.analyzermessages) |
+| Identifier | `INFO_ScanningFile` |
+
+Informative message reporting on the current file being scanned
diff --git a/developer/docs/help/reference/messages/km07001.md b/developer/docs/help/reference/messages/km07001.md
new file mode 100644
index 00000000000..05474815efc
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07001.md
@@ -0,0 +1,11 @@
+---
+title: KM07001: ERROR_NotAnyRequiresVersion14
+---
+
+| | |
+|------------|---------- |
+| Message | Statement notany in context\(\) match requires version 14\.0\+ of KeymanWeb |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_NotAnyRequiresVersion14` |
+
+
diff --git a/developer/docs/help/reference/messages/km07002.md b/developer/docs/help/reference/messages/km07002.md
new file mode 100644
index 00000000000..3725b73a769
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07002.md
@@ -0,0 +1,11 @@
+---
+title: KM07002: ERROR_TouchLayoutIdentifierRequires15
+---
+
+| | |
+|------------|---------- |
+| Message | Key "<param>" on "<param>", layer "<param>" has a multi\-part identifier which requires version 15\.0 or newer\. |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_TouchLayoutIdentifierRequires15` |
+
+
diff --git a/developer/docs/help/reference/messages/km07003.md b/developer/docs/help/reference/messages/km07003.md
new file mode 100644
index 00000000000..f248343f087
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07003.md
@@ -0,0 +1,11 @@
+---
+title: KM07003: ERROR_InvalidTouchLayoutFileFormat
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid touch layout file: <param> |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_InvalidTouchLayoutFileFormat` |
+
+
diff --git a/developer/docs/help/reference/messages/km07004.md b/developer/docs/help/reference/messages/km07004.md
new file mode 100644
index 00000000000..962ebd92f9b
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07004.md
@@ -0,0 +1,11 @@
+---
+title: KM07004: ERROR_TouchLayoutFileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | Touch layout file <param> does not exist |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `ERROR_TouchLayoutFileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km07005.md b/developer/docs/help/reference/messages/km07005.md
new file mode 100644
index 00000000000..852b8689757
--- /dev/null
+++ b/developer/docs/help/reference/messages/km07005.md
@@ -0,0 +1,11 @@
+---
+title: KM07005: HINT_TouchLayoutUsesUnsupportedGesturesDownlevel
+---
+
+| | |
+|------------|---------- |
+| Message | The touch layout uses a flick or multi\-tap gesture on key <param>, which is only available on version 17\.0\+ of Keyman |
+| Module | [kmc-kmn.KmwCompilerMessages](kmc-kmn.kmwcompilermessages) |
+| Identifier | `HINT_TouchLayoutUsesUnsupportedGesturesDownlevel` |
+
+
diff --git a/developer/docs/help/reference/messages/km08001.md b/developer/docs/help/reference/messages/km08001.md
new file mode 100644
index 00000000000..5387285f733
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08001.md
@@ -0,0 +1,11 @@
+---
+title: KM08001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km08002.md b/developer/docs/help/reference/messages/km08002.md
new file mode 100644
index 00000000000..77a251119aa
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08002.md
@@ -0,0 +1,11 @@
+---
+title: KM08002: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km08003.md b/developer/docs/help/reference/messages/km08003.md
new file mode 100644
index 00000000000..ae9d7244d9f
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08003.md
@@ -0,0 +1,11 @@
+---
+title: KM08003: ERROR_FileIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> could not be parsed: unknown error\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_FileIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km08004.md b/developer/docs/help/reference/messages/km08004.md
new file mode 100644
index 00000000000..82ad0635445
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08004.md
@@ -0,0 +1,11 @@
+---
+title: KM08004: WARN_MetadataFieldInconsistent
+---
+
+| | |
+|------------|---------- |
+| Message | Warning: field <param> value "<param>" does not match "<param>" found in source file metadata\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `WARN_MetadataFieldInconsistent` |
+
+
diff --git a/developer/docs/help/reference/messages/km08005.md b/developer/docs/help/reference/messages/km08005.md
new file mode 100644
index 00000000000..b71b3590c1a
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08005.md
@@ -0,0 +1,11 @@
+---
+title: KM08005: ERROR_InvalidAuthorEmail
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid author email: <param> |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_InvalidAuthorEmail` |
+
+
diff --git a/developer/docs/help/reference/messages/km08006.md b/developer/docs/help/reference/messages/km08006.md
new file mode 100644
index 00000000000..15e4997a403
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08006.md
@@ -0,0 +1,11 @@
+---
+title: KM08006: ERROR_LicenseFileIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> does not exist\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km08007.md b/developer/docs/help/reference/messages/km08007.md
new file mode 100644
index 00000000000..6b352d11109
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08007.md
@@ -0,0 +1,11 @@
+---
+title: KM08007: ERROR_LicenseFileIsDamaged
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> could not be loaded or decoded\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsDamaged` |
+
+
diff --git a/developer/docs/help/reference/messages/km08008.md b/developer/docs/help/reference/messages/km08008.md
new file mode 100644
index 00000000000..f7b5cfa3cef
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08008.md
@@ -0,0 +1,11 @@
+---
+title: KM08008: ERROR_LicenseIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | An error was encountered parsing license file <param>: <param>\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_LicenseIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km08009.md b/developer/docs/help/reference/messages/km08009.md
new file mode 100644
index 00000000000..969ba14c7b7
--- /dev/null
+++ b/developer/docs/help/reference/messages/km08009.md
@@ -0,0 +1,11 @@
+---
+title: KM08009: ERROR_NoLicenseFound
+---
+
+| | |
+|------------|---------- |
+| Message | No license for the model was found\. MIT license is required for publication to Keyman lexical\-models repository\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_NoLicenseFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0800a.md b/developer/docs/help/reference/messages/km0800a.md
new file mode 100644
index 00000000000..2d6170b19af
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0800a.md
@@ -0,0 +1,11 @@
+---
+title: KM0800A: ERROR_DescriptionIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | The Info\.Description field in the package <param> is required, but is missing or empty\. |
+| Module | [kmc-model-info.ModelInfoCompilerMessages](kmc-model-info.modelinfocompilermessages) |
+| Identifier | `ERROR_DescriptionIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km09001.md b/developer/docs/help/reference/messages/km09001.md
new file mode 100644
index 00000000000..7012d6e5516
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09001.md
@@ -0,0 +1,11 @@
+---
+title: KM09001: FATAL_UnexpectedException
+---
+
+| | |
+|------------|---------- |
+| Message | This is an internal error; the message will vary |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `FATAL_UnexpectedException` |
+
+
diff --git a/developer/docs/help/reference/messages/km09002.md b/developer/docs/help/reference/messages/km09002.md
new file mode 100644
index 00000000000..17882283d09
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09002.md
@@ -0,0 +1,11 @@
+---
+title: KM09002: ERROR_FileDoesNotExist
+---
+
+| | |
+|------------|---------- |
+| Message | File <param> does not exist\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_FileDoesNotExist` |
+
+
diff --git a/developer/docs/help/reference/messages/km09005.md b/developer/docs/help/reference/messages/km09005.md
new file mode 100644
index 00000000000..d88e2440adb
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09005.md
@@ -0,0 +1,11 @@
+---
+title: KM09005: ERROR_InvalidAuthorEmail
+---
+
+| | |
+|------------|---------- |
+| Message | Invalid author email: <param> |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_InvalidAuthorEmail` |
+
+
diff --git a/developer/docs/help/reference/messages/km09006.md b/developer/docs/help/reference/messages/km09006.md
new file mode 100644
index 00000000000..f8579775216
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09006.md
@@ -0,0 +1,11 @@
+---
+title: KM09006: ERROR_LicenseFileIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> does not exist\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/km09007.md b/developer/docs/help/reference/messages/km09007.md
new file mode 100644
index 00000000000..bbab92c762e
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09007.md
@@ -0,0 +1,11 @@
+---
+title: KM09007: ERROR_LicenseFileIsDamaged
+---
+
+| | |
+|------------|---------- |
+| Message | License file <param> could not be loaded or decoded\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_LicenseFileIsDamaged` |
+
+
diff --git a/developer/docs/help/reference/messages/km09008.md b/developer/docs/help/reference/messages/km09008.md
new file mode 100644
index 00000000000..bc6adb01a61
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09008.md
@@ -0,0 +1,11 @@
+---
+title: KM09008: ERROR_LicenseIsNotValid
+---
+
+| | |
+|------------|---------- |
+| Message | An error was encountered parsing license file <param>: <param>\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_LicenseIsNotValid` |
+
+
diff --git a/developer/docs/help/reference/messages/km09009.md b/developer/docs/help/reference/messages/km09009.md
new file mode 100644
index 00000000000..d8c08d3d6f1
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09009.md
@@ -0,0 +1,11 @@
+---
+title: KM09009: ERROR_CannotBuildWithoutKmpFile
+---
+
+| | |
+|------------|---------- |
+| Message | Compiling the \.keyboard\_info file requires a \.kmp file for metadata\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_CannotBuildWithoutKmpFile` |
+
+
diff --git a/developer/docs/help/reference/messages/km0900a.md b/developer/docs/help/reference/messages/km0900a.md
new file mode 100644
index 00000000000..8623a4f8dbf
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0900a.md
@@ -0,0 +1,11 @@
+---
+title: KM0900A: ERROR_NoLicenseFound
+---
+
+| | |
+|------------|---------- |
+| Message | No license for the keyboard was found\. MIT license is required for publication to Keyman keyboards repository\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_NoLicenseFound` |
+
+
diff --git a/developer/docs/help/reference/messages/km0900e.md b/developer/docs/help/reference/messages/km0900e.md
new file mode 100644
index 00000000000..a2ba732bb78
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0900e.md
@@ -0,0 +1,11 @@
+---
+title: KM0900E: ERROR_FontFileCannotBeRead
+---
+
+| | |
+|------------|---------- |
+| Message | Font <param> could not be parsed to extract a font family\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_FontFileCannotBeRead` |
+
+
diff --git a/developer/docs/help/reference/messages/km0900f.md b/developer/docs/help/reference/messages/km0900f.md
new file mode 100644
index 00000000000..e2d5511d425
--- /dev/null
+++ b/developer/docs/help/reference/messages/km0900f.md
@@ -0,0 +1,11 @@
+---
+title: KM0900F: ERROR_FontFileMetaDataIsInvalid
+---
+
+| | |
+|------------|---------- |
+| Message | Font <param> meta data invalid: <param>\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_FontFileMetaDataIsInvalid` |
+
+
diff --git a/developer/docs/help/reference/messages/km09010.md b/developer/docs/help/reference/messages/km09010.md
new file mode 100644
index 00000000000..4fc25c8c367
--- /dev/null
+++ b/developer/docs/help/reference/messages/km09010.md
@@ -0,0 +1,11 @@
+---
+title: KM09010: ERROR_DescriptionIsMissing
+---
+
+| | |
+|------------|---------- |
+| Message | The Info\.Description field in the package <param> is required, but is missing or empty\. |
+| Module | [kmc-keyboard-info.KeyboardInfoCompilerMessages](kmc-keyboard-info.keyboardinfocompilermessages) |
+| Identifier | `ERROR_DescriptionIsMissing` |
+
+
diff --git a/developer/docs/help/reference/messages/kmc-analyze.analyzermessages.md b/developer/docs/help/reference/messages/kmc-analyze.analyzermessages.md
new file mode 100644
index 00000000000..a874853ba00
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-analyze.analyzermessages.md
@@ -0,0 +1,8 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-analyze
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM06001](km06001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM06002](km06002) | `INFO_ScanningFile` | Scanning <param> file <param>
diff --git a/developer/docs/help/reference/messages/kmc-keyboard-info.keyboardinfocompilermessages.md b/developer/docs/help/reference/messages/kmc-keyboard-info.keyboardinfocompilermessages.md
new file mode 100644
index 00000000000..99c6bb97af9
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-keyboard-info.keyboardinfocompilermessages.md
@@ -0,0 +1,17 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-keyboard-info
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM09001](km09001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM09002](km09002) | `ERROR_FileDoesNotExist` | File <param> does not exist\.
+[KM09005](km09005) | `ERROR_InvalidAuthorEmail` | Invalid author email: <param>
+[KM09006](km09006) | `ERROR_LicenseFileIsMissing` | License file <param> does not exist\.
+[KM09007](km09007) | `ERROR_LicenseFileIsDamaged` | License file <param> could not be loaded or decoded\.
+[KM09008](km09008) | `ERROR_LicenseIsNotValid` | An error was encountered parsing license file <param>: <param>\.
+[KM09009](km09009) | `ERROR_CannotBuildWithoutKmpFile` | Compiling the \.keyboard\_info file requires a \.kmp file for metadata\.
+[KM0900A](km0900a) | `ERROR_NoLicenseFound` | No license for the keyboard was found\. MIT license is required for publication to Keyman keyboards repository\.
+[KM0900E](km0900e) | `ERROR_FontFileCannotBeRead` | Font <param> could not be parsed to extract a font family\.
+[KM0900F](km0900f) | `ERROR_FontFileMetaDataIsInvalid` | Font <param> meta data invalid: <param>\.
+[KM09010](km09010) | `ERROR_DescriptionIsMissing` | The Info\.Description field in the package <param> is required, but is missing or empty\.
diff --git a/developer/docs/help/reference/messages/kmc-kmn.kmncompilermessages.md b/developer/docs/help/reference/messages/kmc-kmn.kmncompilermessages.md
new file mode 100644
index 00000000000..4d7d2eb542e
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-kmn.kmncompilermessages.md
@@ -0,0 +1,177 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-kmn
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM02001](km02001) | `INFO_EndOfFile` | \(no error \- reserved code\)
+[KM02002](km02002) | `FATAL_BadCallParams` | CompileKeyboardFile was called with bad parameters
+[KM02004](km02004) | `FATAL_CannotAllocateMemory` | Out of memory
+[KM02005](km02005) | `ERROR_InfileNotExist` | Cannot find the input file
+[KM02007](km02007) | `FATAL_UnableToWriteFully` | Unable to write the file completely
+[KM02008](km02008) | `ERROR_CannotReadInfile` | Cannot read the input file
+[KM02009](km02009) | `FATAL_SomewhereIGotItWrong` | Internal error: contact Keyman
+[KM0200A](km0200a) | `ERROR_InvalidToken` | Invalid token found
+[KM0200B](km0200b) | `ERROR_InvalidBegin` | A "begin unicode" statement is required to compile a KeymanWeb keyboard
+[KM0200C](km0200c) | `ERROR_InvalidName` | Invalid 'name' command
+[KM0200D](km0200d) | `ERROR_InvalidVersion` | Invalid 'version' command
+[KM0200E](km0200e) | `ERROR_InvalidLanguageLine` | Invalid 'language' command
+[KM0200F](km0200f) | `ERROR_LayoutButNoLanguage` | Layout command found but no language command
+[KM02010](km02010) | `ERROR_InvalidLayoutLine` | Invalid 'layout' command
+[KM02011](km02011) | `ERROR_NoVersionLine` | No version line found for file
+[KM02012](km02012) | `ERROR_InvalidGroupLine` | Invalid 'group' command
+[KM02013](km02013) | `ERROR_InvalidStoreLine` | Invalid 'store' command
+[KM02014](km02014) | `ERROR_InvalidCodeInKeyPartOfRule` | Invalid command or code found in key part of rule
+[KM02015](km02015) | `ERROR_InvalidDeadkey` | Invalid 'deadkey' or 'dk' command
+[KM02016](km02016) | `ERROR_InvalidValue` | Invalid value in extended string
+[KM02017](km02017) | `ERROR_ZeroLengthString` | A string of zero characters was found
+[KM02018](km02018) | `ERROR_TooManyIndexToKeyRefs` | Too many index commands refering to key string
+[KM02019](km02019) | `ERROR_UnterminatedString` | Unterminated string in line
+[KM0201A](km0201a) | `ERROR_StringInVirtualKeySection` | extend string illegal in virtual key section
+[KM0201B](km0201b) | `ERROR_AnyInVirtualKeySection` | 'any' command is illegal in virtual key section
+[KM0201C](km0201c) | `ERROR_InvalidAny` | Invalid 'any' command
+[KM0201D](km0201d) | `ERROR_StoreDoesNotExist` | Store referenced does not exist
+[KM0201E](km0201e) | `ERROR_BeepInVirtualKeySection` | 'beep' command is illegal in virtual key section
+[KM0201F](km0201f) | `ERROR_IndexInVirtualKeySection` | 'index' command is illegal in virtual key section
+[KM02020](km02020) | `ERROR_InvalidIndex` | Invalid 'index' command
+[KM02021](km02021) | `ERROR_OutsInVirtualKeySection` | 'outs' command is illegal in virtual key section
+[KM02022](km02022) | `ERROR_InvalidOuts` | Invalid 'outs' command
+[KM02024](km02024) | `ERROR_ContextInVirtualKeySection` | 'context' command is illegal in virtual key section
+[KM02025](km02025) | `ERROR_InvalidUse` | Invalid 'use' command
+[KM02026](km02026) | `ERROR_GroupDoesNotExist` | Group does not exist
+[KM02027](km02027) | `ERROR_VirtualKeyNotAllowedHere` | Virtual key is not allowed here
+[KM02028](km02028) | `ERROR_InvalidSwitch` | Invalid 'switch' command
+[KM02029](km02029) | `ERROR_NoTokensFound` | No tokens found in line
+[KM0202A](km0202a) | `ERROR_InvalidLineContinuation` | Invalid line continuation
+[KM0202B](km0202b) | `ERROR_LineTooLong` | Line too long
+[KM0202C](km0202c) | `ERROR_InvalidCopyright` | Invalid 'copyright' command
+[KM0202D](km0202d) | `ERROR_CodeInvalidInThisSection` | This line is invalid in this section of the file
+[KM0202E](km0202e) | `ERROR_InvalidMessage` | Invalid 'message' command
+[KM0202F](km0202f) | `ERROR_InvalidLanguageName` | Invalid 'languagename' command
+[KM02030](km02030) | `ERROR_InvalidBitmapLine` | Invalid 'bitmaps' command
+[KM02031](km02031) | `ERROR_CannotReadBitmapFile` | Cannot open the bitmap or icon file for reading
+[KM02032](km02032) | `ERROR_IndexDoesNotPointToAny` | An index\(\) in the output does not have a corresponding any\(\) statement
+[KM02033](km02033) | `ERROR_ReservedCharacter` | A reserved character was found
+[KM02034](km02034) | `ERROR_InvalidCharacter` | A character was found that is outside the valid Unicode range \(U\+0000 \- U\+10FFFF\)
+[KM02035](km02035) | `ERROR_InvalidCall` | The 'call' command is invalid
+[KM02036](km02036) | `ERROR_CallInVirtualKeySection` | 'call' command is illegal in virtual key section
+[KM02037](km02037) | `ERROR_CodeInvalidInKeyStore` | The command is invalid inside a store that is used in a key part of the rule
+[KM02038](km02038) | `ERROR_CannotLoadIncludeFile` | Cannot load the included file: it is either invalid or does not exist
+[KM02039](km02039) | `ERROR_60FeatureOnly_EthnologueCode` | EthnologueCode system store requires VERSION 6\.0 or higher
+[KM0203A](km0203a) | `ERROR_60FeatureOnly_MnemonicLayout` | MnemonicLayout functionality requires VERSION 6\.0 or higher
+[KM0203B](km0203b) | `ERROR_60FeatureOnly_OldCharPosMatching` | OldCharPosMatching system store requires VERSION 6\.0 or higher
+[KM0203C](km0203c) | `ERROR_60FeatureOnly_NamedCodes` | Named character constants requires VERSION 6\.0 or higher
+[KM0203D](km0203d) | `ERROR_60FeatureOnly_Contextn` | Context\(n\) requires VERSION 6\.0 or higher
+[KM0203E](km0203e) | `ERROR_501FeatureOnly_Call` | Call\(\) requires VERSION 5\.01 or higher
+[KM0203F](km0203f) | `ERROR_InvalidNamedCode` | Invalid named code constant
+[KM02040](km02040) | `ERROR_InvalidSystemStore` | Invalid system store name found
+[KM02044](km02044) | `ERROR_60FeatureOnly_VirtualCharKey` | Virtual character keys require VERSION 6\.0 or higher
+[KM02045](km02045) | `ERROR_VersionAlreadyIncluded` | Only one VERSION or store\(version\) line allowed in a source file\.
+[KM02046](km02046) | `ERROR_70FeatureOnly` | This feature requires store\(version\) '7\.0' or higher
+[KM02047](km02047) | `ERROR_80FeatureOnly` | This feature requires store\(version\) '8\.0' or higher
+[KM02048](km02048) | `ERROR_InvalidInVirtualKeySection` | This statement is not valid in a virtual key section
+[KM02049](km02049) | `ERROR_InvalidIf` | The if\(\) statement is not valid
+[KM0204A](km0204a) | `ERROR_InvalidReset` | The reset\(\) statement is not valid
+[KM0204B](km0204b) | `ERROR_InvalidSet` | The set\(\) statement is not valid
+[KM0204C](km0204c) | `ERROR_InvalidSave` | The save\(\) statement is not valid
+[KM0204D](km0204d) | `ERROR_InvalidEthnologueCode` | Invalid ethnologuecode format
+[KM0204E](km0204e) | `FATAL_CannotCreateTempfile` | Cannot create temp file
+[KM0204F](km0204f) | `ERROR_90FeatureOnly_IfSystemStores` | if\(store\) requires store\(version\) '9\.0' or higher
+[KM02050](km02050) | `ERROR_IfSystemStore_NotFound` | System store in if\(\) not found
+[KM02051](km02051) | `ERROR_90FeatureOnly_SetSystemStores` | set\(store\) requires store\(version\) '9\.0' or higher
+[KM02052](km02052) | `ERROR_SetSystemStore_NotFound` | System store in set\(\) not found
+[KM02053](km02053) | `ERROR_90FeatureOnlyVirtualKeyDictionary` | Custom virtual key names require store\(version\) '9\.0'
+[KM02054](km02054) | `ERROR_NotSupportedInKeymanWebContext` | Statement '<param>' is not currently supported in context for web and touch targets
+[KM02055](km02055) | `ERROR_NotSupportedInKeymanWebOutput` | Statement '<param>' is not currently supported in output for web and touch targets
+[KM02056](km02056) | `ERROR_NotSupportedInKeymanWebStore` | '<param>' is not currently supported in store '<param>' when used by any or index for web and touch targets
+[KM02057](km02057) | `ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb` | Virtual character keys not currently supported in KeymanWeb
+[KM02058](km02058) | `ERROR_VirtualKeysNotValidForMnemonicLayouts` | Virtual keys are not valid for mnemonic layouts
+[KM02059](km02059) | `ERROR_InvalidTouchLayoutFile` | Touch layout file <param> is not valid
+[KM0205A](km0205a) | `ERROR_TouchLayoutInvalidIdentifier` | Key "<param>" on "<param>", layer "<param>" has an invalid identifier\.
+[KM0205B](km0205b) | `ERROR_InvalidKeyCode` | Invalid key identifier "<param>"
+[KM0205C](km0205c) | `ERROR_90FeatureOnlyLayoutFile` | Touch layout file reference requires store\(version\) '9\.0'or higher
+[KM0205D](km0205d) | `ERROR_90FeatureOnlyKeyboardVersion` | KeyboardVersion system store requires store\(version\) '9\.0'or higher
+[KM0205E](km0205e) | `ERROR_KeyboardVersionFormatInvalid` | KeyboardVersion format is invalid, expecting dot\-separated integers
+[KM0205F](km0205f) | `ERROR_ContextExHasInvalidOffset` | context\(\) statement has offset out of range
+[KM02060](km02060) | `ERROR_90FeatureOnlyEmbedCSS` | Embedding CSS requires store\(version\) '9\.0'or higher
+[KM02061](km02061) | `ERROR_90FeatureOnlyTargets` | TARGETS system store requires store\(version\) '9\.0'or higher
+[KM02062](km02062) | `ERROR_ContextAndIndexInvalidInMatchNomatch` | context and index statements cannot be used in a match or nomatch statement
+[KM02063](km02063) | `ERROR_140FeatureOnlyContextAndNotAnyWeb` | For web and touch platforms, context\(\) statement referring to notany\(\) requires store\(version\) '14\.0'or higher
+[KM02064](km02064) | `ERROR_ExpansionMustFollowCharacterOrVKey` | An expansion must follow a character or a virtual key
+[KM02065](km02065) | `ERROR_VKeyExpansionMustBeFollowedByVKey` | A virtual key expansion must be terminated by a virtual key
+[KM02066](km02066) | `ERROR_CharacterExpansionMustBeFollowedByCharacter` | A character expansion must be terminated by a character key
+[KM02067](km02067) | `ERROR_VKeyExpansionMustUseConsistentShift` | A virtual key expansion must use the same shift state for both terminators
+[KM02068](km02068) | `ERROR_ExpansionMustBePositive` | An expansion must have positive difference \(i\.e\. A\-Z, not Z\-A\)
+[KM02069](km02069) | `ERROR_CasedKeysMustContainOnlyVirtualKeys` | The &CasedKeys system store must contain only virtual keys or characters found on a US English keyboard
+[KM0206A](km0206a) | `ERROR_CasedKeysMustNotIncludeShiftStates` | The &CasedKeys system store must not include shift states
+[KM0206B](km0206b) | `ERROR_CasedKeysNotSupportedWithMnemonicLayout` | The &CasedKeys system store is not supported with mnemonic layouts
+[KM0206C](km0206c) | `ERROR_CannotUseReadWriteGroupFromReadonlyGroup` | Group used from a readonly group must also be readonly
+[KM0206D](km0206d) | `ERROR_StatementNotPermittedInReadonlyGroup` | Statement is not permitted in output of readonly group
+[KM0206E](km0206e) | `ERROR_OutputInReadonlyGroup` | Output is not permitted in a readonly group
+[KM0206F](km0206f) | `ERROR_NewContextGroupMustBeReadonly` | Group used in begin newContext must be readonly
+[KM02070](km02070) | `ERROR_PostKeystrokeGroupMustBeReadonly` | Group used in begin postKeystroke must be readonly
+[KM02071](km02071) | `ERROR_DuplicateGroup` | A group with this name has already been defined\.
+[KM02072](km02072) | `ERROR_DuplicateStore` | A store with this name has already been defined\.
+[KM02073](km02073) | `ERROR_RepeatedBegin` | Begin has already been set
+[KM02074](km02074) | `ERROR_VirtualKeyInContext` | Virtual keys are not permitted in context
+[KM02075](km02075) | `ERROR_OutsTooLong` | Store cannot be inserted with outs\(\) as it makes the extended string too long
+[KM02076](km02076) | `ERROR_ExtendedStringTooLong` | Extended string is too long
+[KM02077](km02077) | `ERROR_VirtualKeyExpansionTooLong` | Virtual key expansion is too large
+[KM02078](km02078) | `ERROR_CharacterRangeTooLong` | Character range is too large and cannot be expanded
+[KM02079](km02079) | `ERROR_NonBMPCharactersNotSupportedInKeySection` | Characters with codepoints over U\+FFFF are not supported in the key part of the rule
+[KM02080](km02080) | `WARN_TooManyWarnings` | Too many warnings or errors
+[KM02081](km02081) | `WARN_OldVersion` | The keyboard file is an old version
+[KM02082](km02082) | `WARN_BitmapNotUsed` | The 'bitmaps' statement is obsolete and only the first bitmap referred to will be used, you should use 'bitmap'\.
+[KM02083](km02083) | `WARN_CustomLanguagesNotSupported` | Languages over 0x1FF, 0x1F are not supported correctly by Windows\. You should use no LANGUAGE line instead\.
+[KM02084](km02084) | `WARN_KeyBadLength` | There are too many characters in the keystroke part of the rule\.
+[KM02085](km02085) | `WARN_IndexStoreShort` | The store referenced in index\(\) is shorter than the store referenced in any\(\)
+[KM02086](km02086) | `WARN_UnicodeInANSIGroup` | A Unicode character was found in an ANSI group
+[KM02087](km02087) | `WARN_ANSIInUnicodeGroup` | An ANSI character was found in a Unicode group
+[KM02088](km02088) | `WARN_UnicodeSurrogateUsed` | A Unicode surrogate character was found\. You should use Unicode scalar values to represent values > U\+FFFF
+[KM02089](km02089) | `WARN_ReservedCharacter` | A Unicode character was found that should not be used
+[KM0208A](km0208a) | `INFO_Info` | Information
+[KM0208B](km0208b) | `WARN_VirtualKeyWithMnemonicLayout` | Virtual key used instead of virtual character key with a mnemonic layout
+[KM0208C](km0208c) | `WARN_VirtualCharKeyWithPositionalLayout` | Virtual character key used with a positional layout instead of mnemonic layout
+[KM0208D](km0208d) | `WARN_StoreAlreadyUsedAsOptionOrCall` | Store already used as an option or in a call statement and should not be used as a normal store
+[KM0208E](km0208e) | `WARN_StoreAlreadyUsedAsStoreOrCall` | Store already used as a normal store or in a call statement and should not be used as an option
+[KM0208F](km0208f) | `WARN_StoreAlreadyUsedAsStoreOrOption` | Store already used as a normal store or as an option and should not be used in a call statement
+[KM02090](km02090) | `WARN_PunctuationInEthnologueCode` | Punctuation should not be used to separate Ethnologue codes; instead use spaces
+[KM02091](km02091) | `WARN_TouchLayoutMissingLayer` | Key "<param>" on platform "<param>", layer "<param>", references a missing layer "<param>"
+[KM02092](km02092) | `WARN_TouchLayoutCustomKeyNotDefined` | Key "<param>" on platform "<param>", layer "<param>", is a custom key but has no corresponding rule in the source\.
+[KM02093](km02093) | `WARN_TouchLayoutMissingRequiredKeys` | Layer "<param>" on platform "<param>" is missing the required key\(s\) '<param>'\.
+[KM02094](km02094) | `WARN_HelpFileMissing` | File <param> could not be loaded:
+[KM02095](km02095) | `WARN_EmbedJsFileMissing` | File <param> could not be loaded:
+[KM02098](km02098) | `WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb` | Extended shift flags <param> are not supported in KeymanWeb
+[KM02099](km02099) | `WARN_TouchLayoutUnidentifiedKey` | A key on layer "<param>" has no identifier\.
+[KM0209A](km0209a) | `HINT_UnreachableKeyCode` | The rule will never be matched for key <param> because its key code is never fired\.
+[KM0209C](km0209c) | `WARN_PlatformNotInTargets` | The specified platform is not a target platform
+[KM0209D](km0209d) | `WARN_HeaderStatementIsDeprecated` | Header statements are deprecated; use instead the equivalent system store
+[KM0209E](km0209e) | `WARN_UseNotLastStatementInRule` | A rule with use\(\) statements in the output should not have other content following the use\(\) statements
+[KM0209F](km0209f) | `WARN_TouchLayoutFontShouldBeSameForAllPlatforms` | The touch layout font should be the same for all platforms\.
+[KM020A2](km020a2) | `WARN_KVKFileIsInSourceFormat` | \.kvk file should be binary but is an XML file
+[KM020A3](km020a3) | `WARN_DontMixChiralAndNonChiralModifiers` | This keyboard contains Ctrl,Alt and LCtrl,LAlt,RCtrl,RAlt sets of modifiers\. Use only one or the other set for web target\.
+[KM020A4](km020a4) | `WARN_MixingLeftAndRightModifiers` | Left and right modifiers should not both be used in the same rule
+[KM020A5](km020a5) | `WARN_LanguageHeadersDeprecatedInKeyman10` | This language header has been deprecated in Keyman 10\. Instead, add language metadata in the package file
+[KM020A6](km020a6) | `HINT_NonUnicodeFile` | Keyman Developer has detected that the file has ANSI encoding\. Consider converting this file to UTF\-8
+[KM020A8](km020a8) | `WARN_HotkeyHasInvalidModifier` | Hotkey has modifiers that are not supported\. Use only SHIFT, CTRL and ALT
+[KM020A9](km020a9) | `WARN_TouchLayoutSpecialLabelOnNormalKey` | Key "<param>" on platform "<param>", layer "<param>" does not have the key type "Special" or "Special \(active\)" but has the label "<param>"\. This feature is only supported in Keyman 14 or later
+[KM020AA](km020aa) | `WARN_OptionStoreNameInvalid` | The option store <param> should be named with characters in the range A\-Z, a\-z, 0\-9 and \_ only\.
+[KM020AB](km020ab) | `WARN_NulNotFirstStatementInContext` | nul must be the first statement in the context
+[KM020AC](km020ac) | `WARN_IfShouldBeAtStartOfContext` | if, platform and baselayout should be at start of context \(after nul, if present\)
+[KM020AD](km020ad) | `WARN_KeyShouldIncludeNCaps` | Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches
+[KM020AE](km020ae) | `HINT_UnreachableRule` | This rule will never be matched as another rule takes precedence
+[KM020AF](km020af) | `WARN_VirtualKeyInOutput` | Virtual keys are not supported in output
+[KM020C0](km020c0) | `FATAL_BufferOverflow` | The compiler memory buffer overflowed
+[KM020C1](km020c1) | `FATAL_Break` | Compiler interrupted by user
+[KM02900](km02900) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM02901](km02901) | `FATAL_MissingWasmModule` | This is an internal error; the message will vary
+[KM02903](km02903) | `FATAL_CallbacksNotSet` | This is an internal error; the message will vary
+[KM02904](km02904) | `FATAL_UnicodeSetOutOfRange` | This is an internal error; the message will vary
+[KM02905](km02905) | `ERROR_UnicodeSetHasStrings` | uset contains strings, not allowed
+[KM02906](km02906) | `ERROR_UnicodeSetHasProperties` | uset contains properties, not allowed
+[KM02907](km02907) | `ERROR_UnicodeSetSyntaxError` | uset had a Syntax Error while parsing
+[KM02908](km02908) | `ERROR_InvalidKvksFile` | Error encountered parsing <param>: unknown error
+[KM02909](km02909) | `WARN_InvalidVkeyInKvksFile` | Invalid virtual key <param> found in <param>
+[KM0290A](km0290a) | `ERROR_InvalidDisplayMapFile` | Error encountered parsing display map <param>: unknown error
+[KM0290B](km0290b) | `ERROR_InvalidKvkFile` | Error encountered loading <param>: unknown error
+[KM0290C](km0290c) | `ERROR_FileNotFound` | File <param> was not found
diff --git a/developer/docs/help/reference/messages/kmc-kmn.kmwcompilermessages.md b/developer/docs/help/reference/messages/kmc-kmn.kmwcompilermessages.md
new file mode 100644
index 00000000000..84c21e4a170
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-kmn.kmwcompilermessages.md
@@ -0,0 +1,11 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-kmn
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM07001](km07001) | `ERROR_NotAnyRequiresVersion14` | Statement notany in context\(\) match requires version 14\.0\+ of KeymanWeb
+[KM07002](km07002) | `ERROR_TouchLayoutIdentifierRequires15` | Key "<param>" on "<param>", layer "<param>" has a multi\-part identifier which requires version 15\.0 or newer\.
+[KM07003](km07003) | `ERROR_InvalidTouchLayoutFileFormat` | Invalid touch layout file: <param>
+[KM07004](km07004) | `ERROR_TouchLayoutFileDoesNotExist` | Touch layout file <param> does not exist
+[KM07005](km07005) | `HINT_TouchLayoutUsesUnsupportedGesturesDownlevel` | The touch layout uses a flick or multi\-tap gesture on key <param>, which is only available on version 17\.0\+ of Keyman
diff --git a/developer/docs/help/reference/messages/kmc-ldml.compilermessages.md b/developer/docs/help/reference/messages/kmc-ldml.compilermessages.md
new file mode 100644
index 00000000000..46dbf4e2e9c
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-ldml.compilermessages.md
@@ -0,0 +1,49 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-ldml
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM00001](km00001) | `HINT_NormalizationDisabled` | normalization=disabled is not recommended\.
+[KM00002](km00002) | `ERROR_InvalidLocale` | Invalid BCP 47 locale form '<param>'
+[KM00003](km00003) | `ERROR_HardwareLayerHasTooManyRows` | 'hardware' layer has too many rows
+[KM00004](km00004) | `ERROR_RowOnHardwareLayerHasTooManyKeys` | Row \#<param> on 'hardware' <param> layer for modifier none has too many keys
+[KM00005](km00005) | `ERROR_KeyNotFoundInKeyBag` | Key '<param>' in position \#<param> on row \#<param> of layer <param>, form '<param>' not found in key bag
+[KM00006](km00006) | `HINT_OneOrMoreRepeatedLocales` | After minimization, one or more locales is repeated and has been removed
+[KM00007](km00007) | `ERROR_InvalidFile` | The source file has an invalid structure: <param>
+[KM00008](km00008) | `HINT_LocaleIsNotMinimalAndClean` | Locale '<param>' is not minimal or correctly formatted and should be '<param>'
+[KM00009](km00009) | `ERROR_InvalidScanCode` | Form '<param>' has invalid/unknown scancodes '<param>'
+[KM0000A](km0000a) | `WARN_CustomForm` | Custom <form id="<param>"> element\. Key layout may not be as expected\.
+[KM0000B](km0000b) | `ERROR_GestureKeyNotFoundInKeyBag` | Key '<param>' not found in key bag, referenced from other '<param>' in <param>
+[KM0000D](km0000d) | `ERROR_InvalidVersion` | Version number '<param>' must be a semantic version format string\.
+[KM0000E](km0000e) | `ERROR_MustBeAtLeastOneLayerElement` | The source file must contain at least one layer element\.
+[KM00010](km00010) | `ERROR_DisplayIsRepeated` | display has more than one display entry\.
+[KM00011](km00011) | `ERROR_KeyMissingToGapOrSwitch` | key id='<param>' must have either output=, gap=, or layerId=\.
+[KM00012](km00012) | `ERROR_ExcessHardware` | layers formId=<param>: Can only have one non\-'touch' element
+[KM00013](km00013) | `ERROR_InvalidHardware` | layers has invalid value formId=<param>
+[KM00014](km00014) | `ERROR_InvalidModifier` | layer has invalid modifiers='<param>' on layer id=<param>
+[KM00015](km00015) | `ERROR_MissingFlicks` | key id=<param> refers to missing flickId=<param>
+[KM00016](km00016) | `ERROR_DuplicateVariable` | duplicate variables: id=<param>
+[KM00018](km00018) | `ERROR_InvalidTransformsType` | Invalid transforms types: '<param>'
+[KM00019](km00019) | `ERROR_DuplicateTransformsType` | Duplicate transforms types: '<param>'
+[KM0001A](km0001a) | `ERROR_MixedTransformGroup` | transformGroup cannot contain both reorder and transform elements
+[KM0001B](km0001b) | `ERROR_EmptyTransformGroup` | transformGroup must have either reorder or transform elements
+[KM0001C](km0001c) | `ERROR_MissingStringVariable` | Reference to undefined string variable: $\{<param>\}
+[KM0001D](km0001d) | `ERROR_MissingSetVariable` | Reference to undefined set variable: $\[<param>\]
+[KM0001E](km0001e) | `ERROR_MissingUnicodeSetVariable` | Reference to undefined UnicodeSet variable: $\[<param>\]
+[KM0001F](km0001f) | `ERROR_NeedSpacesBetweenSetVariables` | Need spaces between set variables: <param>
+[KM00020](km00020) | `ERROR_CantReferenceSetFromUnicodeSet` | Illegal use of set variable from within UnicodeSet: $\[<param>\]
+[KM00021](km00021) | `ERROR_MissingMarkers` | Markers used for matching but not defined: <param>
+[KM00022](km00022) | `ERROR_DisplayNeedsToOrId` | display needs output= or keyId=, but not both
+[KM00023](km00023) | `HINT_PUACharacters` | File contains <param> PUA character\(s\), including Illegal \(U\+NAN\)
+[KM00024](km00024) | `WARN_UnassignedCharacters` | File contains <param> unassigned character\(s\), including Illegal \(U\+NAN\)
+[KM00025](km00025) | `ERROR_IllegalCharacters` | File contains <param> illegal character\(s\), including Illegal \(U\+NAN\)
+[KM00026](km00026) | `HINT_CharClassImplicitDenorm` | File has character classes which span non\-NFD character\(s\), including Illegal \(U\+NAN\)\. These will not match any text\.
+[KM00027](km00027) | `WARN_CharClassExplicitDenorm` | File has character classes which include non\-NFD characters\(s\), including Illegal \(U\+NAN\)\. These will not match any text\.
+[KM00028](km00028) | `ERROR_UnparseableReorderSet` | Illegal UnicodeSet "<param>" in reorder "<param>
+[KM00030](km00030) | `ERROR_InvalidQuadEscape` | Invalid escape "\\u0000"\. Hint: Use "\\u\{<param>\}"
+[KM00F00](km00f00) | `ERROR_UnparseableTransformFrom` | Invalid transform from="<param>": "<param>"
+[KM00F01](km00f01) | `ERROR_IllegalTransformDollarsign` | Invalid transform from="<param>": Unescaped dollar\-sign \($\) is not valid transform syntax\.
+[KM00F02](km00f02) | `ERROR_TransformFromMatchesNothing` | Invalid transfom from="<param>": Matches an empty string\.
+[KM00F03](km00f03) | `ERROR_IllegalTransformPlus` | Invalid transform from="<param>": Unescaped plus \(\+\) is not valid transform syntax\.
+[KM00F04](km00f04) | `ERROR_IllegalTransformAsterisk` | Invalid transform from="<param>": Unescaped asterisk \(\*\) is not valid transform syntax\.
diff --git a/developer/docs/help/reference/messages/kmc-model-info.modelinfocompilermessages.md b/developer/docs/help/reference/messages/kmc-model-info.modelinfocompilermessages.md
new file mode 100644
index 00000000000..61837405eb4
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-model-info.modelinfocompilermessages.md
@@ -0,0 +1,16 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-model-info
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM08001](km08001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM08002](km08002) | `ERROR_FileDoesNotExist` | File <param> does not exist\.
+[KM08003](km08003) | `ERROR_FileIsNotValid` | File <param> could not be parsed: unknown error\.
+[KM08004](km08004) | `WARN_MetadataFieldInconsistent` | Warning: field <param> value "<param>" does not match "<param>" found in source file metadata\.
+[KM08005](km08005) | `ERROR_InvalidAuthorEmail` | Invalid author email: <param>
+[KM08006](km08006) | `ERROR_LicenseFileIsMissing` | License file <param> does not exist\.
+[KM08007](km08007) | `ERROR_LicenseFileIsDamaged` | License file <param> could not be loaded or decoded\.
+[KM08008](km08008) | `ERROR_LicenseIsNotValid` | An error was encountered parsing license file <param>: <param>\.
+[KM08009](km08009) | `ERROR_NoLicenseFound` | No license for the model was found\. MIT license is required for publication to Keyman lexical\-models repository\.
+[KM0800A](km0800a) | `ERROR_DescriptionIsMissing` | The Info\.Description field in the package <param> is required, but is missing or empty\.
diff --git a/developer/docs/help/reference/messages/kmc-model.modelcompilermessages.md b/developer/docs/help/reference/messages/kmc-model.modelcompilermessages.md
new file mode 100644
index 00000000000..aaa55ec3bb7
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-model.modelcompilermessages.md
@@ -0,0 +1,16 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-model
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM03001](km03001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM03002](km03002) | `HINT_MixedNormalizationForms` | “<param>” is not in Unicode NFC\. Automatically converting to NFC\.
+[KM03003](km03003) | `HINT_DuplicateWordInSameFile` | duplicate word “<param>” found in same file; summing counts
+[KM03004](km03004) | `ERROR_UnimplementedModelFormat` | Unimplemented model format: <param>
+[KM03005](km03005) | `ERROR_UnknownModelFormat` | Unimplemented model format: <param>
+[KM03006](km03006) | `ERROR_NoDefaultExport` | Model source does have a default export\. Did you remember to write \`export default source;\`?
+[KM03007](km03007) | `ERROR_SearchTermToKeyMustBeExplicitlySpecified` | searchTermToKey must be explicitly specified
+[KM03008](km03008) | `ERROR_UTF16BEUnsupported` | UTF\-16BE is unsupported
+[KM03009](km03009) | `ERROR_UnknownWordBreaker` | Unknown word breaker: <param>
+[KM0300A](km0300a) | `ERROR_UnsupportedScriptOverride` | Unsupported script override: <param>
diff --git a/developer/docs/help/reference/messages/kmc-package.compilermessages.md b/developer/docs/help/reference/messages/kmc-package.compilermessages.md
new file mode 100644
index 00000000000..627fd5860ad
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc-package.compilermessages.md
@@ -0,0 +1,38 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc-package
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM04001](km04001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM04002](km04002) | `WARN_AbsolutePath` | File <param> has an absolute path, which is not portable\.
+[KM04003](km04003) | `ERROR_FileDoesNotExist` | File <param> does not exist\.
+[KM04004](km04004) | `ERROR_FileCouldNotBeRead` | File <param> could not be read: unknown error\.
+[KM04005](km04005) | `WARN_FileIsNotABinaryKvkFile` | File <param> does not appear to be a valid binary \.kvk file; this may be an old package that includes an xml\-format \.kvk file\. You must update the package to include the compiled \.kvk file in the package\.
+[KM04006](km04006) | `ERROR_FollowKeyboardVersionNotAllowedForModelPackages` | FollowKeyboardVersion is not allowed in model packages
+[KM04007](km04007) | `ERROR_FollowKeyboardVersionButNoKeyboards` | FollowKeyboardVersion is set, but the package contains no keyboards
+[KM04008](km04008) | `ERROR_KeyboardContentFileNotFound` | Keyboard <param> was listed in <Keyboards> but a corresponding \.kmx file was not found in <Files>
+[KM04009](km04009) | `ERROR_KeyboardFileNotValid` | Keyboard file <param> is not a valid \.kmx file: unknown error
+[KM0400A](km0400a) | `INFO_KeyboardFileHasNoKeyboardVersion` | Keyboard file <param> has no &KeyboardVersion store, using default '0\.0'
+[KM0400B](km0400b) | `ERROR_PackageCannotContainBothModelsAndKeyboards` | The package contains both lexical models and keyboards, which is not permitted\.
+[KM0400C](km0400c) | `HINT_PackageShouldNotRepeatLanguages` | Two language tags in <param> <param>, '<param>' and '<param>', reduce to the same minimal tag '<param>'\.
+[KM0400D](km0400d) | `WARN_PackageNameDoesNotFollowLexicalModelConventions` | The package file <param> does not follow the recommended model filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), not start with a digit, and should have the structure <author>\.<bcp47>\.<uniq>\.model\.kps\.
+[KM0400E](km0400e) | `WARN_PackageNameDoesNotFollowKeyboardConventions` | The package file <param> does not follow the recommended keyboard filename conventions\. The name should be all lower case, include only alphanumeric characters and underscore \(\_\), and not start with a digit\.
+[KM0400F](km0400f) | `WARN_FileInPackageDoesNotFollowFilenameConventions` | The file <param> does not follow the recommended filename conventions\. The extension should be all lower case, and the filename should include only alphanumeric characters, \-, \_, \+ and \.
+[KM04010](km04010) | `ERROR_PackageNameCannotBeBlank` | Package name cannot be an empty string\.
+[KM04011](km04011) | `ERROR_KeyboardFileNotFound` | Keyboard file <param> was not found\. Has it been compiled?
+[KM04012](km04012) | `WARN_KeyboardVersionsDoNotMatch` | Keyboard <param> version <param> does not match keyboard <param> version <param>\.
+[KM04014](km04014) | `ERROR_LanguageTagIsNotValid` | Language tag '<param>' in <param> <param> is invalid\.
+[KM04015](km04015) | `HINT_LanguageTagIsNotMinimal` | Language tag '<param>' in <param> <param> is not minimal, and should be '<param>'\.
+[KM04016](km04016) | `ERROR_ModelMustHaveAtLeastOneLanguage` | The lexical model <param> must have at least one language specified\.
+[KM04017](km04017) | `WARN_RedistFileShouldNotBeInPackage` | The Keyman system file '<param>' should not be compiled into the package\.
+[KM04018](km04018) | `WARN_DocFileDangerous` | Microsoft Word \.doc or \.docx files \('<param>'\) are not portable\. You should instead use HTML or PDF format\.
+[KM04019](km04019) | `ERROR_PackageMustContainAModelOrAKeyboard` | Package must contain a lexical model or a keyboard\.
+[KM0401A](km0401a) | `WARN_JsKeyboardFileIsMissing` | Keyboard <param> targets touch devices but corresponding <param>\.js file is not in the package\.
+[KM0401B](km0401b) | `WARN_KeyboardShouldHaveAtLeastOneLanguage` | The keyboard <param> should have at least one language specified\.
+[KM0401C](km0401c) | `HINT_JsKeyboardFileHasNoTouchTargets` | The keyboard <param> has been included for touch platforms, but does not include a touch layout\.
+[KM0401D](km0401d) | `HINT_PackageContainsSourceFile` | The source file <param> should not be included in the package; instead include the compiled result\.
+[KM0401E](km0401e) | `ERROR_InvalidPackageFile` | Package source file is invalid: unknown error
+[KM0401F](km0401f) | `ERROR_FileRecordIsMissingName` | File record in the package with description 'undefined' is missing a filename\.
+[KM04020](km04020) | `ERROR_InvalidAuthorEmail` | Invalid author email: <param>
+[KM04021](km04021) | `ERROR_PackageFileHasEmptyVersion` | Package version is not following keyboard version, but the package version field is blank\.
diff --git a/developer/docs/help/reference/messages/kmc.infrastructuremessages.md b/developer/docs/help/reference/messages/kmc.infrastructuremessages.md
new file mode 100644
index 00000000000..ce80ed88852
--- /dev/null
+++ b/developer/docs/help/reference/messages/kmc.infrastructuremessages.md
@@ -0,0 +1,36 @@
+---
+title: Compiler Messages Reference for @keymanapp/kmc
+---
+
+ Code | Identifier | Message
+------|------------|---------
+[KM05001](km05001) | `FATAL_UnexpectedException` | This is an internal error; the message will vary
+[KM05002](km05002) | `INFO_BuildingFile` | Building <param>
+[KM05003](km05003) | `ERROR_FileDoesNotExist` | File <param> does not exist
+[KM05004](km05004) | `ERROR_FileTypeNotRecognized` | Unrecognised input file <param>, expecting <param>, or project folder
+[KM05005](km05005) | `ERROR_OutFileNotValidForProjects` | \-\-out\-file should not be specified for project builds
+[KM05006](km05006) | `INFO_FileBuiltSuccessfully` | <param> built successfully\.
+[KM05007](km05007) | `INFO_FileNotBuiltSuccessfully` | <param> failed to build\.
+[KM05008](km05008) | `ERROR_InvalidProjectFile` | Project file is not valid: <param>
+[KM05009](km05009) | `HINT_FilenameHasDifferingCase` | File on disk '<param>' does not match case of '<param>' in source file; this is an error on platforms with case\-sensitive filesystems\.
+[KM0500A](km0500a) | `ERROR_UnknownFileFormat` | Unknown file format <param>; only Markdown \(\.md\), JSON \(\.json\), and Text \(\.txt\) are supported\.
+[KM0500B](km0500b) | `INFO_ProjectBuiltSuccessfully` | Project <param> built successfully\.
+[KM0500C](km0500c) | `INFO_ProjectNotBuiltSuccessfully` | Project <param> failed to build\.
+[KM0500D](km0500d) | `INFO_TooManyMessages` | More than <param> warnings or errors received; suppressing further messages\.
+[KM0500E](km0500e) | `ERROR_FileTypeNotFound` | A file of type <param> was not found in the project\.
+[KM0500F](km0500f) | `ERROR_NotAProjectFile` | File <param> must have a \.kpj extension to be treated as a project\.
+[KM05010](km05010) | `INFO_WarningsHaveFailedBuild` | The build failed because option "treat warnings as errors" is enabled and there are one or more warnings\.
+[KM05011](km05011) | `ERROR_CannotCreateFolder` | This is an internal error; the message will vary
+[KM05012](km05012) | `ERROR_InvalidProjectFolder` | The folder <param> does not appear to be a Keyman Developer project\.
+[KM05013](km05013) | `ERROR_UnsupportedProjectVersion` | Project version <param> is not supported by this version of Keyman Developer\.
+[KM05014](km05014) | `HINT_ProjectIsVersion10` | The project file is an older version and can be upgraded to version 17\.0
+[KM05015](km05015) | `ERROR_OutFileCanOnlyBeSpecifiedWithSingleInfile` | Parameter \-\-out\-file can only be used with a single input file\.
+[KM05016](km05016) | `ERROR_InvalidMessageFormat` | Invalid parameter: \-\-message <param> must match format '\[KM\]\#\#\#\#\#\[:Disable\|Info\|Hint\|Warn\|Error\]'
+[KM05017](km05017) | `ERROR_MessageNamespaceNotFound` | Invalid parameter: \-\-message <param> does not have a recognized namespace
+[KM05018](km05018) | `ERROR_MessageCodeNotFound` | Invalid parameter: \-\-message undefined is not a recognized code
+[KM05019](km05019) | `ERROR_MessageCannotBeCoerced` | Invalid parameter: \-\-message <param> is not of type 'info', 'hint' or 'warn', and cannot be coerced
+[KM0501A](km0501a) | `ERROR_UnrecognizedMessageCode` | Invalid parameter: message identifier '<param>' must match format '\[KM\]\#\#\#\#\#'
+[KM0501B](km0501b) | `ERROR_MustSpecifyMessageCode` | Must specify at least one message code or \-a for all messages
+[KM0501C](km0501c) | `ERROR_MessagesCannotBeFilteredForMarkdownFormat` | Messages cannot be filtered for markdown format
+[KM0501D](km0501d) | `ERROR_OutputPathMustBeSpecifiedForMarkdownFormat` | Output path must be specified with \-o for markdown output format
+[KM0501E](km0501e) | `ERROR_OutputPathMustExistAndBeADirectory` | Output path <param> must exist and must be a folder
diff --git a/developer/docs/help/reference/tools/index.md b/developer/docs/help/reference/tools/index.md
new file mode 100644
index 00000000000..2dd2d36ef82
--- /dev/null
+++ b/developer/docs/help/reference/tools/index.md
@@ -0,0 +1,7 @@
+---
+title: Keyman Developer Tools
+---
+
+* [Setup Bootstrapper](setup)
+* [kmc command line compiler](../kmc)
+* [kmconvert keyboard conversion tool](../../context/kmconvert)
diff --git a/developer/docs/help/reference/tools/setup.md b/developer/docs/help/reference/tools/setup.md
new file mode 100644
index 00000000000..d628634f064
--- /dev/null
+++ b/developer/docs/help/reference/tools/setup.md
@@ -0,0 +1,17 @@
+---
+title: Setup Bootstrapper
+---
+
+# Setup Bootstrapper
+
+The Keyman Desktop installation bootstrapper is a self-extracting executable file that contains a Windows Installer technology MSI file, and optionally keyboards. The bootstrapper is used whenever a package installer is created.
+
+The bootstrapper:
+
+1. Checks the system before starting the installation to ensure that all prerequisites are met, and optionally downloads and installs the prerequisites.
+2. Optionally checks online for an updated version of Keyman Desktop before installing.
+3. Starts the Keyman Desktop Windows Installer MSI package.
+4. Installs any keyboards included.
+5. Starts Keyman Desktop after the install completes.
+
+For command line parameters and more information see [Knowledge Base article KMKB0035](/kb/35)
diff --git a/developer/docs/help/reference/user-settings.md b/developer/docs/help/reference/user-settings.md
new file mode 100644
index 00000000000..8652c5c98bd
--- /dev/null
+++ b/developer/docs/help/reference/user-settings.md
@@ -0,0 +1,28 @@
+---
+title: Keyman Developer User Settings
+---
+
+Keyman Developer has settings stored in several locations:
+
+`~/.keymandeveloper/options.json` (`%userprofile%\.keymandeveloper\options.json` on Windows)
+
+: User preferences are stored in this file. In version 16 and earlier, these
+ options were stored in the Registry under
+ `HKCU\Software\Keyman\Keyman Developer\IDE\Options`. Any options found in
+ the Registry will be migrated to the `options.json` file on first start
+ of the Keyman Developer IDE.
+
+`HKCU\Software\Keyman\Keyman Developer\IDE`
+
+: Stores settings specific to the IDE, including window locations and
+ visibility, recently used projects, font preferences.
+
+`%appdata%\Keyman\Keyman Developer`
+
+: Stores files related to the runtime environment of Keyman Developer, including
+ cache files.
+
+`%localappdata%\Keyman\Diag`
+
+: Stores files relating to diagnostics for Keyman Developer; this folder is also
+ used for Keyman for Windows diagnostics.
diff --git a/developer/docs/help/whats-new.md b/developer/docs/help/whats-new.md
new file mode 100644
index 00000000000..d1dce092c37
--- /dev/null
+++ b/developer/docs/help/whats-new.md
@@ -0,0 +1,43 @@
+---
+title: What's new in Keyman Developer 18.0
+---
+
+Keyman Developer 17 has a number of significant changes:
+
+* `kmcomp` has been removed and replaced with `kmc`. Learn more in our [kmcomp migration guide](reference/kmc/cli/kmcomp-migration).
+
+* Windows installer packages should now be built with
+ `kmc build windows-package-installer` and can no longer be built within
+ the Keyman Developer IDE. More details on how to use this are in the
+ [`kmc` reference documentation](reference/kmc/cli/reference#toc-kmc-build-windows-package-installer-additional-options).
+
+* `.keyboard_info` files are now generated entirely from the package and
+ keyboard files. Extra fields are available in packages for license file,
+ welcome file, typing examples, related packages (including deprecated
+ packages), and additional font files. Keyboards in the Keyman Cloud
+ keyboard repository have already been updated; if you are an author of
+ one of these keyboards, you should pull the changes from the repository
+ before submitting future updates.
+
+* Keyboard project files (.kpj) now have a new format which does not list
+ individual files, but instead includes all files in the same folder and
+ subfolders relative to the project file. This prevents projects from
+ including unrelated files, and reduces the number of files that change
+ when keyboards are updated.
+
+* Keyman Developer will now open each project in a new window, allowing
+ the user to rapidly switch between multiple projects at the same time.
+
+* The Keyman Keyboard and Lexical Model Cloud repositories have new build
+ scripts which run `kmc`. These run much, much faster than previously!
+
+* The [`&displayMap`](/developer/language/reference/displaymap) store allows
+ keyboard developers to specify a font mapping to PUA for the On Screen
+ Keyboard and Touch Layout to resolve diacritic rendering issues.
+
+* Keyman Developer has been updated to support Unicode 15.1.
+
+* Additional non-printing characters have been added to the Touch Layout Editor.
+
+* Virtual keys in output of rules have never worked properly or been officially
+ supported; Keyman Developer will now warn if you attempt to use this pattern.
diff --git a/developer/src/README.md b/developer/src/README.md
index d789b63f08c..f3419c71dc3 100644
--- a/developer/src/README.md
+++ b/developer/src/README.md
@@ -90,6 +90,14 @@ node-based next generation compiler, hosts kmc, (and legacy kmlmc, kmlmp)
File analysis tools for Keyman files.
+### kmc-copy - Project copying and renaming tools
+
+Tools to copy and rename Keyman keyboard files and projects
+
+### kmc-generate - Generation tools
+
+Project generation tools for Keyman.
+
### kmc-keyboard-info - Keyboard Info Compiler
Builds .keyboard_info files for use on the Keyman Cloud keyboard repository
diff --git a/developer/src/build.sh b/developer/src/build.sh
index cde9b7fc233..dd4f5264e5b 100755
--- a/developer/src/build.sh
+++ b/developer/src/build.sh
@@ -11,6 +11,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
builder_describe \
"Keyman Developer" \
+ "@/resources/tools/check-markdown test:help" \
clean \
configure \
build \
@@ -20,8 +21,11 @@ builder_describe \
"install Install built programs locally" \
":common Developer common files" \
":ext Third party components" \
+ ":help Online documentation" \
":kmcmplib Compiler - .kmn compiler" \
":kmc-analyze Compiler - Analysis Tools" \
+ ":kmc-copy Compiler - Project Copying and Renaming Tools" \
+ ":kmc-generate Compiler - Generation Tools" \
":kmc-keyboard-info Compiler - .keyboard_info Module" \
":kmc-kmn Compiler - .kmn to .kmx and .js Keyboard Module" \
":kmc-ldml Compiler - LDML Keyboard Module" \
@@ -125,6 +129,7 @@ fi
#-------------------------------------------------------------------------------------------------------------------
builder_run_child_actions clean configure build test
+builder_run_action test:help check-markdown "$KEYMAN_ROOT/developer/docs/help"
#-------------------------------------------------------------------------------------------------------------------
diff --git a/developer/src/common/delphi/components/Keyman.Developer.UI.RichEdit41.pas b/developer/src/common/delphi/components/Keyman.Developer.UI.RichEdit41.pas
new file mode 100644
index 00000000000..086c9126769
--- /dev/null
+++ b/developer/src/common/delphi/components/Keyman.Developer.UI.RichEdit41.pas
@@ -0,0 +1,150 @@
+unit Keyman.Developer.UI.RichEdit41;
+
+interface
+
+uses
+ Vcl.Controls,
+ Vcl.ComCtrls,
+ Vcl.Themes,
+ Winapi.Windows;
+
+type
+ TRichEdit41 = class(TCustomRichEdit)
+ strict private
+ class constructor Create;
+ class destructor Destroy;
+ protected
+ procedure CreateParams(var Params: TCreateParams); override;
+ published
+ property Align;
+ property Alignment;
+ property Anchors;
+ property BevelEdges;
+ property BevelInner;
+ property BevelOuter;
+ property BevelKind default bkNone;
+ property BevelWidth;
+ property BiDiMode;
+ property BorderStyle;
+ property BorderWidth;
+ property Color;
+ property Ctl3D;
+ property DragCursor;
+ property DragKind;
+ property DragMode;
+ property Enabled;
+ property Font;
+ property HideSelection;
+ property HideScrollBars;
+ property ImeMode;
+ property ImeName;
+ property Constraints;
+ property Lines;
+ property MaxLength;
+ property ParentBiDiMode;
+ property ParentColor;
+ property ParentCtl3D;
+ property ParentFont;
+ property ParentShowHint;
+ property PlainText;
+ property PopupMenu;
+ property ReadOnly;
+ property ScrollBars;
+ property ShowHint;
+ property TabOrder;
+ property TabStop default True;
+ property Touch;
+ property Visible;
+ property WantTabs;
+ property WantReturns;
+ property WordWrap;
+ property StyleElements;
+ property Zoom;
+ property OnChange;
+ property OnClick;
+ property OnContextPopup;
+ property OnDblClick;
+ property OnDragDrop;
+ property OnDragOver;
+ property OnEndDock;
+ property OnEndDrag;
+ property OnEnter;
+ property OnExit;
+ property OnGesture;
+ property OnKeyDown;
+ property OnKeyPress;
+ property OnKeyUp;
+ property OnMouseActivate;
+ property OnMouseDown;
+ property OnMouseEnter;
+ property OnMouseLeave;
+ property OnMouseMove;
+ property OnMouseUp;
+ property OnMouseWheel;
+ property OnMouseWheelDown;
+ property OnMouseWheelUp;
+ property OnProtectChange;
+ property OnResizeRequest;
+ property OnSaveClipboard;
+ property OnSelectionChange;
+ property OnStartDock;
+ property OnStartDrag;
+ end;
+
+procedure Register;
+
+implementation
+
+uses
+ System.Classes,
+ Winapi.RichEdit;
+
+{ TRichEdit41 }
+
+class constructor TRichEdit41.Create;
+begin
+ TCustomStyleEngine.RegisterStyleHook(TRichEdit41, TRichEditStyleHook);
+end;
+
+class destructor TRichEdit41.Destroy;
+begin
+ TCustomStyleEngine.UnRegisterStyleHook(TRichEdit41, TRichEditStyleHook);
+end;
+
+var
+ FRichEditModule: THandle = 0;
+
+procedure TRichEdit41.CreateParams(var Params: TCreateParams);
+const
+ HideScrollBars: array[Boolean] of DWORD = (ES_DISABLENOSCROLL, 0);
+ HideSelections: array[Boolean] of DWORD = (ES_NOHIDESEL, 0);
+ RichEditClassName = 'RICHEDIT50W';
+ RichEditModuleName = 'MSFTEDIT.DLL';
+begin
+ if FRichEditModule = 0 then
+ begin
+ FRichEditModule := LoadLibrary(RichEditModuleName);
+ if FRichEditModule <= HINSTANCE_ERROR then FRichEditModule := 0;
+ end;
+
+ inherited CreateParams(Params);
+
+ CreateSubClass(Params, RichEditClassName);
+
+ with Params do
+ begin
+ Style := Style or HideScrollBars[Self.HideScrollBars] or
+ HideSelections[HideSelection];
+ end;
+end;
+
+procedure Register;
+begin
+ RegisterComponents('Keyman', [TRichEdit41]);
+end;
+
+initialization
+finalization
+ if FRichEditModule <> 0 then FreeLibrary(FRichEditModule);
+end.
+
diff --git a/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas b/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas
index fa062e68628..3cd9397b212 100644
--- a/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas
+++ b/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas
@@ -1,18 +1,18 @@
(*
Name: KeymanDeveloperDebuggerMemo
Copyright: Copyright (C) 2003-2017 SIL International.
- Documentation:
- Description:
+ Documentation:
+ Description:
Create Date: 8 Jun 2012
Modified Date: 8 Jun 2012
Authors: mcdurdin
- Related Files:
- Dependencies:
+ Related Files:
+ Dependencies:
- Bugs:
- Todo:
- Notes:
+ Bugs:
+ Todo:
+ Notes:
History: 08 Jun 2012 - mcdurdin - I3323 - V9.0 - Extract debug-related code TPlus-Memo into subclass
*)
unit KeymanDeveloperDebuggerMemo; // I3323
@@ -22,9 +22,13 @@ interface
uses
System.Classes,
Winapi.Messages,
+ Winapi.RichEdit,
Winapi.Windows,
Vcl.Controls,
- Vcl.StdCtrls;
+ Vcl.ComCtrls,
+ Vcl.StdCtrls,
+
+ Keyman.Developer.UI.RichEdit41;
type
TKeymanDeveloperDebuggerMessageEvent = procedure(Sender: TObject; var Message: TMessage; var Handled: Boolean) of object;
@@ -34,10 +38,11 @@ TMemoSelection = record
Anchor: Integer;
end;
- TKeymanDeveloperDebuggerMemo = class(TMemo)
+ TKeymanDeveloperDebuggerMemo = class(TRichEdit41)
private
FOnMessage: TKeymanDeveloperDebuggerMessageEvent;
FAllowUnicodeInput: Boolean;
+ FSelectionChanging: Boolean;
FIsDebugging: Boolean;
procedure SetAllowUnicode(const Value: Boolean);
function GetSelection: TMemoSelection;
@@ -52,6 +57,7 @@ TKeymanDeveloperDebuggerMemo = class(TMemo)
property AllowUnicode: Boolean read FAllowUnicodeInput write SetAllowUnicode default True;
property OnMessage: TKeymanDeveloperDebuggerMessageEvent read FOnMessage write FOnMessage;
property Selection: TMemoSelection read GetSelection write SetSelection;
+ property SelectionChanging: Boolean read FSelectionChanging;
property IsDebugging: Boolean read FIsDebugging write FIsDebugging;
end;
@@ -77,14 +83,21 @@ constructor TKeymanDeveloperDebuggerMemo.Create(AOwner: TComponent);
begin
FAllowUnicodeInput := True;
inherited Create(AOwner);
+ PlainText := True;
end;
procedure TKeymanDeveloperDebuggerMemo.CreateHandle;
+const
+ TO_ADVANCEDTYPOGRAPHY = 1;
begin
inherited;
if FAllowUnicodeInput
then SetWindowLongW(Handle, GWL_WNDPROC, GetWindowLong(Handle, GWL_WNDPROC))
else SetWindowLongA(Handle, GWL_WNDPROC, GetWindowLong(Handle, GWL_WNDPROC));
+ SendMessage(Handle, WM_SETTEXT, 0, NativeUInt(PChar('')));
+ SendMessage(Handle, EM_SETTEXTMODE, TM_PLAINTEXT or TM_MULTICODEPAGE, 0);
+ SendMessage(Handle, EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
+ SendMessage(Handle, EM_SETLANGOPTIONS, 0, $0040 {IMF_NOIMPLICITLANG} or $0200 {IMF_NOKBDLIDFIXUP});
end;
function TKeymanDeveloperDebuggerMemo.GetSelection: TMemoSelection;
@@ -93,9 +106,21 @@ function TKeymanDeveloperDebuggerMemo.GetSelection: TMemoSelection;
// it out with this kludge. I am not aware of side effects from this
// at this time.
SendMessage(Handle, EM_GETSEL, NativeUInt(@Result.Start), NativeUInt(@Result.Finish));
- SendMessage(Handle, EM_SETSEL, -1, 0);
- SendMessage(Handle, EM_GETSEL, NativeUInt(@Result.Anchor), 0);
- SetSelection(Result);
+ if Result.Start <> Result.Finish then
+ begin
+ // We only need to play the selection test game if there is a non-zero
+ // selection length
+ FSelectionChanging := True;
+ Lines.BeginUpdate;
+ try
+ SendMessage(Handle, EM_SETSEL, -1, 0);
+ SendMessage(Handle, EM_GETSEL, NativeUInt(@Result.Anchor), 0);
+ SetSelection(Result);
+ finally
+ Lines.EndUpdate;
+ FSelectionChanging := False;
+ end;
+ end;
end;
procedure TKeymanDeveloperDebuggerMemo.SetAllowUnicode(const Value: Boolean);
diff --git a/developer/src/common/include/kmn_compiler_errors.h b/developer/src/common/include/kmn_compiler_errors.h
index 4955df5e738..f2410f03b06 100644
--- a/developer/src/common/include/kmn_compiler_errors.h
+++ b/developer/src/common/include/kmn_compiler_errors.h
@@ -90,16 +90,16 @@ namespace KmnCompilerMessages {
ERROR_ZeroLengthString = SevError | 0x017,
ERROR_TooManyIndexToKeyRefs = SevError | 0x018,
ERROR_UnterminatedString = SevError | 0x019,
- ERROR_StringInVirtualKeySection = SevError | 0x01A,
- ERROR_AnyInVirtualKeySection = SevError | 0x01B,
+// ERROR_StringInVirtualKeySection = SevError | 0x01A, no longer used, see #12612
+// ERROR_AnyInVirtualKeySection = SevError | 0x01B, no longer used, see #12612
ERROR_InvalidAny = SevError | 0x01C,
ERROR_StoreDoesNotExist = SevError | 0x01D,
- ERROR_BeepInVirtualKeySection = SevError | 0x01E,
- ERROR_IndexInVirtualKeySection = SevError | 0x01F,
+// ERROR_BeepInVirtualKeySection = SevError | 0x01E, no longer used, see #12612
+// ERROR_IndexInVirtualKeySection = SevError | 0x01F, no longer used, see #12612
ERROR_InvalidIndex = SevError | 0x020,
- ERROR_OutsInVirtualKeySection = SevError | 0x021,
+// ERROR_OutsInVirtualKeySection = SevError | 0x021, no longer used, see #12612
ERROR_InvalidOuts = SevError | 0x022,
- ERROR_ContextInVirtualKeySection = SevError | 0x024,
+// ERROR_ContextInVirtualKeySection = SevError | 0x024, no longer used, see #12612
ERROR_InvalidUse = SevError | 0x025,
ERROR_GroupDoesNotExist = SevError | 0x026,
ERROR_VirtualKeyNotAllowedHere = SevError | 0x027,
@@ -117,7 +117,7 @@ namespace KmnCompilerMessages {
ERROR_ReservedCharacter = SevError | 0x033,
ERROR_InvalidCharacter = SevError | 0x034,
ERROR_InvalidCall = SevError | 0x035,
- ERROR_CallInVirtualKeySection = SevError | 0x036,
+// ERROR_CallInVirtualKeySection = SevError | 0x036, no longer used, see #12612
ERROR_CodeInvalidInKeyStore = SevError | 0x037,
ERROR_CannotLoadIncludeFile = SevError | 0x038,
@@ -138,7 +138,7 @@ namespace KmnCompilerMessages {
ERROR_70FeatureOnly = SevError | 0x046,
ERROR_80FeatureOnly = SevError | 0x047,
- ERROR_InvalidInVirtualKeySection = SevError | 0x048,
+// ERROR_InvalidInVirtualKeySection = SevError | 0x048, no longer used, see #12612
ERROR_InvalidIf = SevError | 0x049,
ERROR_InvalidReset = SevError | 0x04A,
ERROR_InvalidSet = SevError | 0x04B,
diff --git a/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts
new file mode 100644
index 00000000000..a0b0464e218
--- /dev/null
+++ b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts
@@ -0,0 +1,168 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks,
+ CompilerError, CompilerNetAsyncCallbacks, DefaultCompilerFileSystemAsyncCallbacks,
+ CompilerFileSystemAsyncCallbacks } from '@keymanapp/developer-utils';
+import { fileURLToPath } from 'url';
+
+const { TEST_SAVE_FIXTURES } = process.env;
+
+/**
+ * A CompilerCallbacks implementation for testing
+ */
+export class TestCompilerCallbacks implements CompilerCallbacks {
+ /* TestCompilerCallbacks */
+
+ messages: CompilerEvent[] = [];
+ readonly _net: TestCompilerNetAsyncCallbacks;
+ readonly _fsAsync: DefaultCompilerFileSystemAsyncCallbacks = new DefaultCompilerFileSystemAsyncCallbacks(this);
+
+ constructor(basePath?: string) {
+ if(basePath) {
+ this._net = new TestCompilerNetAsyncCallbacks(basePath);
+ }
+ }
+
+ clear() {
+ this.messages = [];
+ }
+
+ printMessages() {
+ if(this.messages.length) {
+ process.stdout.write(CompilerError.formatEvent(this.messages));
+ }
+ }
+
+ hasMessage(code: number): boolean {
+ return this.messages.find((item) => item.code == code) === undefined ? false : true;
+ }
+
+ fileURLToPath(url: string | URL): string {
+ return fileURLToPath(url);
+ }
+
+ /** true of at least one error */
+ hasError(): boolean {
+ return CompilerError.hasError(this.messages);
+ }
+
+ /* CompilerCallbacks */
+
+ loadFile(filename: string): Uint8Array {
+ try {
+ return fs.readFileSync(filename);
+ } catch(e) {
+ if (e.code === 'ENOENT') {
+ return null;
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ fileSize(filename: string): number {
+ return fs.statSync(filename)?.size;
+ }
+
+ isDirectory(filename: string): boolean {
+ return fs.statSync(filename)?.isDirectory();
+ }
+
+ get path(): CompilerPathCallbacks {
+ return {
+ ...path,
+ isAbsolute: path.win32.isAbsolute
+ };
+ }
+
+ get fs(): CompilerFileSystemCallbacks {
+ return fs;
+ }
+
+ get net(): CompilerNetAsyncCallbacks {
+ return this._net;
+ }
+
+ get fsAsync(): CompilerFileSystemAsyncCallbacks {
+ return this._fsAsync;
+ }
+
+ resolveFilename(baseFilename: string, filename: string): string {
+ const basePath =
+ baseFilename.endsWith('/') || baseFilename.endsWith('\\') ?
+ baseFilename :
+ path.dirname(baseFilename);
+ // Transform separators to platform separators -- we are agnostic
+ // in our use here but path prefers files may use
+ // either / or \, although older kps files were always \.
+ if(path.sep == '/') {
+ filename = filename.replace(/\\/g, '/');
+ } else {
+ filename = filename.replace(/\//g, '\\');
+ }
+ if(!path.isAbsolute(filename)) {
+ filename = path.resolve(basePath, filename);
+ }
+ return filename;
+ }
+
+ reportMessage(event: CompilerEvent): void {
+ // console.log(event.message);
+ this.messages.push(event);
+ }
+
+ debug(msg: string) {
+ console.debug(msg);
+ }
+};
+
+class TestCompilerNetAsyncCallbacks implements CompilerNetAsyncCallbacks {
+ constructor(private basePath: string) {
+ }
+
+ urlToPath(url: string): string {
+ const p = new URL(url);
+ return path.join(this.basePath, p.hostname, p.pathname.replaceAll(/[^a-z0-9_.!@#$%() -]/ig, '#'));
+ }
+
+ async fetchBlob(url: string): Promise {
+ const p = this.urlToPath(url);
+
+ if(TEST_SAVE_FIXTURES) {
+ // When TEST_SAVE_FIXTURES env variable is set, we will do the actual
+ // fetch from the origin so that we can build the fixtures easily
+ console.log(`Downloading file ${url} --> ${p}`);
+ let response: Response;
+ try {
+ response = await fetch(url);
+ } catch(e) {
+ console.error(`failed to download ${url}`);
+ console.error(e);
+ throw e; // yes, we want to abort the download
+ }
+
+ fs.mkdirSync(path.dirname(p), {recursive: true});
+ if(!response.ok) {
+ // We won't save a file, just delete any existing file
+ if(fs.existsSync(p)) {
+ fs.rmSync(p);
+ }
+ } else {
+ const data = new Uint8Array(await response.arrayBuffer());
+ fs.writeFileSync(p, data);
+ }
+ }
+
+ if(!fs.existsSync(p)) {
+ // missing file, this is okay
+ return null;
+ }
+ const data: Uint8Array = fs.readFileSync(p);
+ return data;
+ }
+
+ async fetchJSON(url: string): Promise {
+ const data = await this.fetchBlob(url);
+ return data ? JSON.parse(new TextDecoder().decode(data)) : null;
+ }
+}
diff --git a/developer/src/common/web/test-helpers/index.ts b/developer/src/common/web/test-helpers/index.ts
index 93dfb89b2cc..6970ed3cc80 100644
--- a/developer/src/common/web/test-helpers/index.ts
+++ b/developer/src/common/web/test-helpers/index.ts
@@ -1,86 +1,2 @@
-import * as fs from 'fs';
-import * as path from 'path';
-import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerError } from '@keymanapp/developer-utils';
export { verifyCompilerMessagesObject } from './verifyCompilerMessagesObject.js';
-
-/**
- * A CompilerCallbacks implementation for testing
- */
-export class TestCompilerCallbacks implements CompilerCallbacks {
- /* TestCompilerCallbacks */
-
- messages: CompilerEvent[] = [];
-
- clear() {
- this.messages = [];
- }
-
- printMessages() {
- if(this.messages.length) {
- process.stdout.write(CompilerError.formatEvent(this.messages));
- }
- }
-
- hasMessage(code: number): boolean {
- return this.messages.find((item) => item.code == code) === undefined ? false : true;
- }
-
- /** true of at least one error */
- hasError(): boolean {
- return CompilerError.hasError(this.messages);
- }
-
- /* CompilerCallbacks */
-
- loadFile(filename: string): Uint8Array {
- try {
- return fs.readFileSync(filename);
- } catch(e) {
- if (e.code === 'ENOENT') {
- return null;
- } else {
- throw e;
- }
- }
- }
-
- fileSize(filename: string): number {
- return fs.statSync(filename)?.size;
- }
-
- get path(): CompilerPathCallbacks {
- return path;
- }
-
- get fs(): CompilerFileSystemCallbacks {
- return fs;
- }
-
- resolveFilename(baseFilename: string, filename: string): string {
- const basePath =
- baseFilename.endsWith('/') || baseFilename.endsWith('\\') ?
- baseFilename :
- path.dirname(baseFilename);
- // Transform separators to platform separators -- we are agnostic
- // in our use here but path prefers files may use
- // either / or \, although older kps files were always \.
- if(path.sep == '/') {
- filename = filename.replace(/\\/g, '/');
- } else {
- filename = filename.replace(/\//g, '\\');
- }
- if(!path.isAbsolute(filename)) {
- filename = path.resolve(basePath, filename);
- }
- return filename;
- }
-
- reportMessage(event: CompilerEvent): void {
- // console.log(event.message);
- this.messages.push(event);
- }
-
- debug(msg: string) {
- console.debug(msg);
- }
-};
+export { TestCompilerCallbacks } from './TestCompilerCallbacks.js';
diff --git a/developer/src/common/web/utils/build.sh b/developer/src/common/web/utils/build.sh
index 2209463e184..f7433cb2faf 100755
--- a/developer/src/common/web/utils/build.sh
+++ b/developer/src/common/web/utils/build.sh
@@ -49,15 +49,5 @@ function do_build() {
builder_run_action clean rm -rf ./build/
builder_run_action configure verify_npm_setup
builder_run_action build do_build
-
-if builder_start_action test; then
- eslint .
- tsc --build test
- readonly C8_THRESHOLD=40
- c8 --reporter=lcov --reporter=text --exclude-after-remap --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha
- builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal."
- builder_echo warning "Please increase threshold in build.sh as test coverage improves."
- builder_finish_action success test
-fi
-
-builder_run_action publish builder_publish_npm
+builder_run_action test builder_do_typescript_tests 50
+builder_run_action publish builder_publish_npm
diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json
index 1ab9daddbf8..4f066313f6d 100644
--- a/developer/src/common/web/utils/package.json
+++ b/developer/src/common/web/utils/package.json
@@ -9,19 +9,21 @@
"/build/"
],
"dependencies": {
- "@sentry/node": "^7.57.0",
"@keymanapp/common-types": "*",
+ "@sentry/node": "^7.57.0",
"eventemitter3": "^5.0.0",
+ "fast-xml-parser": "^4.5.0",
+ "path-browserify": "^1.0.1",
"restructure": "^3.0.1",
- "semver": "^7.5.4",
"sax": ">=0.6.0",
+ "semver": "^7.5.4",
"xmlbuilder": "~11.0.0"
},
"devDependencies": {
"@types/git-diff": "^2.0.3",
"@types/node": "^20.4.1",
+ "@types/path-browserify": "^1.0.3",
"@types/semver": "^7.3.12",
- "@types/xml2js": "^0.4.5",
"c8": "^7.12.0",
"git-diff": "^2.0.6",
"mocha": "^8.4.0",
@@ -31,7 +33,7 @@
"build": "tsc -b"
},
"mocha": {
- "spec": "build/test/**/test-*.js",
+ "spec": "build/test/**/*.tests.js",
"require": [
"source-map-support/register"
]
diff --git a/developer/src/common/web/utils/src/cloud-urls.ts b/developer/src/common/web/utils/src/cloud-urls.ts
new file mode 100644
index 00000000000..33f9c669b9b
--- /dev/null
+++ b/developer/src/common/web/utils/src/cloud-urls.ts
@@ -0,0 +1,18 @@
+/**
+ * Matches a Keyman keyboard resource, based on the permanent home page for the
+ * keyboard on keyman.com, `https://keyman.com/keyboards/`
+ */
+export const KEYMANCOM_CLOUD_URI = /^(?:http(?:s)?:\/\/)?keyman\.com\/keyboards\/(?[a-z0-9_.-]+)/i;
+
+/**
+ * Matches a `cloud:` URI for a Keyman resource (e.g. keyboard or lexical
+ * model)
+ */
+export const CLOUD_URI = /^cloud:(?.+)$/i;
+
+
+export interface CloudUriRegexMatchArray extends RegExpMatchArray {
+ groups?: {
+ id?: string;
+ }
+}
\ No newline at end of file
diff --git a/developer/src/common/web/utils/src/common-messages.ts b/developer/src/common/web/utils/src/common-messages.ts
index 5b3fd7d5070..6eca08d37c4 100644
--- a/developer/src/common/web/utils/src/common-messages.ts
+++ b/developer/src/common/web/utils/src/common-messages.ts
@@ -1,5 +1,5 @@
/*
- * Keyman is copyright (C) SIL International. MIT License.
+ * Keyman is copyright (C) SIL Global. MIT License.
*/
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m } from './compiler-interfaces.js';
import { constants } from '@keymanapp/ldml-keyboard-constants';
@@ -20,17 +20,19 @@ export class CommonTypesMessages {
static ERROR_ImportInvalidBase = SevError | 0x0002;
static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) =>
m(this.ERROR_ImportInvalidBase,
- `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} is supported.`);
+ `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} or empty (for local) are supported.`);
static ERROR_ImportInvalidPath = SevError | 0x0003;
static Error_ImportInvalidPath = (o: { base: string, path: string, subtag: string }) =>
m(this.ERROR_ImportInvalidPath,
- `Import element with invalid path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml`);
+ `Import element with invalid path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml'`);
static ERROR_ImportReadFail = SevError | 0x0004;
static Error_ImportReadFail = (o: { base: string, path: string, subtag: string }) =>
m(this.ERROR_ImportReadFail,
- `Import could not read data with path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml'`);
+ `Import could not read data with path ${def(o.path)}`,
+ // for CLDR, give guidance on the suggested path
+ (o.base === constants.cldr_import_base) ? `expected the form '${constants.cldr_version_latest}/*.xml' for ${o.base}` : undefined);
static ERROR_ImportWrongRoot = SevError | 0x0005;
static Error_ImportWrongRoot = (o: { base: string, path: string, subtag: string }) =>
@@ -50,4 +52,10 @@ export class CommonTypesMessages {
static ERROR_InvalidXml = SevError | 0x0008;
static Error_InvalidXml = (o:{e: any}) =>
m(this.ERROR_InvalidXml, `The XML file could not be read: ${(o.e ?? '').toString()}`);
+
+ static ERROR_InvalidPackageFile = SevError | 0x0009;
+ static Error_InvalidPackageFile = (o:{e:any}) => m(
+ this.ERROR_InvalidPackageFile,
+ `Package source file is invalid: ${(o.e ?? 'unknown error').toString()}`
+ );
};
diff --git a/developer/src/common/web/utils/src/compiler-callbacks.ts b/developer/src/common/web/utils/src/compiler-callbacks.ts
new file mode 100644
index 00000000000..a7163175e8c
--- /dev/null
+++ b/developer/src/common/web/utils/src/compiler-callbacks.ts
@@ -0,0 +1,244 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * Callbacks for Keyman Developer compilers to ensure independence from
+ * filesystem, network, and console.
+ */
+
+import { CompilerEvent, CompilerCallbackOptions, CompilerErrorSeverity, CompilerError, CompilerMessageOverrideMap, CompilerErrorMask } from "./compiler-interfaces.js";
+
+/**
+ * Abstract interface for callbacks, to abstract out file i/o
+ */
+
+export interface CompilerCallbacks {
+ /**
+ * Attempt to load a file. Return falsy if not found.
+ * @param filename
+ */
+ loadFile(filename: string): Uint8Array;
+
+ /**
+ * Get file size, returns undefined if not found
+ */
+ fileSize(filename: string): number;
+
+ /**
+ * Returns true if file is a directory, undefined if not found
+ */
+ isDirectory(filename: string): boolean;
+
+ get path(): CompilerPathCallbacks;
+ get fs(): CompilerFileSystemCallbacks
+ get fsAsync(): CompilerFileSystemAsyncCallbacks;
+ get net(): CompilerNetAsyncCallbacks;
+
+ /**
+ * Resolves a file path relative to the baseFilename
+ * @param baseFilename
+ * @param filename
+ */
+ resolveFilename(baseFilename: string, filename: string): string;
+
+ reportMessage(event: CompilerEvent): void;
+
+ debug(msg: string): void;
+
+ fileURLToPath(url: string | URL): string;
+};
+
+/**
+ * A mapping for common path operations, maps to Node path module. This only
+ * defines the functions we are actually using, so that we can port more easily
+ * between different systems.
+ */
+export interface CompilerPathCallbacks {
+ dirname(name: string): string;
+ extname(name: string): string;
+ basename(name: string, ext?: string): string;
+ isAbsolute(name: string): boolean;
+ join(...paths: string[]): string;
+ normalize(p: string): string;
+ relative(from: string, to: string): string;
+ resolve(...args: string[]): string;
+}
+
+/**
+ * A mapping for common filesystem operations, maps to Node fs module. This only
+ * defines the functions we are actually using, so that we can port more easily
+ * between different systems.
+ */
+export interface CompilerFileSystemCallbacks {
+ readdirSync(name: string): string[];
+ readFileSync(path: string, options?: { encoding?: null; flag?: string; } | null): Uint8Array;
+ readFileSync(path: string, options: { encoding: string; flag?: string; } | string): string;
+ readFileSync(path: string, options?: { encoding?: string | null; flag?: string; } | string | null): string | Uint8Array;
+ writeFileSync(path: string, data: Uint8Array): void;
+ mkdirSync(path: string, options?: { recursive?: boolean; }): string;
+ existsSync(name: string): boolean;
+}
+
+export interface CompilerAsyncCallbacks {
+ // TODO: readonly log: CompilerLogCallbacks;
+ readonly fsAsync: CompilerFileSystemAsyncCallbacks;
+ readonly path: CompilerPathCallbacks;
+ readonly net: CompilerNetAsyncCallbacks;
+}
+
+export interface CompilerFileSystemCallbacksFolderEntry {
+ filename: string;
+ type: 'file' | 'dir';
+}
+
+export interface CompilerNetAsyncCallbacks {
+ fetchJSON(url: string): Promise;
+ fetchBlob(url: string): Promise;
+}
+
+export interface CompilerFileSystemAsyncCallbacks {
+ readFile(filename: string): Promise;
+ //fileSize(filename: string): Promise;
+ //isDirectory(filename: string): Promise;
+ readdir(filename: string): Promise;
+ // writeFile(filename: string, data: Uint8Array): Promise;
+ // mkdir(path: string, options?: {recursive?: boolean}): Promise;
+ exists(filename: string): Promise;
+ resolveFilename(baseFilename: string, filename: string): string;
+}
+
+
+/**
+ * Wrapper class for CompilerCallbacks for a given input file
+ */
+export class CompilerFileCallbacks implements CompilerCallbacks {
+ messages: CompilerEvent[] = [];
+
+ constructor(private filename: string, private options: CompilerCallbackOptions, private parent: CompilerCallbacks) {
+ }
+
+ /**
+ * Returns `true` if any message in the `messages` array is a Fatal or Error
+ * message, and if `compilerWarningsAsErrors` is `true`, then also returns
+ * `true` if any message is a Warning.
+ */
+ static hasFailureMessage(messages: CompilerEvent[], compilerWarningsAsErrors: boolean) {
+ const failureCodes = [
+ CompilerErrorSeverity.Fatal, CompilerErrorSeverity.Error
+ ].concat(compilerWarningsAsErrors ? [CompilerErrorSeverity.Warn] : []);
+ return messages.find(m => failureCodes.includes(CompilerError.severity(m.code))) != undefined;
+ }
+
+ /**
+ *
+ * @param event
+ * @param overrides
+ * @returns true if event has been suppressed
+ */
+ static applyMessageOverridesToEvent(event: CompilerEvent, overrides: CompilerMessageOverrideMap) {
+ // Override event severity from user preference -- this will not override
+ // fatal or error events
+ const severity = overrides?.[CompilerError.error(event.code)] ??
+ CompilerError.severity(event.code);
+
+ if (severity == 'disable') {
+ return true;
+ }
+
+ // Override the default event severity with the command line option
+ event.code = severity | (event.code & ~CompilerErrorMask.Severity);
+
+ return false;
+ }
+
+ /**
+ * Returns `true` if any message in the `messages` array is a Fatal or Error
+ * message, and if `compilerWarningsAsErrors` is `true`, then also returns
+ * `true` if any message is a Warning.
+ *
+ * If passed a defined `compilerWarningsAsErrors` value, then uses that,
+ * otherwise uses `options.compilerWarningsAsErrors`, or `false` if that is
+ * also `undefined`.
+ */
+ hasFailureMessage(compilerWarningsAsErrors?: boolean) {
+ return CompilerFileCallbacks.hasFailureMessage(
+ this.messages,
+ compilerWarningsAsErrors ?? this.options.compilerWarningsAsErrors ?? false
+ );
+ }
+
+ clear() {
+ this.messages = [];
+ }
+
+ loadFile(filename: string): Uint8Array {
+ return this.parent.loadFile(filename);
+ }
+
+ fileSize(filename: string): number {
+ return this.parent.fileSize(filename);
+ }
+
+ isDirectory(filename: string): boolean {
+ return this.parent.isDirectory(filename);
+ }
+
+ get path(): CompilerPathCallbacks {
+ return this.parent.path;
+ }
+
+ get fs(): CompilerFileSystemCallbacks {
+ return this.parent.fs;
+ }
+
+ get net(): CompilerNetAsyncCallbacks {
+ return this.parent.net;
+ }
+
+ get fsAsync(): CompilerFileSystemAsyncCallbacks {
+ return this.parent.fsAsync;
+ }
+
+ resolveFilename(baseFilename: string, filename: string): string {
+ return this.parent.resolveFilename(baseFilename, filename);
+ }
+
+ reportMessage(event: CompilerEvent): void {
+ const disable = CompilerFileCallbacks.applyMessageOverridesToEvent(event, this.options.messageOverrides);
+ this.messages.push(event);
+ if (!disable) {
+ this.parent.reportMessage({ filename: this.filename, ...event });
+ }
+ }
+
+ debug(msg: string): void {
+ return this.parent.debug(msg);
+ }
+
+ fileURLToPath(url: string | URL): string {
+ return this.parent.fileURLToPath(url);
+ }
+}
+
+export class DefaultCompilerFileSystemAsyncCallbacks implements CompilerFileSystemAsyncCallbacks {
+ constructor(private owner: CompilerCallbacks) {
+
+ }
+ async exists(filename: string): Promise {
+ return this.owner.fs.existsSync(filename);
+ }
+
+ async readFile(filename: string): Promise {
+ return this.owner.loadFile(filename);
+ }
+
+ async readdir(filename: string): Promise {
+ return this.owner.fs.readdirSync(filename).map(item => ({
+ filename: item,
+ type: this.owner.isDirectory(this.owner.path.join(filename, item)) ? 'dir' : 'file'
+ }));
+ }
+
+ resolveFilename(baseFilename: string, filename: string): string {
+ return this.owner.resolveFilename(baseFilename, filename);
+ }
+}
diff --git a/developer/src/common/web/utils/src/compiler-interfaces.ts b/developer/src/common/web/utils/src/compiler-interfaces.ts
index e4ed62d7500..0ffc828f1dd 100644
--- a/developer/src/common/web/utils/src/compiler-interfaces.ts
+++ b/developer/src/common/web/utils/src/compiler-interfaces.ts
@@ -1,3 +1,5 @@
+import { CompilerCallbacks } from "./compiler-callbacks.js";
+
/**
* Abstract interface for compiler error and warning messages
*/
@@ -263,37 +265,16 @@ export enum CompilerErrorNamespace {
* kmc-keyboard-info 0x9000…0x9FFF
*/
KeyboardInfoCompiler = 0x9000,
+ /**
+ * kmc-generate 0xA000…0xAFFF
+ */
+ Generator = 0xA000,
+ /**
+ * kmc-copy 0xB000…0xBFFF
+ */
+ Copier = 0xB000,
};
-/**
- * A mapping for common path operations, maps to Node path module. This only
- * defines the functions we are actually using, so that we can port more easily
- * between different systems.
- */
-export interface CompilerPathCallbacks {
- dirname(name: string): string;
- extname(name: string): string;
- basename(name: string, ext?: string): string;
- isAbsolute(name: string): boolean;
- join(...paths: string[]): string;
- normalize(p: string): string;
-}
-
-/**
- * A mapping for common filesystem operations, maps to Node fs module. This only
- * defines the functions we are actually using, so that we can port more easily
- * between different systems.
- */
-export interface CompilerFileSystemCallbacks {
- readdirSync(name: string): string[];
- readFileSync(path: string, options?: { encoding?: null; flag?: string; } | null): Uint8Array;
- readFileSync(path: string, options: { encoding: string; flag?: string; } | string): string;
- readFileSync(path: string, options?: { encoding?: string | null; flag?: string; } | string | null): string | Uint8Array;
- writeFileSync(path: string, data: Uint8Array): void;
-
- existsSync(name: string): boolean;
-}
-
type CompilerErrorSeverityOverride = CompilerErrorSeverity | 'disable';
export interface CompilerMessageOverrideMap {
[code:number]: CompilerErrorSeverityOverride;
@@ -346,133 +327,6 @@ export interface KeymanCompiler {
write(artifacts: KeymanCompilerArtifacts): Promise;
};
-/**
- * Abstract interface for callbacks, to abstract out file i/o
- */
-export interface CompilerCallbacks {
- /**
- * Attempt to load a file. Return falsy if not found.
- * TODO: never return falsy, just throw if not found?
- * @param filename
- */
- loadFile(filename: string): Uint8Array;
-
- /**
- * Get file size, returns undefined if not found
- */
- fileSize(filename: string): number;
-
- get path(): CompilerPathCallbacks;
- get fs(): CompilerFileSystemCallbacks;
-
- /**
- * Resolves a file path relative to the baseFilename
- * @param baseFilename
- * @param filename
- */
- resolveFilename(baseFilename: string, filename: string): string;
-
- reportMessage(event: CompilerEvent): void;
-
- debug(msg: string): void;
-};
-
-/**
- * Wrapper class for CompilerCallbacks for a given input file
- */
-export class CompilerFileCallbacks implements CompilerCallbacks {
- messages: CompilerEvent[] = [];
-
- constructor(private filename: string, private options: CompilerCallbackOptions, private parent: CompilerCallbacks) {
- }
-
- /**
- * Returns `true` if any message in the `messages` array is a Fatal or Error
- * message, and if `compilerWarningsAsErrors` is `true`, then also returns
- * `true` if any message is a Warning.
- */
- static hasFailureMessage(messages: CompilerEvent[], compilerWarningsAsErrors: boolean) {
- const failureCodes = [
- CompilerErrorSeverity.Fatal, CompilerErrorSeverity.Error
- ].concat(compilerWarningsAsErrors ? [CompilerErrorSeverity.Warn] : []);
- return messages.find(m => failureCodes.includes(CompilerError.severity(m.code))) != undefined;
- }
-
- /**
- *
- * @param event
- * @param overrides
- * @returns true if event has been suppressed
- */
- static applyMessageOverridesToEvent(event: CompilerEvent, overrides: CompilerMessageOverrideMap) {
- // Override event severity from user preference -- this will not override
- // fatal or error events
- const severity = overrides?.[CompilerError.error(event.code)] ??
- CompilerError.severity(event.code);
-
- if(severity == 'disable') {
- return true;
- }
-
- // Override the default event severity with the command line option
- event.code = severity | (event.code & ~CompilerErrorMask.Severity);
-
- return false;
- }
-
- /**
- * Returns `true` if any message in the `messages` array is a Fatal or Error
- * message, and if `compilerWarningsAsErrors` is `true`, then also returns
- * `true` if any message is a Warning.
- *
- * If passed a defined `compilerWarningsAsErrors` value, then uses that,
- * otherwise uses `options.compilerWarningsAsErrors`, or `false` if that is
- * also `undefined`.
- */
- hasFailureMessage(compilerWarningsAsErrors?: boolean) {
- return CompilerFileCallbacks.hasFailureMessage(
- this.messages,
- compilerWarningsAsErrors ?? this.options.compilerWarningsAsErrors ?? false
- );
- }
-
- clear() {
- this.messages = [];
- }
-
- loadFile(filename: string): Uint8Array {
- return this.parent.loadFile(filename);
- }
-
- fileSize(filename: string): number {
- return this.parent.fileSize(filename);
- }
-
- get path(): CompilerPathCallbacks {
- return this.parent.path;
- }
-
- get fs(): CompilerFileSystemCallbacks {
- return this.parent.fs;
- }
-
- resolveFilename(baseFilename: string, filename: string): string {
- return this.parent.resolveFilename(baseFilename, filename);
- }
-
- reportMessage(event: CompilerEvent): void {
- const disable = CompilerFileCallbacks.applyMessageOverridesToEvent(event, this.options.messageOverrides);
- this.messages.push(event);
- if(!disable) {
- this.parent.reportMessage({filename: this.filename, ...event});
- }
- }
-
- debug(msg: string): void {
- return this.parent.debug(msg);
- }
-}
-
/**
* Abstract interface for compiler options
*/
diff --git a/developer/src/common/web/utils/src/deps/xml2js/README.md b/developer/src/common/web/utils/src/deps/xml2js/README.md
deleted file mode 100644
index 67f2104a513..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/README.md
+++ /dev/null
@@ -1,507 +0,0 @@
-node-xml2js
-===========
-
-Ever had the urge to parse XML? And wanted to access the data in some sane,
-easy way? Don't want to compile a C parser, for whatever reason? Then xml2js is
-what you're looking for!
-
-Description
-===========
-
-Simple XML to JavaScript object converter. It supports bi-directional conversion.
-Uses [sax-js](https://github.com/isaacs/sax-js/) and
-[xmlbuilder-js](https://github.com/oozcitak/xmlbuilder-js/).
-
-Note: If you're looking for a full DOM parser, you probably want
-[JSDom](https://github.com/tmpvar/jsdom).
-
-Installation
-============
-
-Simplest way to install `xml2js` is to use [npm](http://npmjs.org), just `npm
-install xml2js` which will download xml2js and all dependencies.
-
-xml2js is also available via [Bower](http://bower.io/), just `bower install
-xml2js` which will download xml2js and all dependencies.
-
-Usage
-=====
-
-No extensive tutorials required because you are a smart developer! The task of
-parsing XML should be an easy one, so let's make it so! Here's some examples.
-
-Shoot-and-forget usage
-----------------------
-
-You want to parse XML as simple and easy as possible? It's dangerous to go
-alone, take this:
-
-```javascript
-var parseString = require('xml2js').parseString;
-var xml = "Hello xml2js! "
-parseString(xml, function (err, result) {
- console.dir(result);
-});
-```
-
-Can't get easier than this, right? This works starting with `xml2js` 0.2.3.
-With CoffeeScript it looks like this:
-
-```coffeescript
-{parseString} = require 'xml2js'
-xml = "Hello xml2js! "
-parseString xml, (err, result) ->
- console.dir result
-```
-
-If you need some special options, fear not, `xml2js` supports a number of
-options (see below), you can specify these as second argument:
-
-```javascript
-parseString(xml, {trim: true}, function (err, result) {
-});
-```
-
-Simple as pie usage
--------------------
-
-That's right, if you have been using xml-simple or a home-grown
-wrapper, this was added in 0.1.11 just for you:
-
-```javascript
-var fs = require('fs'),
- xml2js = require('xml2js');
-
-var parser = new xml2js.Parser();
-fs.readFile(__dirname + '/foo.xml', function(err, data) {
- parser.parseString(data, function (err, result) {
- console.dir(result);
- console.log('Done');
- });
-});
-```
-
-Look ma, no event listeners!
-
-You can also use `xml2js` from
-[CoffeeScript](https://github.com/jashkenas/coffeescript), further reducing
-the clutter:
-
-```coffeescript
-fs = require 'fs',
-xml2js = require 'xml2js'
-
-parser = new xml2js.Parser()
-fs.readFile __dirname + '/foo.xml', (err, data) ->
- parser.parseString data, (err, result) ->
- console.dir result
- console.log 'Done.'
-```
-
-But what happens if you forget the `new` keyword to create a new `Parser`? In
-the middle of a nightly coding session, it might get lost, after all. Worry
-not, we got you covered! Starting with 0.2.8 you can also leave it out, in
-which case `xml2js` will helpfully add it for you, no bad surprises and
-inexplicable bugs!
-
-Promise usage
--------------
-
-```javascript
-var xml2js = require('xml2js');
-var xml = ' ';
-
-// With parser
-var parser = new xml2js.Parser(/* options */);
-parser.parseStringPromise(xml).then(function (result) {
- console.dir(result);
- console.log('Done');
-})
-.catch(function (err) {
- // Failed
-});
-
-// Without parser
-xml2js.parseStringPromise(xml /*, options */).then(function (result) {
- console.dir(result);
- console.log('Done');
-})
-.catch(function (err) {
- // Failed
-});
-```
-
-Parsing multiple files
-----------------------
-
-If you want to parse multiple files, you have multiple possibilities:
-
- * You can create one `xml2js.Parser` per file. That's the recommended one
- and is promised to always *just work*.
- * You can call `reset()` on your parser object.
- * You can hope everything goes well anyway. This behaviour is not
- guaranteed work always, if ever. Use option #1 if possible. Thanks!
-
-So you wanna some JSON?
------------------------
-
-Just wrap the `result` object in a call to `JSON.stringify` like this
-`JSON.stringify(result)`. You get a string containing the JSON representation
-of the parsed object that you can feed to JSON-hungry consumers.
-
-Displaying results
-------------------
-
-You might wonder why, using `console.dir` or `console.log` the output at some
-level is only `[Object]`. Don't worry, this is not because `xml2js` got lazy.
-That's because Node uses `util.inspect` to convert the object into strings and
-that function stops after `depth=2` which is a bit low for most XML.
-
-To display the whole deal, you can use `console.log(util.inspect(result, false,
-null))`, which displays the whole result.
-
-So much for that, but what if you use
-[eyes](https://github.com/cloudhead/eyes.js) for nice colored output and it
-truncates the output with `…`? Don't fear, there's also a solution for that,
-you just need to increase the `maxLength` limit by creating a custom inspector
-`var inspect = require('eyes').inspector({maxLength: false})` and then you can
-easily `inspect(result)`.
-
-XML builder usage
------------------
-
-Since 0.4.0, objects can be also be used to build XML:
-
-```javascript
-var xml2js = require('xml2js');
-
-var obj = {name: "Super", Surname: "Man", age: 23};
-
-var builder = new xml2js.Builder();
-var xml = builder.buildObject(obj);
-```
-will result in:
-
-```xml
-
-
- Super
- Man
- 23
-
-```
-
-At the moment, a one to one bi-directional conversion is guaranteed only for
-default configuration, except for `attrkey`, `charkey` and `explicitArray` options
-you can redefine to your taste. Writing CDATA is supported via setting the `cdata`
-option to `true`.
-
-To specify attributes:
-```javascript
-var xml2js = require('xml2js');
-
-var obj = {root: {$: {id: "my id"}, _: "my inner text"}};
-
-var builder = new xml2js.Builder();
-var xml = builder.buildObject(obj);
-```
-will result in:
-```xml
-
-my inner text
-```
-
-### Adding xmlns attributes
-
-You can generate XML that declares XML namespace prefix / URI pairs with xmlns attributes.
-
-Example declaring a default namespace on the root element:
-
-```javascript
-let obj = {
- Foo: {
- $: {
- "xmlns": "http://foo.com"
- }
- }
-};
-```
-Result of `buildObject(obj)`:
-```xml
-
-```
-Example declaring non-default namespaces on non-root elements:
-```javascript
-let obj = {
- 'foo:Foo': {
- $: {
- 'xmlns:foo': 'http://foo.com'
- },
- 'bar:Bar': {
- $: {
- 'xmlns:bar': 'http://bar.com'
- }
- }
- }
-}
-```
-Result of `buildObject(obj)`:
-```xml
-
-
-
-```
-
-
-Processing attribute, tag names and values
-------------------------------------------
-
-Since 0.4.1 you can optionally provide the parser with attribute name and tag name processors as well as element value processors (Since 0.4.14, you can also optionally provide the parser with attribute value processors):
-
-```javascript
-
-function nameToUpperCase(name){
- return name.toUpperCase();
-}
-
-//transform all attribute and tag names and values to uppercase
-parseString(xml, {
- tagNameProcessors: [nameToUpperCase],
- attrNameProcessors: [nameToUpperCase],
- valueProcessors: [nameToUpperCase],
- attrValueProcessors: [nameToUpperCase]},
- function (err, result) {
- // processed data
-});
-```
-
-The `tagNameProcessors` and `attrNameProcessors` options
-accept an `Array` of functions with the following signature:
-
-```javascript
-function (name){
- //do something with `name`
- return name
-}
-```
-
-The `attrValueProcessors` and `valueProcessors` options
-accept an `Array` of functions with the following signature:
-
-```javascript
-function (value, name) {
- //`name` will be the node name or attribute name
- //do something with `value`, (optionally) dependent on the node/attr name
- return value
-}
-```
-
-Some processors are provided out-of-the-box and can be found in `lib/processors.js`:
-
-- `normalize`: transforms the name to lowercase.
-(Automatically used when `options.normalize` is set to `true`)
-
-- `firstCharLowerCase`: transforms the first character to lower case.
-E.g. 'MyTagName' becomes 'myTagName'
-
-- `stripPrefix`: strips the xml namespace prefix. E.g ` ` will become 'Bar'.
-(N.B.: the `xmlns` prefix is NOT stripped.)
-
-- `parseNumbers`: parses integer-like strings as integers and float-like strings as floats
-E.g. "0" becomes 0 and "15.56" becomes 15.56
-
-- `parseBooleans`: parses boolean-like strings to booleans
-E.g. "true" becomes true and "False" becomes false
-
-Options
-=======
-
-Apart from the default settings, there are a number of options that can be
-specified for the parser. Options are specified by ``new Parser({optionName:
-value})``. Possible options are:
-
- * `attrkey` (default: `$`): Prefix that is used to access the attributes.
- Version 0.1 default was `@`.
- * `charkey` (default: `_`): Prefix that is used to access the character
- content. Version 0.1 default was `#`.
- * `explicitCharkey` (default: `false`) Determines whether or not to use
- a `charkey` prefix for elements with no attributes.
- * `trim` (default: `false`): Trim the whitespace at the beginning and end of
- text nodes.
- * `normalizeTags` (default: `false`): Normalize all tag names to lowercase.
- * `normalize` (default: `false`): Trim whitespaces inside text nodes.
- * `explicitRoot` (default: `true`): Set this if you want to get the root
- node in the resulting object.
- * `emptyTag` (default: `''`): what will the value of empty nodes be. In case
- you want to use an empty object as a default value, it is better to provide a factory
- function `() => ({})` instead. Without this function a plain object would
- become a shared reference across all occurrences with unwanted behavior.
- * `explicitArray` (default: `true`): Always put child nodes in an array if
- true; otherwise an array is created only if there is more than one.
- * `ignoreAttrs` (default: `false`): Ignore all XML attributes and only create
- text nodes.
- * `mergeAttrs` (default: `false`): Merge attributes and child elements as
- properties of the parent, instead of keying attributes off a child
- attribute object. This option is ignored if `ignoreAttrs` is `true`.
- * `validator` (default `null`): You can specify a callable that validates
- the resulting structure somehow, however you want. See unit tests
- for an example.
- * `xmlns` (default `false`): Give each element a field usually called '$ns'
- (the first character is the same as attrkey) that contains its local name
- and namespace URI.
- * `explicitChildren` (default `false`): Put child elements to separate
- property. Doesn't work with `mergeAttrs = true`. If element has no children
- then "children" won't be created. Added in 0.2.5.
- * `childkey` (default `$$`): Prefix that is used to access child elements if
- `explicitChildren` is set to `true`. Added in 0.2.5.
- * `preserveChildrenOrder` (default `false`): Modifies the behavior of
- `explicitChildren` so that the value of the "children" property becomes an
- ordered array. When this is `true`, every node will also get a `#name` field
- whose value will correspond to the XML nodeName, so that you may iterate
- the "children" array and still be able to determine node names. The named
- (and potentially unordered) properties are also retained in this
- configuration at the same level as the ordered "children" array. Added in
- 0.4.9.
- * `charsAsChildren` (default `false`): Determines whether chars should be
- considered children if `explicitChildren` is on. Added in 0.2.5.
- * `includeWhiteChars` (default `false`): Determines whether whitespace-only
- text nodes should be included. Added in 0.4.17.
- * `async` (default `false`): Should the callbacks be async? This *might* be
- an incompatible change if your code depends on sync execution of callbacks.
- Future versions of `xml2js` might change this default, so the recommendation
- is to not depend on sync execution anyway. Added in 0.2.6.
- * `strict` (default `true`): Set sax-js to strict or non-strict parsing mode.
- Defaults to `true` which is *highly* recommended, since parsing HTML which
- is not well-formed XML might yield just about anything. Added in 0.2.7.
- * `attrNameProcessors` (default: `null`): Allows the addition of attribute
- name processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.14
- * `attrValueProcessors` (default: `null`): Allows the addition of attribute
- value processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (value, name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.1
- * `tagNameProcessors` (default: `null`): Allows the addition of tag name
- processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.1
- * `valueProcessors` (default: `null`): Allows the addition of element value
- processing functions. Accepts an `Array` of functions with following
- signature:
- ```javascript
- function (value, name){
- //do something with `name`
- return name
- }
- ```
- Added in 0.4.6
-
-Options for the `Builder` class
--------------------------------
-These options are specified by ``new Builder({optionName: value})``.
-Possible options are:
-
- * `attrkey` (default: `$`): Prefix that is used to access the attributes.
- Version 0.1 default was `@`.
- * `charkey` (default: `_`): Prefix that is used to access the character
- content. Version 0.1 default was `#`.
- * `rootName` (default `root` or the root key name): root element name to be used in case
- `explicitRoot` is `false` or to override the root element name.
- * `renderOpts` (default `{ 'pretty': true, 'indent': ' ', 'newline': '\n' }`):
- Rendering options for xmlbuilder-js.
- * pretty: prettify generated XML
- * indent: whitespace for indentation (only when pretty)
- * newline: newline char (only when pretty)
- * `xmldec` (default `{ 'version': '1.0', 'encoding': 'UTF-8', 'standalone': true }`:
- XML declaration attributes.
- * `xmldec.version` A version number string, e.g. 1.0
- * `xmldec.encoding` Encoding declaration, e.g. UTF-8
- * `xmldec.standalone` standalone document declaration: true or false
- * `doctype` (default `null`): optional DTD. Eg. `{'ext': 'hello.dtd'}`
- * `headless` (default: `false`): omit the XML header. Added in 0.4.3.
- * `allowSurrogateChars` (default: `false`): allows using characters from the Unicode
- surrogate blocks.
- * `cdata` (default: `false`): wrap text nodes in `` instead of
- escaping when necessary. Does not add `` if it is not required.
- Added in 0.4.5.
-
-`renderOpts`, `xmldec`,`doctype` and `headless` pass through to
-[xmlbuilder-js](https://github.com/oozcitak/xmlbuilder-js).
-
-Updating to new version
-=======================
-
-Version 0.2 changed the default parsing settings, but version 0.1.14 introduced
-the default settings for version 0.2, so these settings can be tried before the
-migration.
-
-```javascript
-var xml2js = require('xml2js');
-var parser = new xml2js.Parser(xml2js.defaults["0.2"]);
-```
-
-To get the 0.1 defaults in version 0.2 you can just use
-`xml2js.defaults["0.1"]` in the same place. This provides you with enough time
-to migrate to the saner way of parsing in `xml2js` 0.2. We try to make the
-migration as simple and gentle as possible, but some breakage cannot be
-avoided.
-
-So, what exactly did change and why? In 0.2 we changed some defaults to parse
-the XML in a more universal and sane way. So we disabled `normalize` and `trim`
-so `xml2js` does not cut out any text content. You can reenable this at will of
-course. A more important change is that we return the root tag in the resulting
-JavaScript structure via the `explicitRoot` setting, so you need to access the
-first element. This is useful for anybody who wants to know what the root node
-is and preserves more information. The last major change was to enable
-`explicitArray`, so everytime it is possible that one might embed more than one
-sub-tag into a tag, xml2js >= 0.2 returns an array even if the array just
-includes one element. This is useful when dealing with APIs that return
-variable amounts of subtags.
-
-Running tests, development
-==========================
-
-[![Build Status](https://travis-ci.org/Leonidas-from-XIV/node-xml2js.svg?branch=master)](https://travis-ci.org/Leonidas-from-XIV/node-xml2js)
-[![Coverage Status](https://coveralls.io/repos/Leonidas-from-XIV/node-xml2js/badge.svg?branch=)](https://coveralls.io/r/Leonidas-from-XIV/node-xml2js?branch=master)
-[![Dependency Status](https://david-dm.org/Leonidas-from-XIV/node-xml2js.svg)](https://david-dm.org/Leonidas-from-XIV/node-xml2js)
-
-The development requirements are handled by npm, you just need to install them.
-We also have a number of unit tests, they can be run using `npm test` directly
-from the project root. This runs zap to discover all the tests and execute
-them.
-
-If you like to contribute, keep in mind that `xml2js` is written in
-CoffeeScript, so don't develop on the JavaScript files that are checked into
-the repository for convenience reasons. Also, please write some unit test to
-check your behaviour and if it is some user-facing thing, add some
-documentation to this README, so people will know it exists. Thanks in advance!
-
-Getting support
-===============
-
-Please, if you have a problem with the library, first make sure you read this
-README. If you read this far, thanks, you're good. Then, please make sure your
-problem really is with `xml2js`. It is? Okay, then I'll look at it. Send me a
-mail and we can talk. Please don't open issues, as I don't think that is the
-proper forum for support problems. Some problems might as well really be bugs
-in `xml2js`, if so I'll let you know to open an issue instead :)
-
-But if you know you really found a bug, feel free to open an issue instead.
diff --git a/developer/src/common/web/utils/src/deps/xml2js/bom.js b/developer/src/common/web/utils/src/deps/xml2js/bom.js
deleted file mode 100644
index 0ad6e2a4a43..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/bom.js
+++ /dev/null
@@ -1,8 +0,0 @@
-"use strict";
-export function stripBOM(str) {
- if (str[0] === '\uFEFF') {
- return str.substring(1);
- } else {
- return str;
- }
-};
diff --git a/developer/src/common/web/utils/src/deps/xml2js/builder.js b/developer/src/common/web/utils/src/deps/xml2js/builder.js
deleted file mode 100644
index 5dfca61c9f6..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/builder.js
+++ /dev/null
@@ -1,118 +0,0 @@
-var escapeCDATA, requiresCDATA, wrapCDATA,
- hasProp = {}.hasOwnProperty;
-
-import * as builder from 'xmlbuilder';
-import { defaults } from './defaults.js';
-
-requiresCDATA = function(entry) {
- return typeof entry === "string" && (entry.indexOf('&') >= 0 || entry.indexOf('>') >= 0 || entry.indexOf('<') >= 0);
-};
-
-wrapCDATA = function(entry) {
- return "";
-};
-
-escapeCDATA = function(entry) {
- return entry.replace(']]>', ']]]]>');
-};
-
-export class Builder {
- constructor(opts) {
- var key, ref, value;
- this.options = {};
- ref = defaults["0.2"];
- for (key in ref) {
- if (!hasProp.call(ref, key)) continue;
- value = ref[key];
- this.options[key] = value;
- }
- for (key in opts) {
- if (!hasProp.call(opts, key)) continue;
- value = opts[key];
- this.options[key] = value;
- }
- }
-
- buildObject(rootObj) {
- var attrkey, charkey, render, rootElement, rootName;
- attrkey = this.options.attrkey;
- charkey = this.options.charkey;
- if ((Object.keys(rootObj).length === 1) && (this.options.rootName === defaults['0.2'].rootName)) {
- rootName = Object.keys(rootObj)[0];
- rootObj = rootObj[rootName];
- } else {
- rootName = this.options.rootName;
- }
- render = (function(_this) {
- return function(element, obj) {
- var attr, child, entry, index, key, value;
- if (typeof obj !== 'object') {
- if (_this.options.cdata && requiresCDATA(obj)) {
- element.raw(wrapCDATA(obj));
- } else {
- element.txt(obj);
- }
- } else if (Array.isArray(obj)) {
- for (index in obj) {
- if (!hasProp.call(obj, index)) continue;
- child = obj[index];
- for (key in child) {
- entry = child[key];
- element = render(element.ele(key), entry).up();
- }
- }
- } else {
- for (key in obj) {
- if (!hasProp.call(obj, key)) continue;
- child = obj[key];
- if (key === attrkey) {
- if (typeof child === "object") {
- for (attr in child) {
- value = child[attr];
- element = element.att(attr, value);
- }
- }
- } else if (key === charkey) {
- if (_this.options.cdata && requiresCDATA(child)) {
- element = element.raw(wrapCDATA(child));
- } else {
- element = element.txt(child);
- }
- } else if (Array.isArray(child)) {
- for (index in child) {
- if (!hasProp.call(child, index)) continue;
- entry = child[index];
- if (typeof entry === 'string') {
- if (_this.options.cdata && requiresCDATA(entry)) {
- element = element.ele(key).raw(wrapCDATA(entry)).up();
- } else {
- element = element.ele(key, entry).up();
- }
- } else {
- element = render(element.ele(key), entry).up();
- }
- }
- } else if (typeof child === "object") {
- element = render(element.ele(key), child).up();
- } else {
- if (typeof child === 'string' && _this.options.cdata && requiresCDATA(child)) {
- element = element.ele(key).raw(wrapCDATA(child)).up();
- } else {
- if (child == null) {
- child = '';
- }
- element = element.ele(key, child.toString()).up();
- }
- }
- }
- }
- return element;
- };
- })(this);
- rootElement = builder.create(rootName, this.options.xmldec, this.options.doctype, {
- headless: this.options.headless,
- allowSurrogateChars: this.options.allowSurrogateChars
- });
- return render(rootElement, rootObj).end(this.options.renderOpts);
- };
-}
diff --git a/developer/src/common/web/utils/src/deps/xml2js/defaults.js b/developer/src/common/web/utils/src/deps/xml2js/defaults.js
deleted file mode 100644
index d1009281792..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/defaults.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// Generated by CoffeeScript 1.12.7
-export const defaults = {
- "0.1": {
- explicitCharkey: false,
- trim: true,
- normalize: true,
- normalizeTags: false,
- attrkey: "@",
- charkey: "#",
- explicitArray: false,
- ignoreAttrs: false,
- mergeAttrs: false,
- explicitRoot: false,
- validator: null,
- xmlns: false,
- explicitChildren: false,
- childkey: '@@',
- charsAsChildren: false,
- includeWhiteChars: false,
- async: false,
- strict: true,
- attrNameProcessors: null,
- attrValueProcessors: null,
- tagNameProcessors: null,
- valueProcessors: null,
- emptyTag: ''
- },
- "0.2": {
- explicitCharkey: false,
- trim: false,
- normalize: false,
- normalizeTags: false,
- attrkey: "$",
- charkey: "_",
- explicitArray: true,
- ignoreAttrs: false,
- mergeAttrs: false,
- explicitRoot: true,
- validator: null,
- xmlns: false,
- explicitChildren: false,
- preserveChildrenOrder: false,
- childkey: '$$',
- charsAsChildren: false,
- includeWhiteChars: false,
- async: false,
- strict: true,
- attrNameProcessors: null,
- attrValueProcessors: null,
- tagNameProcessors: null,
- valueProcessors: null,
- rootName: 'root',
- xmldec: {
- 'version': '1.0',
- 'encoding': 'UTF-8',
- 'standalone': true
- },
- doctype: null,
- renderOpts: {
- 'pretty': true,
- 'indent': ' ',
- 'newline': '\n'
- },
- headless: false,
- chunkSize: 10000,
- emptyTag: '',
- cdata: false
- }
- };
diff --git a/developer/src/common/web/utils/src/deps/xml2js/parser.js b/developer/src/common/web/utils/src/deps/xml2js/parser.js
deleted file mode 100644
index 1c7f148d906..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/parser.js
+++ /dev/null
@@ -1,381 +0,0 @@
- var isEmpty, processItem,
- bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
-import sax from 'sax';
-import { EventEmitter } from 'eventemitter3';
-import * as bom from './bom.js';
-import * as processors from './processors.js';
-import { setImmediate } from 'timers';
-import { defaults } from './defaults.js';
-
- isEmpty = function(thing) {
- return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0;
- };
-
- processItem = function(processors, item, key) {
- var i, len, process;
- for (i = 0, len = processors.length; i < len; i++) {
- process = processors[i];
- item = process(item, key);
- }
- return item;
- };
-
-/** @type Class */
-export class Parser extends EventEmitter {
-// export const Parser = (function(superClass) {
- // extend(Parser, superClass);
-
- constructor(opts) {
- super();
- this.parseStringPromise = bind(this.parseStringPromise, this);
- this.parseString = bind(this.parseString, this);
- this.reset = bind(this.reset, this);
- this.assignOrPush = bind(this.assignOrPush, this);
- this.processAsync = bind(this.processAsync, this);
- var key, ref, value;
- if (!(this instanceof Parser)) {
- return new Parser(opts);
- }
- this.options = {};
- ref = defaults["0.2"];
- for (key in ref) {
- if (!hasProp.call(ref, key)) continue;
- value = ref[key];
- this.options[key] = value;
- }
- for (key in opts) {
- if (!hasProp.call(opts, key)) continue;
- value = opts[key];
- this.options[key] = value;
- }
- if (this.options.xmlns) {
- this.options.xmlnskey = this.options.attrkey + "ns";
- }
- if (this.options.normalizeTags) {
- if (!this.options.tagNameProcessors) {
- this.options.tagNameProcessors = [];
- }
- this.options.tagNameProcessors.unshift(processors.normalize);
- }
- this.reset();
- }
-
- processAsync() {
- var chunk, err;
- try {
- if (this.remaining.length <= this.options.chunkSize) {
- chunk = this.remaining;
- this.remaining = '';
- this.saxParser = this.saxParser.write(chunk);
- return this.saxParser.close();
- } else {
- chunk = this.remaining.substr(0, this.options.chunkSize);
- this.remaining = this.remaining.substr(this.options.chunkSize, this.remaining.length);
- this.saxParser = this.saxParser.write(chunk);
- return setImmediate(this.processAsync);
- }
- } catch (error1) {
- err = error1;
- if (!this.saxParser.errThrown) {
- this.saxParser.errThrown = true;
- return this.emit(err);
- }
- }
- };
-
- assignOrPush(obj, key, newValue) {
- if (!(key in obj)) {
- if (!this.options.explicitArray) {
- return obj[key] = newValue;
- } else {
- return obj[key] = [newValue];
- }
- } else {
- if (!(obj[key] instanceof Array)) {
- obj[key] = [obj[key]];
- }
- return obj[key].push(newValue);
- }
- };
-
- reset() {
- var attrkey, charkey, ontext, stack;
- this.removeAllListeners();
- this.saxParser = sax.parser(this.options.strict, {
- trim: false,
- normalize: false,
- xmlns: this.options.xmlns
- });
- this.saxParser.errThrown = false;
- this.saxParser.onerror = (function(_this) {
- return function(error) {
- _this.saxParser.resume();
- if (!_this.saxParser.errThrown) {
- _this.saxParser.errThrown = true;
- return _this.emit("error", error);
- }
- };
- })(this);
- this.saxParser.onend = (function(_this) {
- return function() {
- if (!_this.saxParser.ended) {
- _this.saxParser.ended = true;
- return _this.emit("end", _this.resultObject);
- }
- };
- })(this);
- this.saxParser.ended = false;
- this.EXPLICIT_CHARKEY = this.options.explicitCharkey;
- this.resultObject = null;
- stack = [];
- attrkey = this.options.attrkey;
- charkey = this.options.charkey;
- this.saxParser.onopentag = (function(_this) {
- return function(node) {
- var key, newValue, obj, processedKey, ref;
- obj = Object.create(null);
- obj[charkey] = "";
- if (!_this.options.ignoreAttrs) {
- ref = node.attributes;
- for (key in ref) {
- if (!hasProp.call(ref, key)) continue;
- if (!(attrkey in obj) && !_this.options.mergeAttrs) {
- obj[attrkey] = Object.create(null);
- }
- newValue = _this.options.attrValueProcessors ? processItem(_this.options.attrValueProcessors, node.attributes[key], key) : node.attributes[key];
- processedKey = _this.options.attrNameProcessors ? processItem(_this.options.attrNameProcessors, key) : key;
- if (_this.options.mergeAttrs) {
- _this.assignOrPush(obj, processedKey, newValue);
- } else {
- obj[attrkey][processedKey] = newValue;
- }
- }
- }
- obj["#name"] = _this.options.tagNameProcessors ? processItem(_this.options.tagNameProcessors, node.name) : node.name;
- if (_this.options.xmlns) {
- obj[_this.options.xmlnskey] = {
- uri: node.uri,
- local: node.local
- };
- }
- return stack.push(obj);
- };
- })(this);
- this.saxParser.onclosetag = (function(_this) {
- return function() {
- var cdata, emptyStr, key, node, nodeName, obj, objClone, old, s, xpath;
- obj = stack.pop();
- nodeName = obj["#name"];
- if (!_this.options.explicitChildren || !_this.options.preserveChildrenOrder) {
- delete obj["#name"];
- }
- if (obj.cdata === true) {
- cdata = obj.cdata;
- delete obj.cdata;
- }
- s = stack[stack.length - 1];
- if (obj[charkey].match(/^\s*$/) && !cdata && !_this.options.includeWhiteChars) {
- emptyStr = obj[charkey];
- delete obj[charkey];
- } else {
- if (_this.options.trim) {
- obj[charkey] = obj[charkey].trim();
- }
- if (_this.options.normalize) {
- obj[charkey] = obj[charkey].replace(/\s{2,}/g, " ").trim();
- }
- obj[charkey] = _this.options.valueProcessors ? processItem(_this.options.valueProcessors, obj[charkey], nodeName) : obj[charkey];
- if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) {
- obj = obj[charkey];
- }
- }
- if (isEmpty(obj)) {
- if (typeof _this.options.emptyTag === 'function') {
- obj = _this.options.emptyTag();
- } else {
- obj = _this.options.emptyTag !== '' ? _this.options.emptyTag : emptyStr;
- }
- }
- if (_this.options.validator != null) {
- xpath = "/" + ((function() {
- var i, len, results;
- results = [];
- for (i = 0, len = stack.length; i < len; i++) {
- node = stack[i];
- results.push(node["#name"]);
- }
- return results;
- })()).concat(nodeName).join("/");
- (function() {
- var err;
- try {
- return obj = _this.options.validator(xpath, s && s[nodeName], obj);
- } catch (error1) {
- err = error1;
- return _this.emit("error", err);
- }
- })();
- }
- if (_this.options.explicitChildren && !_this.options.mergeAttrs && typeof obj === 'object') {
- if (!_this.options.preserveChildrenOrder) {
- node = Object.create(null);
- if (_this.options.attrkey in obj) {
- node[_this.options.attrkey] = obj[_this.options.attrkey];
- delete obj[_this.options.attrkey];
- }
- if (!_this.options.charsAsChildren && _this.options.charkey in obj) {
- node[_this.options.charkey] = obj[_this.options.charkey];
- delete obj[_this.options.charkey];
- }
- if (Object.getOwnPropertyNames(obj).length > 0) {
- node[_this.options.childkey] = obj;
- }
- obj = node;
- } else if (s) {
- s[_this.options.childkey] = s[_this.options.childkey] || [];
- objClone = Object.create(null);
- for (key in obj) {
- if (!hasProp.call(obj, key)) continue;
- objClone[key] = obj[key];
- }
- s[_this.options.childkey].push(objClone);
- delete obj["#name"];
- if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) {
- obj = obj[charkey];
- }
- }
- }
- if (stack.length > 0) {
- return _this.assignOrPush(s, nodeName, obj);
- } else {
- if (_this.options.explicitRoot) {
- old = obj;
- obj = Object.create(null);
- obj[nodeName] = old;
- }
- _this.resultObject = obj;
- _this.saxParser.ended = true;
- return _this.emit("end", _this.resultObject);
- }
- };
- })(this);
- ontext = (function(_this) {
- return function(text) {
- var charChild, s;
- s = stack[stack.length - 1];
- if (s) {
- s[charkey] += text;
- if (_this.options.explicitChildren && _this.options.preserveChildrenOrder && _this.options.charsAsChildren && (_this.options.includeWhiteChars || text.replace(/\\n/g, '').trim() !== '')) {
- s[_this.options.childkey] = s[_this.options.childkey] || [];
- charChild = {
- '#name': '__text__'
- };
- charChild[charkey] = text;
- if (_this.options.normalize) {
- charChild[charkey] = charChild[charkey].replace(/\s{2,}/g, " ").trim();
- }
- s[_this.options.childkey].push(charChild);
- }
- return s;
- }
- };
- })(this);
- this.saxParser.ontext = ontext;
- return this.saxParser.oncdata = (function(_this) {
- return function(text) {
- var s;
- s = ontext(text);
- if (s) {
- return s.cdata = true;
- }
- };
- })(this);
- };
-
- parseString(str, cb) {
- var err;
- if ((cb != null) && typeof cb === "function") {
- this.on("end", function(result) {
- this.reset();
- return cb(null, result);
- });
- this.on("error", function(err) {
- this.reset();
- return cb(err);
- });
- }
- try {
- str = str.toString();
- if (str.trim() === '') {
- this.emit("end", null);
- return true;
- }
- str = bom.stripBOM(str);
- if (this.options.async) {
- this.remaining = str;
- setImmediate(this.processAsync);
- return this.saxParser;
- }
- return this.saxParser.write(str).close();
- } catch (error1) {
- err = error1;
- if (!(this.saxParser.errThrown || this.saxParser.ended)) {
- if(this.listenerCount('error') > 0) {
- this.emit('error', err);
- } else {
- throw err;
- }
- return this.saxParser.errThrown = true;
- } else if (this.saxParser.ended) {
- throw err;
- }
- }
- };
-
- parseStringPromise(str) {
- return new Promise((function(_this) {
- return function(resolve, reject) {
- return _this.parseString(str, function(err, value) {
- if (err) {
- return reject(err);
- } else {
- return resolve(value);
- }
- });
- };
- })(this));
- };
-
- }
-
- export const parseString = function(str, a, b) {
- var cb, options, parser;
- if (b != null) {
- if (typeof b === 'function') {
- cb = b;
- }
- if (typeof a === 'object') {
- options = a;
- }
- } else {
- if (typeof a === 'function') {
- cb = a;
- }
- options = {};
- }
- parser = new Parser(options);
- return parser.parseString(str, cb);
- };
-
- export const parseStringPromise = function(str, a) {
- var options, parser;
- if (typeof a === 'object') {
- options = a;
- }
- parser = new Parser(options);
- return parser.parseStringPromise(str);
- };
-
diff --git a/developer/src/common/web/utils/src/deps/xml2js/processors.js b/developer/src/common/web/utils/src/deps/xml2js/processors.js
deleted file mode 100644
index 2a6849088c1..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/processors.js
+++ /dev/null
@@ -1,31 +0,0 @@
- "use strict";
- var prefixMatch;
-
- prefixMatch = new RegExp(/(?!xmlns)^.*:/);
-
- export const normalize = function(str) {
- return str.toLowerCase();
- };
-
- export const firstCharLowerCase = function(str) {
- return str.charAt(0).toLowerCase() + str.slice(1);
- };
-
- export const stripPrefix = function(str) {
- return str.replace(prefixMatch, '');
- };
-
- export const parseNumbers = function(str) {
- if (!isNaN(str)) {
- str = str % 1 === 0 ? parseInt(str, 10) : parseFloat(str);
- }
- return str;
- };
-
- export const parseBooleans = function(str) {
- if (/^(?:true|false)$/i.test(str)) {
- str = str.toLowerCase() === 'true';
- }
- return str;
- };
-
diff --git a/developer/src/common/web/utils/src/deps/xml2js/xml2js.js b/developer/src/common/web/utils/src/deps/xml2js/xml2js.js
deleted file mode 100644
index 8ecbe3bcf2f..00000000000
--- a/developer/src/common/web/utils/src/deps/xml2js/xml2js.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
- // import { defaults } from './defaults.js';
- import * as builder from './builder.js';
- import * as parser from './parser.js';
- import * as processors from './processors.js';
-
- // export const defaults = defaults.defaults;
- // export const processors = processors;
-
- /** @type Class */
- export class ValidationError extends Error {
- constructor(message) {
- super(message);
- this.message = message;
- }
- };
-
- export const Parser = parser.Parser;
-
- export const Builder = builder.Builder;
-
- export const parseString = parser.parseString;
-
- export const parseStringPromise = parser.parseStringPromise;
diff --git a/developer/src/common/web/utils/src/github-urls.ts b/developer/src/common/web/utils/src/github-urls.ts
new file mode 100644
index 00000000000..1a65106bf1f
--- /dev/null
+++ b/developer/src/common/web/utils/src/github-urls.ts
@@ -0,0 +1,35 @@
+/**
+ * Matches only a GitHub permanent raw URI with a commit hash, without any other
+ * components; note hash is called branch to match other URI formats
+ */
+export const GITHUB_STABLE_SOURCE = /^https:\/\/github\.com\/(?[a-zA-Z0-9-]+)\/(?[\w\.-]+)\/raw\/(?[a-f0-9]{40})\/(?.+)$/;
+
+/**
+ * Matches any GitHub git resource raw 'user content' URI which can be
+ * translated to a permanent URI with a commit hash
+ */
+export const GITHUB_RAW_URI = /^https:\/\/raw\.githubusercontent\.com\/(?[a-zA-Z0-9-]+)\/(?[\w\.-]+)\/(?:refs\/(?:heads|tags)\/)?(?