diff --git a/HISTORY.md b/HISTORY.md
index 22efd85bd6..7bb236717b 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,111 @@
# Keyman Version History
+## 18.0.94 alpha 2024-08-21
+
+* fix(core): look for `emcc` instead of `emcc.py` (#12235)
+* fix(web): improve tokenization output when wordbreaker breaks spec for span properties in output (#12229)
+* chore(android): Use RTL-aware alignment and padding for layouts (#12225)
+* fix(web): disable fat-finger data use when mayCorrect = false (#12220)
+* fix(android): Auto-mirror back and forward arrows for RTL support (#12227)
+* feat(android): Add localization for Arabic (#12228)
+* fix(android): Auto-mirror increment and decrement arrows for RTL support (#12230)
+
+## 18.0.93 alpha 2024-08-20
+
+* (#12188)
+* fix(web): fix malformed reversion display strings (#12201)
+* feat(android): Add menu to specify long-press delay (#12170)
+* feat(android): Pass longpress delay to KeymanWeb (#12185)
+* (#12223)
+
+## 18.0.92 alpha 2024-08-19
+
+* chore(deps-dev): bump @75lb/deep-merge from 1.1.1 to 1.1.2 (#12118)
+* chore(deps): bump semver from 7.5.4 to 7.6.0 (#12119)
+* fix(windows): "Keyman" is not localized in UI strings (#12162)
+* feat(android): Enhance how ENTER key is handled in apps (#12125)
+* refactor(web): move `lm-worker` → `worker-thread` (#12150)
+* fix(developer): remove redundant check in LdmlKeyboardCompiler.validate() (#11858)
+
+## 18.0.91 alpha 2024-08-16
+
+* refactor(web): move `predictive-text` → `worker-main` (#12146)
+* fix(web): restore flick functionality (#12187)
+* refactor(web): move `lm-message-types` → `predictive-text/types` (#12149)
+* fix(developer): enforce presence of kps Info.Description field in info compilers (#12204)
+* fix(developer): enforce presence of Version field when FollowKeyboardVersion is not set, in package compiler (#12205)
+
+## 18.0.90 alpha 2024-08-15
+
+* refactor(web): move parts of `keyboard-processor` → `js-processor` (#12111)
+* fix(web): allow `lm-worker` to build on Linux (#12181)
+* refactor(web): move remaining parts of `keyboard-processor` → `keyboard` (#12131)
+* docs: update .kmx documentation around bitmaps, modifier state (#12183)
+* refactor(web): rename `package-cache` → `keyboard-storage` (#12135)
+
+## 18.0.89 alpha 2024-08-14
+
+* feat(web): test skipped prediction round handling (#12169)
+* fix(web): support live configuration of longpress delay (#12175)
+* feat(web): add osk.gestureParams for better gesture-config persistence (#12176)
+* refactor(core): move utfcodec to common (#12171)
+* refactor: move kmx_u16 to common and rename to km_u16 (#12177)
+* refactor(developer): use npm or local source for server addons instead of github references (#12090)
+* fix(developer): fix crash with Windows Clipboard by ignoring zero scan code in debugger (#12166)
+* chore(developer): update SIL logo (#12168)
+
+## 18.0.88 alpha 2024-08-13
+
+* docs: add .kmx specification (#12163)
+
+## 18.0.87 alpha 2024-08-12
+
+* chore(web): drop flaky auto-test component (#12155)
+* fix(common): show description even if child projects specifies path (#12145)
+
+## 18.0.86 alpha 2024-08-09
+
+* chore(android): Update splash screen with SIL Tai Heritage Pro logo (#12127)
+* chore(common): Update localization for Greek Polytonic (#12112)
+* chore(common): add support for build target platform exclusions (#12113)
+* feat(common): add top-level build.sh (#12114)
+* chore(developer): start building Developer on linux,mac (#12117)
+* refactor(web): Move `common/web/recorder` → `web/src/tools/testing/recorder-core` (#12092)
+
+## 18.0.85 alpha 2024-08-08
+
+* fix(developer): update date for last git commit date fixture (#12122)
+* refactor(android/engine): Parse keyboards.json for FirstVoices app (#11943)
+
+## 18.0.84 alpha 2024-08-07
+
+* refactor(common): move kpj-related files into developer-utils (#11531)
+* refactor(common): move kps-file.ts to @keymanapp/developer-utils (#11763)
+* refactor(common): move kvks-file to @keymanapp/developer-utils (#11764)
+* refactor(common): move .keyman-touch-layout reader/writer to @keymanapp/developer-utils (#11765)
+* refactor(common): move LDML keyboard .xml reader/writer and kmx-plus builder to @keymanapp/developer-utils (#12081)
+* refactor(common): move compiler-interfaces to @keymanapp/developer-utils (#12088)
+* refactor(common): move xml2js and related deps to @keymanapp/developer-utils (#12101)
+
+## 18.0.83 alpha 2024-08-06
+
+* chore(common): Update history from 17.0.328 (#12093)
+* change(common/models): change model tokenization to also tokenize whitespace (#11975)
+* feat(web): support transform tokenization when given a root context (#11998)
+* change(web): track whitespace-aware tokenization for context + correction-search caching (#11979)
+* change(web): leverage tokenization to preserve punctuation and whitespace when predicting (#11997)
+* fix(web): patch up worker build to provide artifacts for its tests (#12082)
+* chore(web): move `web/src/engine/paths/` → `web/src/engine/interfaces/` (#12064)
+* chore(web): move `common/web/input-processor/` → `web/src/engine/main/` (#12066)
+* refactor(web): refactor and harmonize constants (#12072)
+* chore(common): add data versions to minimum-versions.inc.sh (#12103)
+* fix(web): fix lm-worker test broken by botched merge conflict resolution (#12104)
+
+## 18.0.82 alpha 2024-08-05
+
+* chore(windows): remove QIT_VSHIFTDOWN QIT _VSHIFTUP (#11973)
+* chore(developer): add language/reference (#11799)
+
## 18.0.81 alpha 2024-08-03
* chore(developer): Revert "chore(developer): remove redundant references from tsconfig.json" (#12076)
@@ -138,7 +244,7 @@
## 18.0.63 alpha 2024-06-26
-* feat(linux): implement Linux side of SimulateAltGr option :checkered_flag: (#11852)
+* feat(linux): implement Linux side of SimulateAltGr option (#11852)
## 18.0.62 alpha 2024-06-25
@@ -150,8 +256,8 @@
* feat(web): optimization via lazy preprocessing of keyboard touch-layout info (#11265)
* fix(android): clear globe highlight when displaying keyboard picker (#11826)
-* refactor(linux): add KeymanOption class for options :checkered_flag: (#11850)
-* refactor(linux): rename methods that deal with keyboard options :checkered_flag: (#11851)
+* refactor(linux): add KeymanOption class for options (#11850)
+* refactor(linux): rename methods that deal with keyboard options (#11851)
## 18.0.60 alpha 2024-06-21
@@ -566,6 +672,10 @@
* chore(common): move to 18.0 alpha (#10713)
* chore: move to 18.0 alpha
+## 17.0.328 stable 2024-07-27
+
+* fix(web): add nullish test in setOsk (#12041)
+
## 17.0.327 stable 2024-07-25
* fix(android): include DOMRect polyfill for older ES6-supporting devices (#11654)
diff --git a/LICENSE.md b/LICENSE.md
index e031c6cc13..5b20376235 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
# License
-Copyright (c) 2017-2022 SIL International. All rights reserved.
+Copyright (c) 2017-2024 SIL Global. All rights reserved.
Licensed under the MIT License.
@@ -10,7 +10,7 @@ Licensed under the MIT License.
The MIT License
-Copyright (c) 2017-2022 SIL International
+Copyright (c) 2017-2024 SIL Global
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index acd890cc45..caab39c2f9 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
# License
-Copyright (c) SIL International.
+Copyright (c) SIL Global.
Keyman is an open source project distributed under the [MIT license](LICENSE.md).
diff --git a/VERSION.md b/VERSION.md
index 716c613096..a41e5425c0 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-18.0.82
\ No newline at end of file
+18.0.95
\ No newline at end of file
diff --git a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml
index 7196abdb22..3ba7eaf9fc 100644
--- a/android/KMAPro/kMAPro/src/main/AndroidManifest.xml
+++ b/android/KMAPro/kMAPro/src/main/AndroidManifest.xml
@@ -297,6 +297,11 @@
android:configChanges="orientation"
android:label="@string/app_name"
android:theme="@style/AppTheme.Base" />
+
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 c6885b2b56..4fb1c56792 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
@@ -4,6 +4,7 @@
package com.keyman.android;
+import com.tavultesoft.kmapro.AdjustLongpressDelayActivity;
import com.tavultesoft.kmapro.BuildConfig;
import com.tavultesoft.kmapro.DefaultLanguageResource;
import com.tavultesoft.kmapro.KeymanSettingsActivity;
@@ -175,6 +176,9 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
}
}
+ // Determine special handling for ENTER key
+ KMManager.setEnterMode(attribute.imeOptions, inputType);
+
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0);
@@ -244,6 +248,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
if (exText != null)
exText = null;
}
+ // Initialize keyboard options
+ KMManager.sendOptionsToKeyboard();
}
@Override
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
new file mode 100644
index 0000000000..2ecf62fe09
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java
@@ -0,0 +1,149 @@
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
+ * (Optional description of this file)
+ */
+package com.tavultesoft.kmapro;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.ContextCompat;
+
+import com.keyman.engine.BaseActivity;
+import com.keyman.engine.KMManager;
+
+/**
+ * Settings menu for adjusting the longpress delay time. The value for the current longpress delay time
+ * is saved in shared preferences as an integer (milliseconds).
+ */
+public class AdjustLongpressDelayActivity extends BaseActivity {
+ private static final String TAG = "AdjustLongpressDelay";
+ public static final String adjustLongpressDelayKey = "AdjustLongpressDelay";
+
+ // Keeps track of the adjusted longpress delay time for saving.
+ // Internally use milliseconds, but GUI displays seconds
+ private static int currentDelayTimeMS = KMManager.KMDefault_LongpressDelay; // ms
+ private static int minLongpressTime = 300; // ms
+ private static int maxLongpressTime = 1500; // ms
+ private static int delayTimeIncrement = 200; // ms
+
+ /**
+ * Convert currentDelayTimeMS to progress
+ * @return int
+ */
+ private int delayTimeToProgress() {
+ return (currentDelayTimeMS / delayTimeIncrement) - 1;
+ }
+
+ /**
+ * Convert progress to currentDelayTimeMS
+ * @param progress
+ * @return int (milliseconds)
+ */
+ private int progressToDelayTime(int progress) {
+ return (progress + 1) * delayTimeIncrement + 100;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Context context = this;
+
+ setContentView(R.layout.activity_adjust_longpress_delay);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar);
+ setSupportActionBar(toolbar);
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(null);
+ actionBar.setDisplayUseLogoEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayShowCustomEnabled(true);
+ actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.keyman_blue)));
+ }
+
+ TextView adjustLongpressDelayActivityTitle = (TextView) findViewById(R.id.bar_title);
+
+ String titleStr = getString(R.string.adjust_longpress_delay);
+ adjustLongpressDelayActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white));
+ adjustLongpressDelayActivityTitle.setText(titleStr);
+
+ currentDelayTimeMS = KMManager.getLongpressDelay();
+
+ TextView adjustLongpressDelayText = (TextView) findViewById(R.id.delayTimeText);
+ String longpressDelayTextSeconds = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTimeMS/1000.0));
+ adjustLongpressDelayText.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
+ adjustLongpressDelayText.setText(longpressDelayTextSeconds);
+
+ final SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar);
+ seekBar.setProgress(delayTimeToProgress());
+ seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ // Update the text field.
+ // The keyboard options will be saved and sent to KeymanWeb when exiting the menu
+ currentDelayTimeMS = progressToDelayTime(progress);
+ String longpressDelayTextSeconds = String.format(getString(R.string.longpress_delay_time), (float)(currentDelayTimeMS/1000.0));
+ adjustLongpressDelayText.setText(longpressDelayTextSeconds);
+ }
+ });
+
+ findViewById(R.id.delayTimeDownButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (currentDelayTimeMS > minLongpressTime) {
+ currentDelayTimeMS -= delayTimeIncrement;
+ seekBar.setProgress(delayTimeToProgress());
+ }
+ }
+ });
+
+ findViewById(R.id.delayTimeUpButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (currentDelayTimeMS < maxLongpressTime) {
+ currentDelayTimeMS += delayTimeIncrement;
+ seekBar.setProgress(delayTimeToProgress());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Store the longpress delay as a reference
+ // and then update KeymanWeb with the longpress delay
+ KMManager.setLongpressDelay(currentDelayTimeMS);
+ KMManager.sendOptionsToKeyboard();
+
+ super.onBackPressed();
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+}
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java
index 0989bf0d08..689548a56e 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeyboardSettingsActivity.java
@@ -105,7 +105,7 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
hashMap = new HashMap<>();
final String customHelpLink = kbd.getHelpLink();
// Check if app declared FileProvider
- icon = String.valueOf(R.drawable.ic_arrow_forward);
+ icon = String.valueOf(R.drawable.ic_action_forward);
// Don't show help link arrow if File Provider unavailable, or custom help doesn't exist
if ( (customHelpLink != null && !FileProviderUtils.exists(context)) ||
(customHelpLink == null && !packageID.equals(KMManager.KMDefault_UndefinedPackageID)) ) {
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java
index 72c54aa0d0..95bb7774b0 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java
@@ -28,7 +28,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
private static Context context;
private Preference languagesPreference, installKeyboardOrDictionary, displayLanguagePreference,
- adjustKeyboardHeight;
+ adjustKeyboardHeight, adjustLongpressDelay;
private ListPreference spacebarTextPreference;
private CheckBoxPreference setSystemKeyboardPreference;
private CheckBoxPreference setDefaultKeyboardPreference;
@@ -99,6 +99,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
});
setDefaultKeyboardPreference.setOnPreferenceChangeListener(checkBlocker);
+
adjustKeyboardHeight = new Preference(context);
adjustKeyboardHeight.setKey(AdjustKeyboardHeightActivity.adjustKeyboardHeightKey);
adjustKeyboardHeight.setTitle(getString(R.string.adjust_keyboard_height));
@@ -106,6 +107,13 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
Intent adjustKeyboardHeightIntent = new Intent(context, AdjustKeyboardHeightActivity.class);
adjustKeyboardHeight.setIntent(adjustKeyboardHeightIntent);
+ adjustLongpressDelay = new Preference(context);
+ adjustLongpressDelay.setKey(AdjustLongpressDelayActivity.adjustLongpressDelayKey);
+ adjustLongpressDelay.setTitle(getString(R.string.adjust_longpress_delay));
+ adjustLongpressDelay.setWidgetLayoutResource(R.layout.preference_duration_icon_layout);
+ Intent adjustLongpressDelayIntent = new Intent(context, AdjustLongpressDelayActivity.class);
+ adjustLongpressDelay.setIntent(adjustLongpressDelayIntent);
+
/* Spacebar Caption Preference */
spacebarTextPreference = new ListPreference(context);
@@ -188,8 +196,6 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
sendCrashReportPreference.setSummaryOff(getString(R.string.show_send_crash_report_off));
sendCrashReportPreference.setDefaultValue(true);
-
-
screen.addPreference(languagesPreference);
screen.addPreference(installKeyboardOrDictionary);
screen.addPreference(displayLanguagePreference);
@@ -197,6 +203,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat {
screen.addPreference(setDefaultKeyboardPreference);
screen.addPreference(adjustKeyboardHeight);
+ screen.addPreference(adjustLongpressDelay);
screen.addPreference(spacebarTextPreference);
screen.addPreference(hapticFeedbackPreference);
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 faa3e0efbf..75116bad8c 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
@@ -174,7 +174,7 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
updateActiveLexicalModel();
ImageView imageView = (ImageView) layout.findViewById(R.id.image1);
- imageView.setImageResource(R.drawable.ic_arrow_forward);
+ imageView.setImageResource(R.drawable.ic_action_forward);
layout.setEnabled(true);
layout.setOnClickListener(new View.OnClickListener() {
@Override
@@ -198,7 +198,7 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
* textView = (TextView) layout.findViewById(R.id.text1);
* textView.setText(getString(R.string.manage_dictionary));
* imageView = (ImageView) layout.findViewById(R.id.image1);
- * imageView.setImageResource(R.drawable.ic_arrow_forward);
+ * imageView.setImageResource(R.drawable.ic_action_forward);
*/
listView.setAdapter(adapter);
@@ -346,7 +346,7 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
}
holder.text.setText(kbd.getResourceName());
- holder.img.setImageResource(R.drawable.ic_arrow_forward);
+ holder.img.setImageResource(R.drawable.ic_action_forward);
return convertView;
}
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java
index 7b621da4b7..6e21d04f7b 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/LanguagesSettingsActivity.java
@@ -203,7 +203,7 @@ public final class LanguagesSettingsActivity extends AppCompatActivity {
}
holder.textLang.setText(data.name);
- holder.img.setImageResource(R.drawable.ic_arrow_forward);
+ holder.img.setImageResource(R.drawable.ic_action_forward);
holder.textCount.setText(getContext().getResources().getQuantityString(R.plurals.keyboard_count, data.keyboards.size(), data.keyboards.size()));
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
index 1e5231f521..2ae689364c 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
@@ -472,7 +472,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
@Override
public void onKeyboardLoaded(KeyboardType keyboardType) {
- // Do nothing
+ // Initialize keyboard options
+ KMManager.sendOptionsToKeyboard();
}
@Override
@@ -646,7 +647,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
final View textSizeController = inflater.inflate(R.layout.text_size_controller, null);
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this);
dialogBuilder.setIcon(R.drawable.ic_light_action_textsize);
- dialogBuilder.setTitle(String.format(getString(R.string.text_size), textSize));
+ dialogBuilder.setTitle(getTextSizeString());
dialogBuilder.setView(textSizeController);
dialogBuilder.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() {
@Override
@@ -677,7 +678,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
textSize = progress + minTextSize;
textView.setTextSize((float) textSize);
- dialog.setTitle(String.format(getString(R.string.text_size), textSize));
+ dialog.setTitle(getTextSizeString());
}
});
@@ -702,6 +703,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
});
}
+ /**
+ * Combine a localized string for "Text Size" plus Arabic numerals
+ * @return String
+ */
+ private String getTextSizeString() {
+ // Instead of formatting the number, will truncate formatting and concat the actual textSize
+ String label = getString(R.string.text_size).replace("%1$d", "");
+ return label + KMString.format(" %d", textSize);
+ }
+
private void showClearTextDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MainActivity.this);
dialogBuilder.setIcon(R.drawable.ic_light_action_trash);
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java
index 46bc68ca1a..9b05332182 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/SelectPackageActivity.java
@@ -81,7 +81,7 @@ public final class SelectPackageActivity extends AppCompatActivity {
HashMap hashMap = new HashMap<>();
hashMap.put(titleKey, keyboardName);
hashMap.put(subtitleKey, pkgID);
- String icon = String.valueOf(R.drawable.ic_arrow_forward);
+ String icon = String.valueOf(R.drawable.ic_action_forward);
hashMap.put(iconKey, icon);
list.add(hashMap);
}
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_blue_2017_1.9.png
deleted file mode 100644
index ddc2905347..0000000000
Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_blue_2017_1.9.png and /dev/null differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_color_tai_heritage_pro.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_color_tai_heritage_pro.9.png
new file mode 100644
index 0000000000..8f25377aa8
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-hdpi/sil_logo_color_tai_heritage_pro.9.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_blue_2017_1.9.png
deleted file mode 100644
index 0e720d69a1..0000000000
Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_blue_2017_1.9.png and /dev/null differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_color_tai_heritage_pro.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_color_tai_heritage_pro.9.png
new file mode 100644
index 0000000000..24cc6b9c92
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-mdpi/sil_logo_color_tai_heritage_pro.9.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_blue_2017_1.9.png
deleted file mode 100644
index 17f25f6cce..0000000000
Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_blue_2017_1.9.png and /dev/null differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_color_tai_heritage_pro.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_color_tai_heritage_pro.9.png
new file mode 100644
index 0000000000..308a7c6f9a
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xhdpi/sil_logo_color_tai_heritage_pro.9.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_blue_2017_1.9.png
deleted file mode 100644
index 014572d691..0000000000
Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_blue_2017_1.9.png and /dev/null differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_color_tai_heritage_pro.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_color_tai_heritage_pro.9.png
new file mode 100644
index 0000000000..eee36efdec
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxhdpi/sil_logo_color_tai_heritage_pro.9.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_blue_2017_1.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_blue_2017_1.9.png
deleted file mode 100644
index 04366bd151..0000000000
Binary files a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_blue_2017_1.9.png and /dev/null differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_color_tai_heritage_pro.9.png b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_color_tai_heritage_pro.9.png
new file mode 100644
index 0000000000..3dec6473b9
Binary files /dev/null and b/android/KMAPro/kMAPro/src/main/res/drawable-xxxhdpi/sil_logo_color_tai_heritage_pro.9.png differ
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml
index cb6b2cbabf..2f5d1eca2b 100644
--- a/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml
+++ b/android/KMAPro/kMAPro/src/main/res/drawable/branded_logo.xml
@@ -15,7 +15,7 @@
-
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_back.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_back.xml
deleted file mode 100644
index 165d535d3d..0000000000
--- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_back.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml
new file mode 100644
index 0000000000..c3085bec5e
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_decrement.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml
new file mode 100644
index 0000000000..ea188f818f
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_increment.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml
new file mode 100644
index 0000000000..80dc29e33f
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_timelapse.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml
new file mode 100644
index 0000000000..ed0bed54a9
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml
index 59de518535..3b71e87178 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_info.xml
@@ -19,11 +19,12 @@
+ android:src="@drawable/ic_action_back" />
+ android:src="@drawable/ic_action_forward" />
+ android:src="@drawable/ic_action_back" />
+ android:src="@drawable/ic_action_forward" />
+
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml
index 6aa84ce384..7074c696de 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/preference_icon_layout.xml
@@ -2,4 +2,4 @@
+ android:src="@drawable/ic_action_forward" />
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml
index 2245ef557a..c250b1bf07 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/text_size_controller.xml
@@ -15,7 +15,7 @@
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:contentDescription="@string/ic_text_size_down"
- android:src="@drawable/ic_light_dialog_textsize_down" />
+ android:src="@drawable/ic_action_decrement" />
+ android:src="@drawable/ic_action_increment" />
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml b/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml
index 2e9b059c55..749412e067 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/web_browser_bar_layout.xml
@@ -15,7 +15,6 @@
android:layout_height="match_parent"
android:layout_margin="3dp"
android:padding="4dp"
- android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_above="@+id/progressBar"
android:weightSum="2"
@@ -76,7 +75,6 @@
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/keyman_bar_height"
- android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:visibility="visible"
diff --git a/android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml
new file mode 100644
index 0000000000..6ceb657187
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/values-ar-rSA/strings.xml
@@ -0,0 +1,168 @@
+
+
+
+
+ مشاركة
+
+ متصفح ويب
+
+ حجم النص
+
+ المزيد
+
+ محو النص
+
+ معلومات
+
+ الإعدادات
+
+ تنزيل التحديثات
+
+ إصدار: %1$s
+
+ يتطلب Keyman إصدار 57 من Chrome أو أعلى.
+
+ تحديث Chrome
+
+ ابدأ الكتابة هنا…
+
+
+
+ حجم النص: %1$d
+
+ تكبير حجم النص
+
+ تصغير حجم النص
+
+ Text size slider
+
+ \nسيتم محو كل النص\n
+
+ إبدأ الآن
+
+ اضف لوحة مفاتيح بلغتك
+
+ تمكين Keyman لوحة المفاتيح الأساسية للنظام
+
+ إعداد Keyman لوحة المفاتيح الإفتراضية
+
+ المزيد من المعلومات
+
+ أظهر \"%1$s\" عند البدء
+
+ لتثبيت حزم لوحات المفاتيح، امنح Keyman الإذن لقراءة التخزين الخارجي.
+
+ تم رفض أذونات التخزين. قد يفشل تثبيت حزمة لوحة المفاتيح
+ Storage permission request failed. Try Keyman Settings - Install from local file
+
+ الإعدادات
+
+
+ - اللغات المثبتة (%1$d)
+ - اللغات المثبتة (%1$d)
+ - اللغات المثبتة (%1$d)
+ - اللغات المثبتة (%1$d)
+ - اللغات المثبتة (%1$d)
+ - اللغات المثبتة (%1$d)
+
+
+ تثبيت لوحة المفاتيح او القاموس
+
+ لغة العرض
+
+ تعديل ارتفاع لوحة المفاتيح
+
+ نص مفتاح المسافة
+
+ لوحة المفاتيح
+
+ اللغة
+
+ اللغة + لوحة المفاتيح
+
+ فارغ
+
+ أظهر اسم لوحة المفاتيح على مفتاح المسافة
+
+ أظهر اسم اللغة على المسافة
+
+ اللغة ولوحة المفاتيح على المسافة
+
+ لا تظهر نص على المسافة
+
+ الاهتزاز عند الكتابة
+
+ إظهار اللافتة دائماً
+
+ سيتم تنفيذها
+
+ عند إيقاف التشغيل، يظهر فقط عند تفعيل النص التنبؤي
+
+ السماح بإرسال تقارير توقف التشغيل المفاجئ عبر الشبك
+
+ عند التشغيل، سيتم إرسال تقارير التوقف المفاجئ
+
+ عند إيقاف التشغيل، لن يتم إرسال التقارير
+
+ تثبيت من keyman.com
+
+ التثبيت من ملف محلي
+
+ التثبيت من جهاز آخر
+
+ اضف لغات للوحة المفاتيح المثبتة
+
+ (من حزمة لوحة المفاتيح)
+
+ اختر حزمة لوحة مفاتيح
+
+ اختر لغة %1$s
+
+ تمت اضافة %1$s إلى %2$s
+
+ كل اللغات مثبتة مسبقا
+
+ اسحب لوحة المفاتيح لتغيير مدى ارتفاعها
+
+ دور الجهاز لضبط الوضع العمودي والوضع الأفقي
+
+ إعادة ضبط الافتراضيات
+
+ ابحث أو اكتب عنوان URL
+
+ علامات مرجعية
+
+ لا تتوفر أي علامات مرجعية
+
+ أضف علامة مرجعية
+
+ العنوان
+
+ عنوان Url
+
+ فشل في تنزيل حزمة %1$s
+
+ يتم الآن تحميل لوحة مفاتيح حزمة \n%1$s…
+
+ فشل في الإستخراج
+
+ تثبيت لوحة المفاتيح
+
+ تثبيت القاموس
+
+ ليس %1$s ملف حزمة صالحة لبرنامج Keyman.\n%2$s\"
+
+ ليس في حزمة لوحة المفاتيح لوحات مفاتيح مكيّفة لشاشات اللمس
+
+ لا يوجد نصوص تنبؤية للتنزيل
+
+ لا لوحات مفاتيح و نصوص تنبؤية للتنزيل
+
+ ليس لحزمة لوحة المفاتيح لغات ذات صلة لتثبيتها
+
+ غير صالح\ناقص البيانات الوصفية في الحزمة
+
+ تتطلب لوحة المفاتيح إصدارًا أحدث من Keyman
+
+ غير قادر على تشغيل متصفح الويب
+
diff --git a/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml
index 43e43eb4ca..428bf5ad59 100644
--- a/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml
@@ -30,9 +30,9 @@
Μέγεθος κειμένου: %1$d
- Μεγαλῶστε τὸ κείμενο
+ Μεγαλῶστε τὴν γραμματοσειρά
- Μεγαλῶστε τὸ κείμενο
+ Μικρύνετε τὴν γραμματοσειρά
Ρύθμιση μεγέθους κειμένου
@@ -68,7 +68,7 @@
Μεταβολὴ ὕψους πληκτρολογίου
- Spacebar caption
+ Λεζάντα πλήκτρου διαστήματος
Πληκτρολόγιο
@@ -80,9 +80,9 @@
\'Ονομα πληκτρολογίου στὸ πλῆκτρο διαστήματος
- \'Ονομα γλώσσας στὸ πλῆκτρο διαστήματος
+ Ὄνομα γλώσσας στὸ πλῆκτρο διαστήματος
- \'Ονομα πληκτρολογίου καὶ γλώσσας στὸ πλῆκτρο διαστήματος
+ Ὄνομα πληκτρολογίου καὶ γλώσσας στὸ πλῆκτρο διαστήματος
Καμμία λεζάντα στὸ πλῆκτρο διαστήματος
@@ -92,13 +92,13 @@
Πρὸς ὑλοποίησιν
- Ὅταν εἶναι off, ἐμφανίζεται μόνο ὅταν ἔχει ἐνεργοποιηθεῖ τὸ προγνωστικὸ κείμενο
+ OFF: ἐμφάνιση μόνον ὅταν ἔχει ἐνεργοποιηθεῖ ἡ πρόβλεψη κειμένου
Ἐπιτρέψτε τὴν ἀποστολὴ ἀναφορῶν κατάρρευσης μέσῳ δικτύου
- Ὅταν εἶναι ΟΝ, θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης
+ ON: Ἀποστολὴ ἀναφορῶν κατάρρευσης
- Ὅταν εἶναι off, δὲν θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης
+ OFF: Δὲν ἀποστέλλονται ἀναφορὲς κατάρρευσης
Ἐγκατάσταση ἀπὸ τὸ keyman.com
@@ -118,9 +118,9 @@
Ὅλες οἱ γλῶσσες ἔχουν ἤδη ἐγκατασταθεῖ
- Σύρετε τὸ πληκτρολόγιο γιὰ νὰ ἀλλάξετε τὸ ὕψος
+ Σύρετε τὸ πληκτρολόγιο γιὰ νὰ ἀλλάξετε τὸ ὕψος του
- Περιστρέψτε τὴν συσκευὴ γιὰ λειτουργία πορτραίτου καὶ τοπίου
+ Περιστρέψτε τὴν συσκευὴ γιὰ προσαρμογὴ τοῦ ὕψους σὲ λειτουργία πορτραίτου καὶ τοπίου
Ἐπαναφέρετε τὶς προεπιλεγμένες ρυθμίσεις
@@ -150,9 +150,9 @@
Τὸ πακέτο πληκτρολογίου δὲν ἔχει βελτιστοποιημένα πληκτρολόγια ἀφῆς πρὸς ἐγκατάστασιν
- Δὲν ὑπάρχει νέο προγνωστικό κείμενο πρὸς ἐγκατάστασιν
+ Δὲν ὑπάρχουν προγνώσεις κειμένων πρὸς ἐγκατάστασιν
- Δὲν ὑπάρχουν πληκτρολόγια ἢ προγνωστικό κείμενο πρὸς ἐγκατάστασιν
+ Δὲν ὑπάρχουν πληκτρολόγια ἢ προγνώσεις κειμένων πρὸς ἐγκατάστασιν
Τὸ πακέτο πληκτρολογίου δὲν ἔχει σχετικὲς μὲ αὐτὸ γλῶσσες πρὸς ἐγκατάστασιν
diff --git a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml
index cfcc2f4917..60e89def04 100644
--- a/android/KMAPro/kMAPro/src/main/res/values/dimens.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values/dimens.xml
@@ -23,4 +23,5 @@
32dp
16dp
0dp
+ 24sp
diff --git a/android/KMAPro/kMAPro/src/main/res/values/strings.xml b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
index 3cec4f2558..a1f9e7240d 100644
--- a/android/KMAPro/kMAPro/src/main/res/values/strings.xml
+++ b/android/KMAPro/kMAPro/src/main/res/values/strings.xml
@@ -113,6 +113,9 @@
Adjust keyboard height
+
+ Adjust longpress delay
+
Spacebar caption
@@ -198,6 +201,17 @@
Reset to Defaults
+
+ Delay Time: %1$.1f seconds
+
+
+ Delay time longer
+
+
+ Delay time shorter
+
+
+ Longpress delay time slider
Search or type URL
diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js
index 9c786e1777..62d396aae0 100644
--- a/android/KMEA/app/src/main/assets/android-host.js
+++ b/android/KMEA/app/src/main/assets/android-host.js
@@ -112,6 +112,17 @@ function notifyHost(event, params) {
}, 10);
}
+// Update the KeymanWeb longpress delay
+// delay is in milliseconds
+function setLongpressDelay(delay) {
+ if (keyman.osk) {
+ keyman.osk.gestureParams.longpress.waitLength = delay;
+ console.debug('setLongpressDelay('+delay+')');
+ } else {
+ window.console.log('setLongpressDelay error: keyman.osk undefined');
+ }
+}
+
// Update the KMW banner height
// h is in dpi (different from iOS)
function setBannerHeight(h) {
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java
index 6a159523a2..4783e401b8 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java
@@ -40,6 +40,7 @@ public class DisplayLanguages {
DisplayLanguageType[] languages = {
new DisplayLanguageType(unspecifiedLocale, context.getString(R.string.default_locale)),
new DisplayLanguageType("am-ET", "አማርኛ (Amharic)"),
+ new DisplayLanguageType("ar-SA", "(Arabic) العربية"),
new DisplayLanguageType("az-AZ", "Azərbaycanca (Azəricə)"),
new DisplayLanguageType("bwr-NG", "Bura-Pabir"),
new DisplayLanguageType("cs-CZ", "čeština (Czech)"),
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java
index 926ef094f7..d403a29235 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardJSHandler.java
@@ -20,6 +20,7 @@ import android.webkit.JavascriptInterface;
import static android.content.Context.VIBRATOR_SERVICE;
+import com.keyman.engine.KMManager.EnterModeType;
import com.keyman.engine.KMManager.KeyboardType;
import com.keyman.engine.data.Keyboard;
import com.keyman.engine.util.CharSequenceUtil;
@@ -153,7 +154,53 @@ public class KMKeyboardJSHandler {
}
if (s.length() > 0 && s.charAt(0) == '\n') {
- keyDownUp(KeyEvent.KEYCODE_ENTER, 0);
+ if (k.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
+ // Special handling of ENTER key
+ switch (KMManager.enterMode) {
+ // Go action
+ case GO :
+ ic.performEditorAction(EditorInfo.IME_ACTION_GO);
+ break;
+
+ // Search action
+ case SEARCH :
+ ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH);
+ break;
+
+ // Send action
+ case SEND :
+ ic.performEditorAction(EditorInfo.IME_ACTION_SEND);
+ break;
+
+ // Next action
+ case NEXT :
+ ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
+ break;
+
+ // Done action
+ case DONE :
+ ic.performEditorAction(EditorInfo.IME_ACTION_DONE);
+ break;
+
+ // Previous action
+ case PREVIOUS :
+ ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+ break;
+
+ // Messaging apps
+ case NEWLINE :
+ // Send newline and advance cursor
+ ic.commitText("\n", 1);
+ break;
+
+ // Default ENTER action
+ default:
+ keyDownUp(KeyEvent.KEYCODE_ENTER, 0);
+ }
+ } else {
+ // In-app keyboard uses default ENTER action
+ keyDownUp(KeyEvent.KEYCODE_ENTER, 0);
+ }
ic.endBatchEdit();
return;
}
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 aa1c43626a..b921ac6c0a 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
@@ -25,7 +25,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Typeface;
import android.inputmethodservice.InputMethodService;
@@ -180,6 +179,19 @@ public final class KMManager {
}
}
+ // Enum for how the System Keyboard ENTER key is handled for the EditorInfo action
+ // Reference: https://developer.android.com/reference/android/view/inputmethod/EditorInfo#summary
+ public enum EnterModeType {
+ GO, // Go action
+ SEARCH, // Search action
+ SEND, // Send action
+ NEXT, // Next action
+ DONE, // Done action
+ PREVIOUS, // Previous action
+ NEWLINE, // Send newline character
+ DEFAULT, // Default ENTER action
+ }
+
protected static InputMethodService IMService;
private static boolean debugMode = false;
@@ -218,6 +230,9 @@ public final class KMManager {
// regardless what the Settings preference is.
private static boolean mayPredictOverride = false;
+ // Determine how system keyboard handles ENTER key
+ public static EnterModeType enterMode = EnterModeType.DEFAULT;
+
// Boolean for whether a keyboard can send embedded KMW crash reports to Sentry
// When maySendCrashReport is false, KMW will still attempt to send crash reports, but it
// will be blocked.
@@ -248,6 +263,8 @@ public final class KMManager {
public static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait";
public static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape";
+ public static final String KMKey_LongpressDelay = "longpressDelay";
+
public static final String KMKey_CustomHelpLink = "CustomHelpLink";
public static final String KMKey_KMPLink = "kmp";
public static final String KMKey_UserKeyboardIndex = "UserKeyboardIndex";
@@ -293,6 +310,9 @@ public final class KMManager {
public static final String KMDefault_DictionaryVersion = "0.1.4";
public static final String KMDefault_DictionaryKMP = KMDefault_DictionaryPackageID + FileUtils.MODELPACKAGE;
+ // Default KeymanWeb longpress delay in milliseconds
+ public static final int KMDefault_LongpressDelay = 500;
+
// Keyman files
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
protected static final String KMFilename_JSEngine = "keymanweb-webview.js";
@@ -1251,6 +1271,61 @@ public final class KMManager {
}
}
+ /**
+ * Sets enterMode which specifies how the System keyboard ENTER key is handled
+ *
+ * @param imeOptions EditorInfo.imeOptions
+ * @param inputType InputType
+ */
+ public static void setEnterMode(int imeOptions, int inputType) {
+ if ((inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0) {
+ enterMode = EnterModeType.NEWLINE;
+ return;
+ }
+
+ int imeActions = imeOptions & EditorInfo.IME_MASK_ACTION;
+ EnterModeType value = EnterModeType.DEFAULT;
+
+ switch (imeActions) {
+ case EditorInfo.IME_ACTION_GO:
+ value = EnterModeType.GO;
+ break;
+
+ case EditorInfo.IME_ACTION_SEARCH:
+ value = EnterModeType.SEARCH;
+ break;
+
+ case EditorInfo.IME_ACTION_SEND:
+ value = EnterModeType.SEND;
+ break;
+
+ case EditorInfo.IME_ACTION_NEXT:
+ value = EnterModeType.NEXT;
+ break;
+
+ case EditorInfo.IME_ACTION_DONE:
+ value = EnterModeType.DONE;
+ break;
+
+ case EditorInfo.IME_ACTION_PREVIOUS:
+ value = EnterModeType.PREVIOUS;
+ break;
+
+ default:
+ value = EnterModeType.DEFAULT;
+ }
+
+ enterMode = value;
+ }
+
+ /**
+ * Get the value of enterMode
+ * @return EnterModeType
+ */
+ public static EnterModeType getEnterMode() {
+ return enterMode;
+ }
+
/**
* Sets mayPredictOverride true if the InputType field is a hidden password text field
* (either TYPE_TEXT_VARIATION_PASSWORD or TYPE_TEXT_VARIATION_WEB_PASSWORD
@@ -2010,6 +2085,45 @@ public final class KMManager {
return Configuration.ORIENTATION_UNDEFINED;
}
+ /**
+ * Get the longpress delay (in milliseconds) from stored preference. Defaults to 500ms
+ * @return int - longpress delay in milliseconds
+ */
+ public static int getLongpressDelay() {
+ SharedPreferences prefs = appContext.getSharedPreferences(
+ appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
+
+ return prefs.getInt(KMKey_LongpressDelay, KMDefault_LongpressDelay);
+ }
+
+ /**
+ * Set the longpress delay (in milliseconds) as a stored preference.
+ * @param longpressDelay - int longpress delay in milliseconds
+ */
+ public static void setLongpressDelay(int longpressDelay) {
+ SharedPreferences prefs = appContext.getSharedPreferences(
+ appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(KMKey_LongpressDelay, longpressDelay);
+ editor.commit();
+ }
+
+ /**
+ * Sends options to the KeymanWeb keyboard.
+ * 1. number of milliseconds to trigger a longpress gesture.
+ * This method requires a keyboard to be loaded for the value to take effect.
+ */
+ public static void sendOptionsToKeyboard() {
+ int longpressDelay = getLongpressDelay();
+ if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) {
+ InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay));
+ }
+
+ if (SystemKeyboard != null) {
+ SystemKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay));
+ }
+ }
+
public static int getBannerHeight(Context context) {
int bannerHeight = 0;
if (InAppKeyboard != null && InAppKeyboard.getBanner() != BannerType.BLANK) {
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java
index c2daaea303..1897920b73 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KeyboardInfoActivity.java
@@ -88,7 +88,7 @@ public final class KeyboardInfoActivity extends BaseActivity {
hashMap = new HashMap();
final String customHelpLink = kbd.getHelpLink();
// Check if app declared FileProvider
- String icon = String.valueOf(R.drawable.ic_arrow_forward);
+ String icon = String.valueOf(R.drawable.ic_action_forward);
// Don't show help link arrow if it's a local help file and File Provider unavailable,
// or custom help doesn't exist
if ( (customHelpLink != null && ! KMManager.isTestMode() &&
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java
index ef1b180c52..ce81927699 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/ModelInfoActivity.java
@@ -81,7 +81,7 @@ public final class ModelInfoActivity extends BaseActivity {
final String customHelpLink = lm.getHelpLink();
// Check if app declared FileProvider
// Currently, model help only available if custom link exists
- icon = String.valueOf(R.drawable.ic_arrow_forward);
+ icon = String.valueOf(R.drawable.ic_action_forward);
// Don't show help link arrow if both custom help and File Provider don't exist
// TODO: Update this when model help available on help.keyman.com
if ( (!customHelpLink.equals("") && !FileProviderUtils.exists(context)) ||
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java b/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java
index 8b74e261a3..4595aae1e7 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/ModelPickerActivity.java
@@ -321,7 +321,7 @@ public final class ModelPickerActivity extends BaseActivity {
// once it has its own backing Dataset instance.
// Is this an installed model or not?
- holder.imgDetails.setImageResource(R.drawable.ic_arrow_forward);
+ holder.imgDetails.setImageResource(R.drawable.ic_action_forward);
if (KeyboardPickerActivity.containsLexicalModel(context, modelKey)) {
holder.imgInstalled.setImageResource(R.drawable.ic_check);
} else {
diff --git a/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_back.png
new file mode 100644
index 0000000000..7922c65016
Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-hdpi/ic_arrow_back.png differ
diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_back.png
new file mode 100644
index 0000000000..0c1eb7a417
Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-mdpi/ic_arrow_back.png differ
diff --git a/android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_back.png
new file mode 100644
index 0000000000..e8e7047ce7
Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xhdpi/ic_arrow_back.png differ
diff --git a/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png
new file mode 100644
index 0000000000..831448cb17
Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png differ
diff --git a/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png
new file mode 100644
index 0000000000..5794fbcaac
Binary files /dev/null and b/android/KMEA/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png differ
diff --git a/android/KMEA/app/src/main/res/drawable/ic_action_back.xml b/android/KMEA/app/src/main/res/drawable/ic_action_back.xml
new file mode 100644
index 0000000000..cae0c5eed4
--- /dev/null
+++ b/android/KMEA/app/src/main/res/drawable/ic_action_back.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward.xml b/android/KMEA/app/src/main/res/drawable/ic_action_forward.xml
similarity index 67%
rename from android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward.xml
rename to android/KMEA/app/src/main/res/drawable/ic_action_forward.xml
index 15efecf557..2ffd9c4540 100644
--- a/android/KMAPro/kMAPro/src/main/res/drawable/ic_action_forward.xml
+++ b/android/KMEA/app/src/main/res/drawable/ic_action_forward.xml
@@ -1,4 +1,5 @@
+ android:tint="@android:color/black"
+ android:autoMirrored="true"/>
diff --git a/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml b/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml
index 953c74e692..16e2c3d47a 100644
--- a/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml
+++ b/android/KMEA/app/src/main/res/layout/help_bubble_layout.xml
@@ -15,8 +15,8 @@
android:layout_gravity="center"
android:gravity="center"
android:paddingBottom="10dp"
- android:paddingLeft="2dp"
- android:paddingRight="2dp"
+ android:paddingStart="2dp"
+ android:paddingEnd="2dp"
android:text="@string/help_bubble_text"
android:textColor="#404040"
android:textSize="12sp" />
diff --git a/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml b/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml
index ac07621cdd..a2116bcb4c 100644
--- a/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml
+++ b/android/KMEA/app/src/main/res/layout/models_list_row_layout.xml
@@ -44,7 +44,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
- android:src="@drawable/ic_arrow_forward"
+ android:src="@drawable/ic_action_forward"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="12dp"
diff --git a/android/KMEA/app/src/main/res/values-ar-rSA/strings.xml b/android/KMEA/app/src/main/res/values-ar-rSA/strings.xml
new file mode 100644
index 0000000000..cb2e9f0d51
--- /dev/null
+++ b/android/KMEA/app/src/main/res/values-ar-rSA/strings.xml
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+ - لوحة مفاتيح
+ - لوحات مفاتيح
+ - لوحتا مفاتيح
+ - لوحات مفاتيح
+ - لوحة مفاتيح
+ - لوحة مفاتيح
+
+
+
+ - طريقة إداخل أخرى
+ - طريقة إدخال أخرى
+ - طريقتا إدخال أخريتان
+ - طرق إدخال
+ - طريقة إدخال أخرى
+ - طريقة إدخال أخرى
+
+
+ إضافة لوحة مفاتيح جديدة
+
+ اللغات المثبتة
+
+ إعدادات %1$s
+
+ إضافة
+
+ عودة
+
+ إلغاء
+
+ إغلاق
+
+ إغلاق Keyman
+
+ الأمام
+
+ طريقة الإدخال التالية
+
+ تحميل
+
+ تثبيت
+
+ لاحقًا
+
+ التالي
+
+ موافق
+
+ تحديث
+
+ لا يوجد اتصال بالإنترنت
+
+ لا يمكن الاتصال بخادم Keyman!
+
+ هل ترغب بإزالة لوحة المفاتيح هذه؟
+
+ هل ترغب بتحميل آخر إصدار من لوحة المفاتيح هذه؟
+
+ هل ترغب بتحديث لوحات المفاتيح والقواميس الآن؟
+
+ تحديثات الموارد
+
+ تتوفر تحديثات موارد
+
+ %1$s (تحديث متوفر)
+
+ تحديثات متوفرة للوحة مفاتيح %1$s: %2$s
+
+ تحديثات متوفرة لقاموس %1$s: %2$s
+
+ إصدار لوحة المفاتيح
+
+ رابط المساعدة
+
+ إزالة تثبيت لوحة المفاتيح
+
+ [new] %1$s
+
+ امسح هذا الرمز لتحميل \nلوحة المفاتيح هذه على جهاز آخر
+
+ أهلًا بك في %1$s
+
+ مكتبة FileProvider مطلوبة لعرض ملفات المساعدة: %1$s
+
+ خطأ كبير في لوحة مفاتيح مع %1$s:%2$s لأجل لغة %3$s. سيتم تحميل لوحة المفاتيح الأساسية.
+
+ خطأ في لوحة مفاتيح %1$s:%2$s لأجل لغة %3$s.
+
+ يجري التحقق من قاموس ذي صلة للتحميل
+ لا يمكن الاتصال بخادم Keyman للتحقق من تنزيل القاموس المرتبط
+
+ هل ترغب بتحملي آخر إصدار من هذا القاموس؟
+
+ لا يوجد قاموس لتحميله
+
+ كتالوغ المصدر غير متوفر
+
+ بدء تحديث الكتالوغ في الخلفية
+
+ الكتالوغ لا يزال يتم تحميله، يرجى المحاولة مجددًا بعد قليل!
+
+ البحث عن مصدر
+
+ بدء تحميل لوحة المفاتيح في الخلفية
+
+ يجري بالفعل تحميل لوحة المفاتيح المختارة، يرجى المحاولة مجددًا بعد قليل!
+
+ اكتمل تحميل لوحة المفاتيح!
+
+ بدأ تحميل القاموس في الخلفية
+
+ يجري بالفعل تحميل القاموس، يرجى المحاولة مجددًا بعد قليل!
+
+ اكتمل تحميل القاموس.
+
+ فشل التحميل
+
+ فشل استرجاع الملف المحمل
+
+ فشل الوصول إلى الخادم!
+
+ "جميع الموارد محدّثة!"
+
+ فشل تحديث واحد أو أكثر من المصادر!
+
+ تم تحديث الموارد بنجاح!
+
+ إصدار القاموس
+
+ إزالة تثبيت القاموس
+
+ هل ترغب بإزالة هذا القاموس؟
+
+ تم حذف القاموس
+
+ تم تثبيت لوحة مفاتيح %1$s
+
+ تم حذف لوحة المفاتيح
+
+ تفعيل التصحيحات
+
+ تفعيل التوقعات
+
+ القاموس
+
+ - Dictionaries
+ - Dictionary
+ - Dictionaries
+ - Dictionaries
+ - Dictionaries
+ - Dictionaries
+
+
+ البحث عن قاموس متوفر
+ تحقق من وجود قواميس على الإنترنت
+
+ قاموس: %1$s
+
+ قواميس %1$s
+
+
+ تم تثبيت القاموس
+
+
+ - (%1$d اوحات)
+ - (لوحة مفاتيح %1$d)
+ - (لوحتا مفاتيح %1$d)
+ - (%1$d لوحة مفاتيح)
+ - (%1$d لوحة مفاتيح)
+ - (%1$d لوحة مفاتيح)
+
+
+ اللغة الأصلية
+
+
+
+ حذف
+
+
+ اضغط هنا لتغيير لوحة المفاتيح
+
+ غير قادر على تشغيل متصفح الويب
+
diff --git a/android/KMEA/app/src/main/res/values-b+el/strings.xml b/android/KMEA/app/src/main/res/values-b+el/strings.xml
index 871171d63f..75c774ebd1 100644
--- a/android/KMEA/app/src/main/res/values-b+el/strings.xml
+++ b/android/KMEA/app/src/main/res/values-b+el/strings.xml
@@ -27,7 +27,7 @@
Κλεῖστε
- Κλεῖστε τὸ Κῆμαν
+ Κλεῖστε τὸ Keyman
Προχωρῆστε
@@ -53,11 +53,11 @@
Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ πληκτρολογίου;
- Θά θέλατε νὰ ἐνημερώσετε τώρα πληκτρολόγια καὶ λεξικά;
+ Θὰ θέλατε νὰ ἐνημερώσετε τώρα πληκτρολόγια καὶ λεξικά;
- Ἐνημερώσεις Πόρων
+ Ἐνημερώσεις
- Διαθέσιμες Ἐνημερώσεις Πόρων
+ Διαθέσιμες Ἐνημερώσεις
%1$s (Διαθέσιμη Ἐνημέρωση)
@@ -67,13 +67,13 @@
Ἔκδοση πληκτρολογίου
- Σύνδεσμος βοηθείας
+ Βοήθεια
Ἀπεγκαταστῆστε πληκτρολόγιο
[νέο] %1$s
- Σαρῶστε αὐτὸν τὸν κωδικό γιὰ νὰ φορτώσετε\nαὐτὸ τὸ πληκτρολόγιο σὲ ἄλλη συσκευή
+ Σαρῶστε αὐτὸν τὸν κωδικὸ γιὰ νὰ φορτώσετε\nαὐτὸ τὸ πληκτρολόγιο σὲ ἄλλη συσκευή
Καλωσορίσατε στὸ %1$s
@@ -81,22 +81,22 @@
Μοιραῖο σφάλμα πληκτρολογίου στὸ %1$s:%2$s γιὰ τὴν %3$s γλῶσσα. Φορτώνεται προεπιλεγμένο πληκτρολόγιο.
- Error in keyboard %1$s:%2$s for %3$s language.
+ Σφάλμα στὸ πληκτρολόγιο %1$s:%2$s γιὰ τὴν γλῶσσα \"%3$s\".
- Ἔλεγχος συσχετισμένου λεξικοῦ πρὸς λῆψιν
- Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ Κῆμαν γιὰ τὸν ἔλεγχο συσχετισμένου λεξικοῦ πρὸς λῆψιν
+ Ἀναζήτηση σχετικοῦ λεξικοῦ πρὸς μεταφόρτωσιν
+ Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ Keyman γιὰ τὴν εὕρεση σχετικοῦ λεξικοῦ πρὸς μεταφόρτωσιν
Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ λεξικοῦ;
- Δὲν ὑπάρχει λεξικὸ πρὸς λῆψιν
+ Δὲν ὑπάρχει λεξικὸ πρὸς μεταφόρτωσιν
Μὴ διαθέσιμος κατάλογος πόρων
Ἔχει ξεκινήσει ἐνημέρωση καταλόγου στὸ παρασκήνιο
- Ἡ λήψη τοῦ καταλόγου συνεχίζεται· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο!
+ Ἡ μεταφόρτωση τοῦ καταλόγου συνεχίζεται· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο!
- Ἔλεγχος πόρου σὲ ἐξέλιξη
+ Ἀναζήτηση πόρου σὲ ἐξέλιξη
Ἡ λήψη τοῦ πληκτρολογίου ἔχει ξεκινήσει στὸ παρασκήνιο
@@ -112,7 +112,7 @@
Ἡ λήψη ἀπέτυχε
- Ἀποτυχία ἀνακτήσεως ληφθέντος ἀρχείου
+ Ἀποτυχία ἀνακλήσεως ληφθέντος ἀρχείου
Ἀποτυχία προσβάσεως στὸν διακομιστή!
@@ -120,11 +120,11 @@
Ἕνας ἢ περισσότεροι πόροι ἀπέτυχαν νὰ ἐνημερωθοῦν!
- Οἱ πόροι ἐνημερώθηκαν ἐπιτυχῶς!
+ Ὅλες οἱ ἐνημερώσεις ὁλοκληρώθηκαν ἐπιτυχῶς!
Ἔκδοση λεξικοῦ
- Ἀπεγκαταστῆτε λεξικό
+ Ἀπεγκαταστῆστε λεξικό
Θὰ θέλατε νὰ διαγράψετε αὐτὸ τὸ λεξικό;
@@ -136,16 +136,16 @@
Ἐνεργοποιῆστε τὶς διορθώσεις
- Ἐνεργοποιῆστε προβλέψεις
+ Ἐνεργοποιῆστε προβλέψεις κειμένου
- Λεξικά
+ Λεξικό
- Λεξικό
- Λεξικά
- Ἔλεγχος διαθεσίμου λεξικοῦ
- Ἔλεγχος λεξικῶν ὀνλάϊν
+ Ἀναζήτηση διαθεσίμου λεξικοῦ
+ Ἀναζήτηση λεξικῶν ὀνλάϊν
Λεξικό: %1$s
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000000..a455d3307f
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+## START STANDARD BUILD SCRIPT INCLUDE
+# adjust relative paths as necessary
+THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
+. "${THIS_SCRIPT%/*}/resources/build/builder.inc.sh"
+## END STANDARD BUILD SCRIPT INCLUDE
+
+builder_describe "Keyman -- all projects" \
+ ":common" \
+ ":core" \
+ ":android" \
+ ":ios" \
+ ":linux" \
+ ":mac" \
+ ":web" \
+ ":windows" \
+ ":developer" \
+ clean \
+ configure \
+ build \
+ test \
+ install \
+ publish
+
+builder_describe_platform \
+ :android android-studio \
+ :ios mac \
+ :linux linux \
+ :mac mac \
+ :windows win
+
+builder_parse "$@"
+
+#-------------------------------------------------------------------------------------------------------------------
+
+builder_run_child_actions clean configure build test publish install
diff --git a/common/build.sh b/common/build.sh
index 2d3c2ff8c4..5119bca8ac 100755
--- a/common/build.sh
+++ b/common/build.sh
@@ -16,6 +16,11 @@ builder_describe "Keyman common and resources modules" \
build \
test
+builder_describe_platform \
+ :linux linux \
+ :mac mac \
+ :windows win
+
builder_parse "$@"
#-------------------------------------------------------------------------------------------------------------------
diff --git a/common/cpp/km_u16.cpp b/common/cpp/km_u16.cpp
new file mode 100644
index 0000000000..84a6c4a75e
--- /dev/null
+++ b/common/cpp/km_u16.cpp
@@ -0,0 +1,389 @@
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
+ * std::u16string functions and string conversion utility functions
+ */
+
+#include
+#include "utfcodec.hpp"
+#include "km_u16.h"
+
+/** string <- wstring
+ * @brief Obtain a std::string from a std::wstring
+ * @param wstr the std::wstring to be converted
+ * @return a std::string
+ */
+std::string string_from_wstring(std::wstring const wstr) {
+ return convert((const std::wstring)wstr);
+}
+
+/** wstring <- string
+ * @brief Obtain a std::wstring from a std::string
+ * @param str the std::string to be converted
+ * @return a std::wstring
+ */
+std::wstring wstring_from_string(std::string const str) {
+ return convert((const std::string)str);
+}
+
+/** u16string <- string
+ * @brief Obtain a std::u16string from a std::string
+ * @param str the std::string to be converted
+ * @return a std::u16string
+ */
+std::u16string u16string_from_string(std::string const str) {
+ return convert((const std::string)str);
+}
+
+/** string <- u16string
+ * @brief Obtain a std::string from a std::u16string
+ * @param str16 the std::u16string to be converted
+ * @return a std::string
+ */
+std::string string_from_u16string(std::u16string const str16) {
+ return convert((const std::u16string)str16);
+}
+
+/** wstring <- u16string
+ * @brief Obtain a std::wstring from a std::u16string
+ * @param str16 the std::u16string to be converted
+ * @return a std::wstring
+ */
+std::wstring wstring_from_u16string(std::u16string const str16) {
+ return convert((const std::u16string)str16);
+}
+
+/** u16string <- wstring
+ * @brief Obtain a std::u16string from a std::wstring
+ * @param wstr the std::wstring to be converted
+ * @return a std::u16string
+ */
+std::u16string u16string_from_wstring(std::wstring const wstr) {
+ return convert((const std::wstring)wstr);
+}
+
+/**
+ * @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst
+ * @param dst destination
+ * @param sz nr of characters to be copied
+ * @param fmt source to convert and copy
+ */
+void u16sprintf(KMX_WCHAR* dst, const size_t sz, const wchar_t* fmt, ...) {
+ wchar_t* wbuf = new wchar_t[sz];
+ va_list args;
+ va_start(args, fmt);
+ vswprintf(wbuf, sz, fmt, args);
+ va_end(args);
+
+ std::u16string u16str = u16string_from_wstring(wbuf);
+ u16ncpy(dst, u16str.c_str(), sz);
+ delete[] wbuf;
+}
+
+/**
+ * @brief Convert u16string to long integer
+ * @param str u16string beginning with the representation of an integral number.
+ * @param endptr Reference to the next character in str
+ * @param base Numerical base (radix) that determines the valid characters and their interpretation
+ * @return a long
+ */
+long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base) {
+ auto s = string_from_u16string(str);
+ char* t;
+ long int result = strtol(s.c_str(), &t, base);
+ if (endptr != nullptr)
+ *endptr = (KMX_WCHAR*)str + (t - s.c_str());
+ return result;
+}
+
+std::string toHex(int num1) {
+ if (num1 == 0)
+ return "0";
+ int num = num1;
+ std::string s = "";
+ while (num) {
+ int temp = num % 16;
+ if (temp <= 9)
+ s += (48 + temp);
+ else
+ s += (87 + temp);
+ num = num / 16;
+ }
+ std::reverse(s.begin(), s.end());
+ return s;
+}
+
+/**
+ * @brief Append max characters from u16string
+ * @param dst Pointer to the destination array
+ * @param src u16string to be appended
+ * @param max Maximum number of characters to be appended.
+ * @return Pointer to dst
+ */
+const KMX_WCHAR* u16ncat(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max) {
+ KMX_WCHAR* o = dst;
+ dst = (KMX_WCHAR*)u16chr(dst, 0);
+ // max -= (dst-o);
+ while (*src && max > 0) {
+ *dst++ = *src++;
+ max--;
+ }
+ if (max > 0)
+ *dst = 0;
+ return o;
+}
+
+/**
+ * @brief Find last '/' or '\\' in an array of char16_t
+ * @param name Pointer to the source
+ * @return Pointer to the last slash/backslash
+ */
+const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* name) {
+ const KMX_WCHAR* cp = NULL;
+ cp = u16rchr(name, '\\');
+ if (cp == NULL)
+ cp = u16rchr(name, '/');
+ return cp;
+}
+
+/**
+ * @brief Find last '/' or '\\' in an array of char
+ * @param name Pointer to the source
+ * @return Pointer to the last slash/backslash
+ */
+KMX_CHAR* strrchr_slash(KMX_CHAR* name) {
+ KMX_CHAR* cp = NULL;
+ cp = strrchr(name, '\\');
+ if (cp == NULL)
+ cp = strrchr(name, '/');
+ return cp;
+}
+
+/**
+ * @brief Locate last occurrence of character in u16string
+ * @param p Pointer to the source
+ * @param ch The character to be found
+ * @return A pointer to the last occurrence of character in u16str
+ */
+const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch) {
+ const KMX_WCHAR* p_end = p + u16len(p) - 1;
+
+ if (ch == '\0')
+ return p_end + 1;
+ while (p_end >= p) {
+ if (*p_end == ch)
+ return p_end;
+ p_end--;
+ }
+ return NULL;
+}
+
+/**
+ * @brief Locate first occurrence of character in u16string
+ * @param p Pointer to the source
+ * @param ch The character to be found
+ * @return A pointer to the first occurrence of character in u16str
+ */
+const KMX_WCHAR* u16chr(const KMX_WCHAR* p, KMX_WCHAR ch) {
+ while (*p) {
+ if (*p == ch)
+ return p;
+ p++;
+ }
+ return ch == 0 ? p : NULL;
+}
+
+/**
+ * @brief Copy the u16string pointed to by scr into the array pointed to by dst
+ * @param dst Pointer to the destination
+ * @param src Pointer to the source to be copied
+ * @return Pointer to dst
+ */
+const KMX_WCHAR* u16cpy(KMX_WCHAR* dst, const KMX_WCHAR* src) {
+ KMX_WCHAR* o = dst;
+ while (*src) {
+ *dst++ = *src++;
+ }
+ *dst = 0;
+ return o;
+}
+
+/**
+ * @brief Copy max characters of the u16string pointed to by src into the array pointed by dst
+ * @param dst Pointer to the destination
+ * @param src Pointer to the source to be copied
+ * @param max Maximum number of characters to be copied
+ * @return Pointer to dst
+ */
+const KMX_WCHAR* u16ncpy(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max) {
+ KMX_WCHAR* o = dst;
+ while (*src && max > 0) {
+ *dst++ = *src++;
+ max--;
+ }
+ if (max > 0) {
+ *dst = 0;
+ }
+ return o;
+}
+
+/**
+ * @brief Return the length of the u16string str
+ * @param p Pointer to the source
+ * @return The length of u16string
+ */
+size_t u16len(const KMX_WCHAR* p) {
+ int i = 0;
+ while (*p) {
+ p++;
+ i++;
+ }
+ return i;
+}
+
+/**
+ * @brief Compare two u16strings
+ * @param p Pointer one u16string
+ * @param q Pointer another u16string
+ * @return 0 if strings are equal
+ * ! = 0 if unequal
+ */
+int u16cmp(const KMX_WCHAR* p, const KMX_WCHAR* q) {
+ while (*p && *q) {
+ if (*p != *q)
+ return *p - *q;
+ p++;
+ q++;
+ }
+ return *p - *q;
+}
+
+/**
+ * @brief Case insensitive comparison of up to count characters in two strings
+ * @param p Pointer one u16string
+ * @param q Pointer another u16string
+ * @param count Maximum number of characters to compare
+ * @return 0 if strings are equal
+ * ! = 0 if unequal
+ */
+int u16nicmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count) {
+ while (*p && *q && count) {
+ if (toupper(*p) != toupper(*q))
+ return *p - *q;
+ p++;
+ q++;
+ count--;
+ }
+ if (count)
+ return *p - *q;
+ return 0;
+}
+
+/**
+ * @brief Case insensitive comparison of two strings
+ * @param p Pointer one u16string
+ * @param q Pointer another u16string
+ * @return 0 if strings are equal
+ * ! = 0 if unequal
+ */
+int u16icmp(const KMX_WCHAR* p, const KMX_WCHAR* q) {
+ while (*p && *q) {
+ if (toupper(*p) != toupper(*q))
+ return *p - *q;
+ p++;
+ q++;
+ }
+ return *p - *q;
+}
+
+/**
+ * @brief Comparison of up to count characters in two strings
+ * @param p Pointer one u16string
+ * @param q Pointer another u16string
+ * @param count Maximum number of characters to compare
+ * @return 0 if strings are equal
+ * ! = 0 if unequal
+ */
+int u16ncmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count) {
+ while (*p && *q && count) {
+ if (*p != *q)
+ return *p - *q;
+ p++;
+ q++;
+ count--;
+ }
+ if (count)
+ return *p - *q;
+ return 0;
+}
+
+/**
+ * @brief Split u16string into tokens
+ * @param p Pointer to u16string to parse.
+ * @param ch the delimiter character
+ * @param ctx the remaining string after the first delimiter
+ * @return Pointer to the first token in p
+ */
+KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx) {
+ if (!p) {
+ p = *ctx;
+ if (!p)
+ return NULL;
+ }
+
+ KMX_WCHAR* q = p;
+ while (*q && *q != ch) {
+ q++;
+ }
+ if (*q) {
+ *q = 0;
+ q++;
+ while (*q == ch)
+ q++;
+ *ctx = q;
+ } else {
+ *ctx = NULL;
+ }
+ return *p ? p : NULL;
+}
+
+/**
+ * @brief Split u16string into tokens
+ * @param p Pointer to u16string to parse.
+ * @param delimiters an array of delimiter characters
+ * @param ctx the remaining string after the first delimiter
+ * @return Pointer to the first token in p
+ */
+KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx) {
+ if (!p) {
+ p = *ctx;
+ if (!p)
+ return NULL;
+ }
+
+ KMX_WCHAR* q = p;
+ while (*q && !u16chr(delimiters, *q)) {
+ q++;
+ }
+ if (*q) {
+ *q = 0;
+ q++;
+ while (*q && u16chr(delimiters, *q))
+ q++;
+ *ctx = q;
+ } else {
+ *ctx = NULL;
+ }
+ return *p ? p : NULL;
+}
+
+/**
+ * @brief Convert a u16string to a double
+ * @param str Pointer to u16string
+ * @return double value equivalent to the string
+ */
+double u16tof(KMX_WCHAR* str16) {
+ char* pEnd;
+ std::string str = string_from_u16string(str16);
+ return strtof(str.c_str(), &pEnd);
+}
diff --git a/core/src/utfcodec.cpp b/common/cpp/utfcodec.cpp
similarity index 100%
rename from core/src/utfcodec.cpp
rename to common/cpp/utfcodec.cpp
diff --git a/common/include/km_u16.h b/common/include/km_u16.h
new file mode 100644
index 0000000000..f0c2c2d16c
--- /dev/null
+++ b/common/include/km_u16.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include
+#include
+#include
+
+/** @brief Obtain a std::string from a std::wstring */
+std::string string_from_wstring(std::wstring const wstr);
+
+/** @brief Obtain a std::wstring from a std::string */
+std::wstring wstring_from_string(std::string const str);
+
+/** @brief Obtain a std::u16string from a std::string */
+std::u16string u16string_from_string(std::string const str);
+
+/** @brief Obtain a std::string from a std::u16string */
+std::string string_from_u16string(std::u16string const str16);
+
+/** @brief Obtain a std::wstring from a std::u16string */
+std::wstring wstring_from_u16string(std::u16string const str16);
+
+/** @brief Obtain a std::u16string from a std::wstring */
+std::u16string u16string_from_wstring(std::wstring const wstr);
+
+/** @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst */
+void u16sprintf(KMX_WCHAR* dst, const size_t sz, const wchar_t* fmt, ...);
+
+/** @brief Return the length of the u16string str */
+size_t u16len(const KMX_WCHAR* p);
+
+/** @brief Compare two u16strings */
+int u16cmp(const KMX_WCHAR* p, const KMX_WCHAR* q);
+
+/** @brief Case insensitive comparison of two strings */
+int u16icmp(const KMX_WCHAR* p, const KMX_WCHAR* q);
+
+/** @brief Comparison of up to count characters in two strings */
+int u16ncmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count);
+
+/** @brief Case insensitive comparison of up to count characters in two strings */
+int u16nicmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count);
+
+/** @brief Copy max characters of the u16string pointed to by src into the array pointed to by dst */
+const KMX_WCHAR* u16ncpy(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max);
+
+/** @brief Copy the u16string pointed to by src into the array pointed to by dst */
+const KMX_WCHAR* u16cpy(KMX_WCHAR* dst, const KMX_WCHAR* src);
+
+/** @brief Locate last occurrence of character in u16string */
+const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch);
+
+/** @brief Locate first occurrence of character in u16string */
+const KMX_WCHAR* u16chr(const KMX_WCHAR* p, KMX_WCHAR ch);
+
+/** @brief Append max characters from u16string */
+const KMX_WCHAR* u16ncat(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max);
+
+/** @brief Split u16string into tokens */
+KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx);
+
+/** @brief Split u16string into tokens */
+KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx);
+
+/** @brief Convert a u16string to a double */
+long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base);
+
+/** @brief Convert a u16string to a double */
+double u16tof(KMX_WCHAR* str);
+
+/** @brief find last '/' or '\\' in an array of char */
+KMX_CHAR* strrchr_slash(KMX_CHAR* Name);
+
+/** @brief find last '/' or '\\' in an array of char16_t */
+const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name);
+
+std::string toHex(int num1);
diff --git a/core/src/utfcodec.hpp b/common/include/utfcodec.hpp
similarity index 100%
rename from core/src/utfcodec.hpp
rename to common/include/utfcodec.hpp
diff --git a/common/models/templates/src/index.ts b/common/models/templates/src/index.ts
index 8563004fef..0966fb4494 100644
--- a/common/models/templates/src/index.ts
+++ b/common/models/templates/src/index.ts
@@ -3,5 +3,5 @@ export {
transformToSuggestion, defaultApplyCasing
} from "./common.js";
export { default as QuoteBehavior } from "./quote-behavior.js";
-export { Tokenization, tokenize, getLastPreCaretToken, wordbreak } from "./tokenization.js";
+export { getLastPreCaretToken, Token, Tokenization, tokenize, wordbreak } from "./tokenization.js";
export { default as TrieModel, TrieModelOptions } from "./trie-model.js";
\ No newline at end of file
diff --git a/common/models/templates/src/tokenization.ts b/common/models/templates/src/tokenization.ts
index 2aa5c392e6..2fb07da357 100644
--- a/common/models/templates/src/tokenization.ts
+++ b/common/models/templates/src/tokenization.ts
@@ -1,18 +1,23 @@
// While we _could_ define this within @keymanapp/models-wordbreakers instead, it's probably
// better to leave that package as _just_ the wordbreakers.
+export interface Token {
+ text: string,
+ isWhitespace?: boolean
+}
+
export interface Tokenization {
/**
* An array of tokens to the left of the caret. If the caret is in the middle of a token,
* only the part to the left of the caret is included.
*/
- left: USVString[],
+ left: Token[],
/**
* An array of tokens to the right of the caret. If the caret is in the middle of a token,
* only the part to the right of the caret is included.
*/
- right: USVString[],
+ right: Token[],
/**
* A flag indicating whether or not the caret's position in the context caused a token
@@ -22,21 +27,74 @@ export interface Tokenization {
caretSplitsToken: boolean
}
-export function tokenize(wordBreaker: WordBreakingFunction, context?: Partial): Tokenization {
+export function tokenize(
+ wordBreaker: WordBreakingFunction,
+ context: Partial,
+ options?: {
+ /** Characters to rejoin to preceding tokens if found immediately pre-caret. */
+ rejoins?: string[]
+ }
+): Tokenization {
+ // The Unicode word-breaker algorithm looks for places where it's "safe" to
+ // split a word across lines, operating upon _completed_ words. There are
+ // some cases where, if placed mid-word, it would add a boundary that does not
+ // exist at the end of the word. The single-quote character is one such
+ // location - it's hard to tell if `can'` is the end of a quote or the prefix
+ // to `can't`. So, if `'` is immediately pre-caret, we "rejoin" it.
+ const rejoins = options?.rejoins || ["'"];
context = context || {
left: undefined,
startOfBuffer: undefined,
endOfBuffer: undefined
};
- let leftSpans = wordBreaker(context.left || '') || [];
- let rightSpans = wordBreaker(context.right || '') || [];
+ const leftSpans = wordBreaker(context.left || '') || [];
+ const rightSpans = wordBreaker(context.right || '') || [];
- let leftTail: Span;
- if(leftSpans.length > 0) {
- leftTail = leftSpans[leftSpans.length - 1];
+ const tokenization: Tokenization = {
+ left: [],
+ right: [],
+ caretSplitsToken: false
}
+ // New step 1: process left-context.
+ let currentIndex = 0;
+ while(leftSpans.length > 0) {
+ const nextSpan = leftSpans[0];
+ if(Math.max(nextSpan.start, currentIndex) != currentIndex) {
+ const nextIndex = Math.max(currentIndex, nextSpan.start);
+ // Implicit whitespace span!
+ tokenization.left.push({
+ text: context.left!.substring(currentIndex, nextIndex),
+ isWhitespace: true
+ });
+ currentIndex = nextIndex;
+ } else {
+ leftSpans.shift();
+ // Explicit non-whitespace span.
+ tokenization.left.push({
+ text: nextSpan.text
+ });
+ currentIndex = Math.max(currentIndex, nextSpan.end);
+ }
+ }
+
+ // Detect any pre-caret whitespace after the final pre-caret non-whitespace
+ // token
+ //
+ // Note: the default wordbreaker won't need this code, as it emits a `''`
+ // after final whitespace.
+ if(context.left != null && currentIndex != context.left.length) {
+ const nextIndex = Math.max(currentIndex, context.left!.length);
+ tokenization.left.push({
+ text: context.left.substring(currentIndex, nextIndex),
+ isWhitespace: true
+ });
+ currentIndex = nextIndex;
+ }
+
+ // New step 2: handle any rejoins needed.
+
// Handle any desired special handling for directly-pre-caret scenarios - where for this
// _specific_ context, we should not make a token division where one normally would exist otherwise.
//
@@ -45,62 +103,88 @@ export function tokenize(wordBreaker: WordBreakingFunction, context?: Partial 1) {
- const leftTailBase = leftSpans[leftSpans.length - 2];
+ let leftTokenCount = tokenization.left.length;
+ if(leftTokenCount > 1) {
+ const leftTailBase = tokenization.left[leftTokenCount - 2];
+ const leftTail = tokenization.left[leftTokenCount - 1];
// If the final two pre-caret spans are adjacent - without intervening whitespace...
- if(leftTailBase.end == leftTail!.start) {
+ if(!leftTailBase.isWhitespace && !leftTail.isWhitespace) {
// Ideal: if(leftTailBase is standard-char-class && leftTail is single-quote-class)
// But we don't have character class access here; it's all wordbreaker-function internal.
// Upon inspection of the wordbreaker data definitions... the single-quote-class is ONLY "'".
// So... we'll just be lazy for now and append the `'`.
- if(leftTail!.text == "'") {
- let mergedSpan: Span = {
- text: leftTailBase.text + leftTail!.text,
- start: leftTailBase.start,
- end: leftTail!.end,
- length: leftTailBase.length + leftTail!.length
- };
-
- leftSpans.pop(); // leftTail
- leftSpans.pop(); // leftTailBase
- leftSpans.push(mergedSpan);
- leftTail = mergedSpan; // don't forget to update the `leftTail` Span!
+ if(rejoins.indexOf(leftTail!.text) != -1) {
+ tokenization.left.pop(); // leftTail
+ tokenization.left.pop(); // leftTailBase
+ tokenization.left.push({
+ text: leftTailBase.text + leftTail.text
+ });
+ leftTokenCount--;
}
}
}
- // With any 'detokenization' cases already handled, we may now begin to build the return object.
- let tokenization: Tokenization = {
- left: leftSpans.map(span => span.text),
- right: rightSpans.map(span => span.text),
+ // New step 3: right-context tokenization + token split detection
- // A default initialization of the value.
- caretSplitsToken: false
- };
+ // context.right starts from index 0; it's an 'index reset'.
+ currentIndex = 0;
+ // Set a flag for special "first token" processing.
+ let firstRightToken = true;
- // Now the hard part - determining whether or not the caret caused a token split.
- if(leftSpans.length > 0 && rightSpans.length > 0) {
- let rightHead = rightSpans[0];
+ // Note: is MOSTLY "WET" with the left-span loop, though the
+ // `caretSplitsToken` check is additional.
+ while(rightSpans.length > 0) {
+ const nextSpan = rightSpans[0];
+ if(Math.max(nextSpan.start, currentIndex) != currentIndex) {
+ const nextIndex = Math.max(currentIndex, nextSpan.start);
+ // Implicit whitespace span!
+ tokenization.right.push({
+ text: context.right!.substring(currentIndex, nextIndex),
+ isWhitespace: true
+ });
+ currentIndex = nextIndex;
+ } else {
+ const leftTail = tokenization.left[leftTokenCount-1];
+ if(leftTail) {
+ // If the first non-whitespace token to the right is non-whitespace,
+ // and the last token to the left is non-whitespace, the caret may
+ // be splitting a token.
+ if(firstRightToken && !leftTail.isWhitespace) {
+ if(wordBreaker(leftTail!.text + nextSpan.text).length == 1) {
+ tokenization.caretSplitsToken = true;
+ }
+ }
+ }
- // If tokenization includes all characters on each side of the caret,
- // we have a good candidate for a caret-splitting scenario.
- let leftSuffixWordbreak = leftTail!.end != context.left!.length;
- let rightPrefixWordbreak = rightHead.start != 0;
-
- if(leftSuffixWordbreak || rightPrefixWordbreak) {
- // Bypass the final test, as we already know the caret didn't split a token.
- // (The tokenization process already removed characters between the two.)
- return tokenization;
+ // Explicit non-whitespace span.
+ rightSpans.shift();
+ tokenization.right.push({
+ text: nextSpan.text
+ });
+ currentIndex = Math.max(currentIndex, nextSpan.end);
}
- // Worth note - some languages don't use wordbreaking characters. So, a final check:
- //
- // Does the wordbreaker split a merge of the 'two center' tokens?
- // If not, then the caret is responsible for the split.
- if(wordBreaker(leftTail!.text + rightHead.text).length == 1) {
- tokenization.caretSplitsToken = true;
- }
+ // We've always processed the "first right token" after the first iteration.
+ // Do not run the caret-split check on any future iterations.
+ firstRightToken = false;
+ }
+
+ // Detect any pre-caret whitespace after the final pre-caret non-whitespace
+ // token
+ //
+ // Note: the default wordbreaker won't need this code, as it emits a `''`
+ // after final whitespace.
+ //
+ // Also note: is pretty much WET with the similar check after the
+ // leftSpan loop.
+ if(context.right && currentIndex != context.right.length) {
+ const nextIndex = Math.max(currentIndex, context.right!.length);
+ tokenization.right.push({
+ text: context.right.substring(currentIndex, nextIndex),
+ isWhitespace: true
+ });
+ currentIndex = nextIndex;
}
return tokenization;
@@ -108,12 +192,18 @@ export function tokenize(wordBreaker: WordBreakingFunction, context?: Partial 0) {
- return tokenization.left.pop() as string;
+ const lastToken = tokenization.left.pop();
+ if(lastToken!.isWhitespace) {
+ return '';
+ } else {
+ return lastToken!.text;
+ }
}
return '';
diff --git a/common/models/templates/src/trie-model.ts b/common/models/templates/src/trie-model.ts
index 9de2fb9050..bfe41c7aba 100644
--- a/common/models/templates/src/trie-model.ts
+++ b/common/models/templates/src/trie-model.ts
@@ -98,13 +98,9 @@ class Traversal implements LexiconTraversal {
}
child(char: USVString): LexiconTraversal | undefined {
- /*
- Note: would otherwise return the current instance if `char == ''`. If
- such a call is happening, it's probably indicative of an implementation
- issue elsewhere - let's signal now in order to catch such stuff early.
- */
+ // May result for blank tokens resulting immediately after whitespace.
if(char == '') {
- return undefined;
+ return this;
}
// Split into individual code units.
diff --git a/common/models/templates/test/custom-breakers.def.js b/common/models/templates/test/custom-breakers.def.js
new file mode 100644
index 0000000000..c907467ce0
--- /dev/null
+++ b/common/models/templates/test/custom-breakers.def.js
@@ -0,0 +1,128 @@
+/**
+ * The custom wordbreaker used by the sil.km.gcc model as of
+ * https://github.com/keymanapp/lexical-models/pull/265.
+ * @type {WordBreakingFunction}
+ * */
+export function customWordBreakerProper(str) {
+ const whitespaceRegex = /\s|\u200b|\n|\r/;
+ const tokens = str.split(whitespaceRegex);
+
+ for(let i=0; i < tokens.length; i++) {
+ const token = tokens[i];
+ if(token.length == 0) {
+ tokens.splice(i, 1);
+ i--;
+ continue;
+ } else if(token.length == 1 && whitespaceRegex.test(token)) {
+ tokens.splice(i, 1);
+ i--;
+ continue;
+ }
+
+ // Certain punctuation marks should be considered a separate token from the word they're next to.
+ const punctuationMarks = ['«', '»', '$', '#' /* add extras here */];
+ const punctSplitIndices = [];
+
+ // Find if and where each mark exists within the token
+ for(let i = 0; i < punctuationMarks.length; i++) {
+ const split = token.indexOf(punctuationMarks[i]);
+ if(split >= 0) {
+ punctSplitIndices.push(split);
+ }
+ }
+
+ // Sort and pick the earliest mark's location. If none exists, use -1.
+ punctSplitIndices.sort();
+ const splitPoint = punctSplitIndices[0] === undefined ? -1 : punctSplitIndices[0];
+
+ if(splitPoint > -1) {
+ const left = token.substring(0, splitPoint); // (0, -1) => ''
+ const punct = token.substring(splitPoint, splitPoint+1);
+ const right = token.substring(splitPoint+1); // Starting past the end of the string => ''
+
+ if(left) {
+ tokens.splice(i++, 0, left);
+ }
+ tokens.splice(i++, 1, punct);
+ if(right) {
+ tokens.splice(i, 0, right);
+ }
+ // Ensure that the next iteration puts `i` immediately after the punctuation token... even if
+ // there was a `right` portion, as it may have extra marks that also need to be spun off.
+ i--;
+ }
+ }
+
+ let latestIndex = 0;
+ return tokens.map(function(token) {
+ const start = str.indexOf(token, latestIndex);
+ latestIndex = start + token.length;
+ return {
+ left: start,
+ start: start,
+ right: start + token.length,
+ end: start + token.length,
+ length: token.length,
+ text: token
+ }
+ });
+}
+
+/**
+ * The version of the custom wordbreaker used by the sil.km.gcc model
+ * before https://github.com/keymanapp/lexical-models/pull/265, which
+ * triggered #12200.
+ * @type {WordBreakingFunction}
+ * */
+export function customWordBreakerFormer (str) {
+ const tokens = str.split(/\s|\u200b/);
+
+ for(let i=0; i < tokens.length; i++) {
+ const token = tokens[i];
+ if(token.length == 1) {
+ continue;
+ }
+
+ // Certain punctuation marks should be considered a separate token from the word they're next to.
+ const punctuationMarks = ['«', '»', '$', '#' /* add extras here */];
+ const punctSplitIndices = [];
+
+ // Find if and where each mark exists within the token
+ for(let i = 0; i < punctuationMarks.length; i++) {
+ const split = token.indexOf(punctuationMarks[i]);
+ if(split >= 0) {
+ punctSplitIndices.push(split);
+ }
+ }
+
+ // Sort and pick the earliest mark's location. If none exists, use -1.
+ punctSplitIndices.sort();
+ const splitPoint = punctSplitIndices[0] === undefined ? -1 : punctSplitIndices[0];
+
+ if(splitPoint > -1) {
+ const left = token.substring(0, splitPoint); // (0, -1) => ''
+ const punct = token.substring(splitPoint, splitPoint+1);
+ const right = token.substring(splitPoint+1); // Starting past the end of the string => ''
+
+ if(left) {
+ tokens.splice(i++, 0, left);
+ }
+ tokens.splice(i++, 1, punct);
+ if(right) {
+ tokens.splice(i, 0, right);
+ }
+ // Ensure that the next iteration puts `i` immediately after the punctuation token... even if
+ // there was a `right` portion, as it may have extra marks that also need to be spun off.
+ i--;
+ }
+ }
+ return tokens.map(function(token) {
+ return {
+ left: str.indexOf(token),
+ start: str.indexOf(token),
+ right: str.indexOf(token) + token.length,
+ end: str.indexOf(token) + token.length,
+ text: token
+ }
+ });
+}
\ No newline at end of file
diff --git a/common/models/templates/test/test-tokenization.js b/common/models/templates/test/test-tokenization.js
index 6c1ff746e5..d8f27197a2 100644
--- a/common/models/templates/test/test-tokenization.js
+++ b/common/models/templates/test/test-tokenization.js
@@ -5,6 +5,29 @@
import { assert } from 'chai';
import * as models from "@keymanapp/models-templates";
import * as wordBreakers from "@keymanapp/models-wordbreakers";
+import { customWordBreakerFormer, customWordBreakerProper } from './custom-breakers.def.js';
+
+function asProcessedToken(text) {
+ // default wordbreaker emits these at the end of each context half if ending with whitespace.
+ // Indicates a new spot for non-whitespace text.
+ if(text == '') {
+ return {
+ text: text
+ };
+ } else if(text.trim() == '') {
+ // Simple cases using standard Latin-script patterns - can be handled via trim()
+ return {
+ text: text,
+ isWhitespace: true
+ };
+ }
+
+ // could add simple check for other, non-default cases here.
+
+ return {
+ text: text
+ };
+}
describe('Tokenization functions', function() {
describe('tokenize', function() {
@@ -19,8 +42,8 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(wordBreakers.default, context);
let expectedResult = {
- left: ['The', 'quick', 'brown', 'fox'],
- right: ['jumped', 'over', 'the', 'lazy', 'dog'],
+ left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox'].map(asProcessedToken),
+ right: [' ', 'jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
caretSplitsToken: false
};
@@ -30,7 +53,7 @@ describe('Tokenization functions', function() {
it('tokenizes English using defaults, pre-whitespace caret, partial context', function() {
let context = {
left: "quick brown fox", // No "The"
- right: " jumped over the lazy", // No "dog"
+ right: " jumped over the lazy ", // No "dog"
startOfBuffer: false,
endOfBuffer: false
};
@@ -38,8 +61,8 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(wordBreakers.default, context);
let expectedResult = {
- left: ['quick', 'brown', 'fox'],
- right: ['jumped', 'over', 'the', 'lazy'],
+ left: ['quick', ' ', 'brown', ' ', 'fox'].map(asProcessedToken),
+ right: [' ', 'jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', ''].map(asProcessedToken),
caretSplitsToken: false
};
@@ -59,14 +82,35 @@ describe('Tokenization functions', function() {
// Technically, we're editing the start of the first token on the right
// when in this context.
let expectedResult = {
- left: ['The', 'quick', 'brown', 'fox', ''],
- right: ['jumped', 'over', 'the', 'lazy', 'dog'],
+ left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' ', ''].map(asProcessedToken),
+ right: ['jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
caretSplitsToken: true
};
assert.deepEqual(tokenization, expectedResult);
});
+ it('tokenizes English using ascii-breaker, post-whitespace caret', function() {
+ let context = {
+ left: "The quick brown fox ",
+ right: "jumped over the lazy dog ",
+ startOfBuffer: true,
+ endOfBuffer: true
+ };
+
+ let tokenization = models.tokenize(wordBreakers.ascii, context);
+
+ // Technically, we're editing the start of the first token on the right
+ // when in this context.
+ let expectedResult = {
+ left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' '].map(asProcessedToken),
+ right: ['jumped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog', ' '].map(asProcessedToken),
+ caretSplitsToken: false
+ };
+
+ assert.deepEqual(tokenization, expectedResult);
+ });
+
it('tokenizes English using defaults, post-whitespace caret, partial context', function() {
let context = {
left: "quick brown fox ",
@@ -80,8 +124,8 @@ describe('Tokenization functions', function() {
// Technically, we're editing the start of the first token on the right
// when in this context.
let expectedResult = {
- left: ['quick', 'brown', 'fox', ''],
- right: ['jumped', 'over', 'the', 'lazy'],
+ left: ['quick', ' ', 'brown', ' ', 'fox', ' ', ''].map(asProcessedToken),
+ right: ['jumped', ' ', 'over', ' ', 'the', ' ', 'lazy'].map(asProcessedToken),
caretSplitsToken: true
};
@@ -99,8 +143,8 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(wordBreakers.default, context);
let expectedResult = {
- left: ['The', 'quick', 'brown', 'fox', 'jum'],
- right: ['ped', 'over', 'the', 'lazy', 'dog'],
+ left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' ', 'jum'].map(asProcessedToken),
+ right: ['ped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
caretSplitsToken: true
};
@@ -118,15 +162,15 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(wordBreakers.default, context);
let expectedResult = {
- left: ['The', 'quick', 'brown', 'fox', 'jum'],
- right: ['ped', 'over', 'the', 'lazy', 'dog'],
+ left: ['The', ' ', 'quick', ' ', 'brown', ' ', 'fox', ' ', 'jum'].map(asProcessedToken),
+ right: ['ped', ' ', 'over', ' ', 'the', ' ', 'lazy', ' ', 'dog'].map(asProcessedToken),
caretSplitsToken: true
};
assert.deepEqual(tokenization, expectedResult);
});
- it('empty context case', function() {
+ it('properly handles empty-context cases', function() {
// Wordbreaking on a empty space => no word.
let context = {
left: '', startOfBuffer: true,
@@ -144,7 +188,7 @@ describe('Tokenization functions', function() {
assert.deepEqual(tokenization, expectedResult);
});
- it('nil context case', function() {
+ it('properly handles null context cases', function() {
// Wordbreaking on a empty space => no word.
let tokenization = models.tokenize(wordBreakers.default, null);
@@ -157,7 +201,7 @@ describe('Tokenization functions', function() {
assert.deepEqual(tokenization, expectedResult);
});
- it('near-empty context: one space before caret', function() {
+ it('properly handles a near-empty context: one space before caret', function() {
// Wordbreaking on a empty space => no word.
let context = {
left: ' ', startOfBuffer: true,
@@ -167,7 +211,7 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(wordBreakers.default, context);
let expectedResult = {
- left: [''],
+ left: [' ', ''].map(asProcessedToken),
right: [],
caretSplitsToken: false
};
@@ -175,6 +219,85 @@ describe('Tokenization functions', function() {
assert.deepEqual(tokenization, expectedResult);
});
+ it('properly tokenizes partial English contractions - default setting', function() {
+ let context = {
+ left: "I can'",
+ right: "",
+ startOfBuffer: true,
+ endOfBuffer: true
+ };
+
+ let tokenization = models.tokenize(wordBreakers.default, context);
+
+ // Technically, we're editing the start of the first token on the right
+ // when in this context.
+ let expectedResult = {
+ left: ['I', ' ', 'can\''].map(asProcessedToken),
+ right: [].map(asProcessedToken),
+ caretSplitsToken: false
+ };
+
+ assert.deepEqual(tokenization, expectedResult);
+ });
+
+ it('overly tokenizes partial English contractions when (default) apostrophe rejoin is disabled', function() {
+ let context = {
+ left: "I can'",
+ right: "",
+ startOfBuffer: true,
+ endOfBuffer: true
+ };
+
+ let tokenization = models.tokenize(wordBreakers.default, context, { rejoins: [] });
+
+ // Technically, we're editing the start of the first token on the right
+ // when in this context.
+ let expectedResult = {
+ left: ['I', ' ', 'can' , '\''].map(asProcessedToken),
+ right: [].map(asProcessedToken),
+ caretSplitsToken: false
+ };
+
+ assert.deepEqual(tokenization, expectedResult);
+ });
+
+ it('properly tokenizes English contractions', function() {
+ // Note: a 'context0' with the caret before the `'` actually
+ // is not supported well yet; a leading `'` is broken from
+ // following text when in isolation.
+
+ let context1 = {
+ left: "I can'",
+ right: "t",
+ startOfBuffer: true,
+ endOfBuffer: true
+ };
+
+ let context2 = {
+ left: "I can't",
+ right: "",
+ startOfBuffer: true,
+ endOfBuffer: true
+ }
+
+ let tokenization1 = models.tokenize(wordBreakers.default, context1);
+ let tokenization2 = models.tokenize(wordBreakers.default, context2);
+
+ let expectedResult1 = {
+ left: ['I', ' ', 'can\''].map(asProcessedToken),
+ right: ['t'].map(asProcessedToken),
+ caretSplitsToken: true
+ };
+ let expectedResult2 = {
+ left: ['I', ' ', 'can\'t'].map(asProcessedToken),
+ right: [].map(asProcessedToken),
+ caretSplitsToken: false
+ };
+
+ assert.deepEqual(tokenization1, expectedResult1);
+ assert.deepEqual(tokenization2, expectedResult2);
+ });
+
// For the next few tests: a mocked wordbreaker for Khmer, a language
// without whitespace between words.
let mockedKhmerBreaker = function(text) {
@@ -258,8 +381,8 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(mockedKhmerBreaker, context);
let expectedResult = {
- left: ['ស្រុក'],
- right: ['ខ្មែរ'],
+ left: ['ស្រុក'].map(asProcessedToken),
+ right: ['ខ្មែរ'].map(asProcessedToken),
caretSplitsToken: false
};
@@ -282,8 +405,8 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(mockedKhmerBreaker, context);
let expectedResult = {
- left: ['ស្រុ'],
- right: ['ក', 'ខ្មែរ'],
+ left: ['ស្រុ'].map(asProcessedToken),
+ right: ['ក', 'ខ្មែរ'].map(asProcessedToken),
caretSplitsToken: true
};
@@ -315,7 +438,7 @@ describe('Tokenization functions', function() {
return wordBreakers.default(text, customization);
}
- it('treats caret as `eot` for pre-caret text', function() {
+ it('treats caret as `eot` for pre-caret text tokenization', function() {
let context = {
left: "don-", // We use a hyphen here b/c single-quote is hardcoded.
right: " worry",
@@ -326,21 +449,21 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(wordBreakers.default, context);
assert.deepEqual(tokenization, {
- left: ["don", "-"],
- right: ["worry"],
+ left: ["don", "-"].map(asProcessedToken),
+ right: [" ", "worry"].map(asProcessedToken),
caretSplitsToken: false
});
tokenization = models.tokenize(midLetterNonbreaker, context);
assert.deepEqual(tokenization, {
- left: ["don-"],
- right: ["worry"],
+ left: ["don-"].map(asProcessedToken),
+ right: [" ", "worry"].map(asProcessedToken),
caretSplitsToken: false
});
});
- it('handles mid-contraction tokenization', function() {
+ it('handles mid-contraction tokenization (via wordbreaker customization)', function() {
let context = {
left: "don:",
right: "t worry",
@@ -351,24 +474,108 @@ describe('Tokenization functions', function() {
let tokenization = models.tokenize(wordBreakers.default, context);
assert.deepEqual(tokenization, {
- left: ["don", ":"], // This particular case feels like a possible issue.
- right: ["t", "worry"], // It'd be a three-way split token, as "don:t" would
- // be a single token were it not for the caret in the middle.
+ // This particular case feels like a possible issue.
+ // It'd be a three-way split token, as "don:t" would
+ // be a single token were it not for the caret in the middle.
+ left: ["don", ":"].map(asProcessedToken),
+ right: ["t", " ", "worry"].map(asProcessedToken),
+
caretSplitsToken: false
})
tokenization = models.tokenize(midLetterNonbreaker, context);
assert.deepEqual(tokenization, {
- left: ["don:"],
- right: ["t", "worry"],
+ left: ["don:"].map(asProcessedToken),
+ right: ["t", " ", "worry"].map(asProcessedToken),
caretSplitsToken: true
});
});
+
+ it('mitigates effects of previously-distributed malformed wordbreaker output', function () {
+ const text = 'the quick brown fox jumped over the lazy dog ';
+ /** @type { Context } */
+ const context = {
+ left: text,
+ right: '',
+ startOfBuffer: true,
+ endOfBuffer: true
+ }
+
+ const tokenized = models.tokenize(customWordBreakerFormer, context);
+
+ // Mitigation aims to prevent the _worst_ side-effects that can result from invalidating the
+ // underlying assumption of a monotonically-increasing index within the context -
+ // assigning repeated or blank entries the text that preceded them!
+ assert.notExists(tokenized.left.find((token) => token.text == text));
+ assert.notExists(tokenized.left.find((token) => token.text.startsWith(text.substring(0, 25))));
+
+ // 'the' appears twice in the context, which should result in two separate 'the' tokens here.
+ // This was improperly handled when we didn't check that assumption.
+ assert.equal(tokenized.left.filter((token) => token.text == 'the').length, 2);
+
+ // Does not address multiple blank-token ('') entries that result from intervening spaces;
+ // that would add too much extra complexity to the method... and it can already be
+ // handled decently by the predictive-text engine.
+ assert.deepEqual(
+ tokenized.left
+ .filter((entry) => !entry.isWhitespace)
+ .filter((entry) => entry.text != '')
+ .map((entry) => entry.text),
+ ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']
+ );
+ });
+
+ it('properly works with well-formed custom wordbreaker output', function () {
+ const text = 'the quick brown fox jumped over the lazy dog ';
+ /** @type { Context } */
+ const context = {
+ left: text,
+ right: '',
+ startOfBuffer: true,
+ endOfBuffer: true
+ }
+
+ const tokenized = models.tokenize(customWordBreakerProper, context);
+
+ // Easier-to-parse version
+ assert.deepEqual(
+ tokenized.left
+ .filter((entry) => !entry.isWhitespace)
+ .map((entry) => entry.text),
+ ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']
+ );
+
+ // This time, with whitespaces.
+ assert.deepEqual(
+ tokenized.left.map((entry) => entry.text), [
+ 'the',
+ ' ',
+ 'quick',
+ ' ',
+ 'brown',
+ ' ',
+ 'fox',
+ ' ',
+ 'jumped',
+ ' ',
+ 'over',
+ ' ',
+ 'the',
+ ' ',
+ 'lazy',
+ ' ',
+ 'dog',
+ ' '
+ ]
+ );
+ });
+
+ //
});
describe('getLastPreCaretToken', function() {
- it('with pre-whitespace caret', function() {
+ it('operates properly with pre-whitespace caret', function() {
let context = {
left: "The quick brown fox",
right: " jumped over the lazy dog",
@@ -381,7 +588,7 @@ describe('Tokenization functions', function() {
assert.equal(tokenization, 'fox');
});
- it('with post-whitespace caret', function() {
+ it('operates properly with post-whitespace caret', function() {
let context = {
left: "The quick brown fox ",
right: "jumped over the lazy dog",
@@ -394,7 +601,21 @@ describe('Tokenization functions', function() {
assert.equal(tokenization, '');
});
- it('within a token', function() {
+
+ it('operates properly with post-whitespace caret, ascii breaker', function() {
+ let context = {
+ left: "The quick brown fox ",
+ right: "jumped over the lazy dog",
+ startOfBuffer: true,
+ endOfBuffer: true
+ };
+
+ let tokenization = models.getLastPreCaretToken(wordBreakers.ascii, context);
+
+ assert.equal(tokenization, '');
+ });
+
+ it('operates properly within a token', function() {
let context = {
left: "The quick brown fox jum",
right: "ped over the lazy dog",
@@ -406,10 +627,15 @@ describe('Tokenization functions', function() {
assert.equal(tokenization, 'jum');
});
+
+ it('operates properly with no context', function() {
+ let tokenization = models.getLastPreCaretToken(wordBreakers.default, null);
+ assert.equal(tokenization, '');
+ });
});
describe('wordbreak', function() {
- it('with pre-whitespace caret', function() {
+ it('operates properly with pre-whitespace caret', function() {
let context = {
left: "The quick brown fox",
right: " jumped over the lazy dog",
@@ -422,7 +648,7 @@ describe('Tokenization functions', function() {
assert.equal(tokenization, 'fox');
});
- it('with post-whitespace caret', function() {
+ it('operates properly with post-whitespace caret', function() {
let context = {
left: "The quick brown fox ",
right: "jumped over the lazy dog",
@@ -437,7 +663,7 @@ describe('Tokenization functions', function() {
// This version is subject to change. In the future, we may wish the wordbreak
// operation to include "the rest of the word" - the post-caret part.
- it('within a token', function() {
+ it('operates properly within a token', function() {
let context = {
left: "The quick brown fox jum",
right: "ped over the lazy dog",
diff --git a/common/predictive-text/.build-builder b/common/predictive-text/.build-builder
deleted file mode 100644
index 4e15741a62..0000000000
--- a/common/predictive-text/.build-builder
+++ /dev/null
@@ -1 +0,0 @@
-The presence of this file tells CI to use the new builder_ style parameters for build.sh and unit_tests/test.sh.
\ No newline at end of file
diff --git a/common/web/build.sh b/common/web/build.sh
index 554b1e8ebb..fbc015613f 100755
--- a/common/web/build.sh
+++ b/common/web/build.sh
@@ -10,7 +10,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
#
# TODO: future modules may include
-# :lm-message-types \
# :sentry-manager \
#
diff --git a/common/web/input-processor/README.md b/common/web/input-processor/README.md
deleted file mode 100644
index 70417353a2..0000000000
--- a/common/web/input-processor/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Keyman: Keyboard Processor Module
-The Original Code is (C) 2017-2020 SIL International
-
-The Keyboard Processor module is an internal component of KeymanWeb, seen within this repo at /web/.
-
-## Minimum Compilation Requirements
-
-* A local installation of [Node.js](https://nodejs.org/) v8.9+.
- * Builds will call `npm install` to automatically install further necessary build dependencies.
-
- * Linux users can update to LTS version of nodejs by following instructions on the [NodeSource Distributions](https://github.com/nodesource/distributions#table-of-contents) page.
-
-**********************************************************************
-
-The build script may be found at src/build.sh.
diff --git a/common/web/input-processor/build.sh b/common/web/input-processor/build.sh
deleted file mode 100755
index 45d5277db4..0000000000
--- a/common/web/input-processor/build.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env bash
-#
-# Compile KeymanWeb's 'keyboard-processor' module, one of the components of Web's 'core' module.
-#
-## START STANDARD BUILD SCRIPT INCLUDE
-# adjust relative paths as necessary
-THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
-. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
-## END STANDARD BUILD SCRIPT INCLUDE
-
-. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
-
-BUNDLE_CMD="node $KEYMAN_ROOT/common/web/es-bundling/build/common-bundle.mjs"
-
-################################ Main script ################################
-
-builder_describe "Builds the standalone, headless form of Keyman Engine for Web's input-processor module" \
- "@/common/web/keyman-version" \
- "@/common/web/keyboard-processor" \
- "@/common/predictive-text" \
- "@/developer/src/kmc-model test" \
- "clean" \
- "configure" \
- "build" \
- "test" \
- "--ci Sets $(builder_term test) action to use CI-based test configurations & reporting"
-
-builder_describe_outputs \
- configure /node_modules \
- build /common/web/input-processor/build/lib/index.mjs \
-
-builder_parse "$@"
-
-function do_build() {
- tsc -b ./tsconfig.json
-
- $BUNDLE_CMD "${KEYMAN_ROOT}/common/web/input-processor/build/obj/index.js" \
- --out "${KEYMAN_ROOT}/common/web/input-processor/build/lib/index.mjs" \
- --format esm
-
- # Declaration bundling.
- tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts
-}
-
-function do_test() {
- local FLAGS=
- if builder_has_option --ci; then
- FLAGS="--reporter mocha-teamcity-reporter"
- fi
-
- mocha --recursive $FLAGS ./tests/cases/
-}
-
-builder_run_action configure verify_npm_setup
-builder_run_action clean rm -rf build/
-builder_run_action build do_build
-builder_run_action test do_test
\ No newline at end of file
diff --git a/common/web/input-processor/package.json b/common/web/input-processor/package.json
deleted file mode 100644
index 6a81cacde7..0000000000
--- a/common/web/input-processor/package.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{
- "name": "@keymanapp/input-processor",
- "description": "The core text and prediction processing engine for KeymanWeb",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/keymanapp/keyman.git"
- },
- "keywords": [
- "input",
- "languages",
- "keyboards"
- ],
- "author": "SIL International",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/keymanapp/keyman/issues"
- },
- "homepage": "https://github.com/keymanapp/keyman#readme",
- "devDependencies": {
- "@keymanapp/kmc": "*",
- "@keymanapp/resources-gosh": "*",
- "mocha": "^10.0.0",
- "mocha-teamcity-reporter": "^4.0.0",
- "typescript": "^5.4.5"
- },
- "scripts": {
- "test": "gosh ./test.sh"
- },
- "dependencies": {
- "@keymanapp/keyboard-processor": "*",
- "@keymanapp/lexical-model-layer": "*",
- "@keymanapp/models-types": "*",
- "@keymanapp/keyman-version": "*",
- "@keymanapp/web-utils": "*",
- "eventemitter3": "^5.0.0"
- },
- "type": "module",
- "main": "./build/obj/index.js",
- "types": "./build/obj/index.d.ts",
- "exports": {
- ".": {
- "es6-bundling": "./src/index.ts",
- "default": "./build/obj/index.js"
- },
- "./lib": {
- "types": "./build/lib/index.d.ts",
- "import": "./build/lib/index.mjs"
- },
- "./obj/*.js": "./build/obj/*.js"
- }
-}
diff --git a/common/web/input-processor/src/index.ts b/common/web/input-processor/src/index.ts
deleted file mode 100644
index 6747f42a23..0000000000
--- a/common/web/input-processor/src/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-///
-
-export * from './corrections.js';
-export * from './correctionLayout.js';
-export { default as InputProcessor } from './text/inputProcessor.js';
-export { default as ContextWindow } from './text/contextWindow.js';
-export { default as ModelSpec } from './text/prediction/modelSpec.js';
-export { default as LanguageProcessor, StateChangeEnum } from './text/prediction/languageProcessor.js';
-export { default as PredictionContext } from './text/prediction/predictionContext.js';
-export { TranscriptionCache } from './transcriptionCache.js';
\ No newline at end of file
diff --git a/common/web/input-processor/tsconfig.json b/common/web/input-processor/tsconfig.json
deleted file mode 100644
index 4551a235dc..0000000000
--- a/common/web/input-processor/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "extends": "../tsconfig.kmw-main-base.json",
- "compilerOptions": {
- "baseUrl": "./",
- // Necessary for ES5 iteration of Map.keys().
- "downlevelIteration": true,
- "outDir": "build/obj/",
- "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
- "rootDir": "./src"
- },
- "references": [
- { "path": "../utils" },
- { "path": "../keyboard-processor" },
- { "path": "../../predictive-text/tsconfig.all.json" },
- ],
- // The extra .d.ts is necessary to avoid issues with lack of a Worker type definition when not including
- // the standard DOM in the TS "lib" configuration.
- "include": ["./src/**/*.ts", "../../predictive-text/src/worker-interface.d.ts"]
-}
diff --git a/common/web/keyboard-processor/.gitignore b/common/web/keyboard-processor/.gitignore
deleted file mode 100644
index c613a78f7d..0000000000
--- a/common/web/keyboard-processor/.gitignore
+++ /dev/null
@@ -1,18 +0,0 @@
-# Build products
-src/environment.inc.ts
-
-# Legacy build folders.
-output/
-build/
-embedded/
-
-# Current build output folder (matches standard node publish location).
-dist/
-
-# Other local files.
-node_modules/
-unit_tests/modernizr.js
-source/environment.inc.ts
-.idea/**/*.xml
-*.iml
-*.kpj.user
\ No newline at end of file
diff --git a/common/web/keyboard-processor/build.sh b/common/web/keyboard-processor/build.sh
deleted file mode 100755
index a7647983f9..0000000000
--- a/common/web/keyboard-processor/build.sh
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/env bash
-#
-# Compile KeymanWeb's 'keyboard-processor' module, one of the components of Web's 'core' module.
-#
-## START STANDARD BUILD SCRIPT INCLUDE
-# adjust relative paths as necessary
-THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
-. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
-## END STANDARD BUILD SCRIPT INCLUDE
-
-. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh"
-
-BUNDLE_CMD="node ${KEYMAN_ROOT}/common/web/es-bundling/build/common-bundle.mjs"
-
-################################ Main script ################################
-
-builder_describe \
- "Compiles the web-oriented utility function module." \
- "@/common/web/recorder test" \
- "@/common/web/keyman-version" \
- "@/common/web/es-bundling" \
- "@/common/web/types" \
- "@/common/web/utils" \
- configure \
- clean \
- build \
- test \
- "--ci For use with action $(builder_term test) - emits CI-friendly test reports"
-
-builder_describe_outputs \
- configure /node_modules \
- build /common/web/keyboard-processor/build/lib/index.mjs
-
-builder_parse "$@"
-
-function do_configure() {
- verify_npm_setup
-
- # Configure Web browser-engine testing environments. As is, this should only
- # make changes when we update the dependency, even on our CI build agents.
- playwright install
-}
-
-function do_build() {
- tsc --build "${THIS_SCRIPT_PATH}/tsconfig.all.json"
-
- # Base product - the main keyboard processor
- builder_echo "Bundle base product - the main keyboard processor"
- ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/index.js" \
- --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/index.mjs" \
- --format esm
-
- # The DOM-oriented keyboard loader
- builder_echo "Bundle the DOM-oriented keyboard loader"
- ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/keyboards/loaders/dom-keyboard-loader.js" \
- --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/dom-keyboard-loader.mjs" \
- --format esm
-
- # The Node-oriented keyboard loader
- builder_echo "Bundle the Node-oriented keyboard loader"
- ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/keyboards/loaders/node-keyboard-loader.js" \
- --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/node-keyboard-loader.mjs" \
- --format esm \
- --platform node
-
- # Tests
- builder_echo "Bundle tests"
- ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/tests/dom/cases/domKeyboardLoader.spec.js" \
- --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/tests/dom/domKeyboardLoader.spec.mjs" \
- --format esm
-
- # Declaration bundling.
- builder_echo "Declaration bundling"
- tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts
- tsc --emitDeclarationOnly --outFile ./build/lib/dom-keyboard-loader.d.ts -p src/keyboards/loaders/tsconfig.dom.json
- tsc --emitDeclarationOnly --outFile ./build/lib/node-keyboard-loader.d.ts -p src/keyboards/loaders/tsconfig.node.json
-}
-
-function do_test() {
- local MOCHA_FLAGS=
- local WTR_CONFIG=
- if builder_has_option --ci; then
- echo "Replacing user-friendly test reports with CI-friendly versions."
- MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter"
- WTR_CONFIG=.CI
- fi
-
- c8 mocha --recursive $MOCHA_FLAGS ./tests/node/
- web-test-runner --config tests/dom/web-test-runner${WTR_CONFIG}.config.mjs
-}
-
-builder_run_action configure do_configure
-builder_run_action clean rm -rf ./build
-builder_run_action build do_build
-builder_run_action test do_test
diff --git a/common/web/keyboard-processor/package.json b/common/web/keyboard-processor/package.json
deleted file mode 100644
index 3f0a595897..0000000000
--- a/common/web/keyboard-processor/package.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
- "name": "@keymanapp/keyboard-processor",
- "description": "Core module for Keyman keyboard support in KeymanWeb.",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/keymanapp/keyman.git"
- },
- "keywords": [
- "input",
- "languages",
- "keyboards"
- ],
- "author": "SIL International",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/keymanapp/keyman/issues"
- },
- "homepage": "https://github.com/keymanapp/keyman#readme",
- "devDependencies": {
- "@keymanapp/resources-gosh": "*",
- "c8": "^7.12.0",
- "mocha": "^10.0.0",
- "mocha-teamcity-reporter": "^4.0.0",
- "typescript": "^5.4.5"
- },
- "scripts": {
- "build": "gosh build.sh",
- "clean": "gosh build.sh clean",
- "test": "gosh build.sh test"
- },
- "dependencies": {
- "@keymanapp/common-types": "*",
- "@keymanapp/models-types": "*",
- "@keymanapp/keyman-version": "*",
- "@keymanapp/web-utils": "*"
- },
- "type": "module",
- "main": "./build/obj/index.js",
- "types": "./build/obj/index.d.ts",
- "exports": {
- ".": {
- "es6-bundling": "./src/index.ts",
- "default": "./build/obj/index.js"
- },
- "./node-keyboard-loader": {
- "es6-bundling": "./src/keyboards/loaders/node-keyboard-loader.ts",
- "types": "./build/obj/keyboards/loaders/node-keyboard-loader.d.ts",
- "import": "./build/obj/keyboards/loaders/node-keyboard-loader.js"
- },
- "./dom-keyboard-loader": {
- "es6-bundling": "./src/keyboards/loaders/dom-keyboard-loader.ts",
- "types": "./build/obj/keyboards/loaders/dom-keyboard-loader.d.ts",
- "import": "./build/obj/keyboards/loaders/dom-keyboard-loader.js"
- },
- "./lib": {
- "types": "./build/lib/index.d.ts",
- "import": "./build/lib/index.mjs"
- },
- "./lib/node-keyboard-loader": {
- "types": "./build/lib/node-keyboard-loader.d.ts",
- "import": "./build/lib/node-keyboard-loader.mjs"
- },
- "./lib/dom-keyboard-loader": {
- "types": "./build/lib/dom-keyboard-loader.d.ts",
- "import": "./build/lib/dom-keyboard-loader.mjs"
- },
- "./obj/*.js": "./build/obj/*.js"
- }
-}
diff --git a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json
deleted file mode 100644
index 7ba2abe026..0000000000
--- a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "extends": "../../../../tsconfig.kmw-main-base.json",
- "compilerOptions": {
- "baseUrl": "../../../",
- "outDir": "../../../build/obj/keyboards/loaders/",
- "tsBuildInfoFile": "../../../build/obj/keyboards/loaders/tsconfig.dom.tsbuildinfo",
- "rootDir": "."
- },
- "references": [
- { "path": "../../../tsconfig.json" }
- ],
- "include": ["dom-keyboard-loader.ts", "domKeyboardLoader.ts"],
-}
diff --git a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.node.json b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.node.json
deleted file mode 100644
index 0be3f169ad..0000000000
--- a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.node.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "extends": "../../../../tsconfig.kmw-main-base.json",
- "compilerOptions": {
- "types": [ "node" ],
- "baseUrl": "../../../",
- "outDir": "../../../build/obj/keyboards/loaders/",
- "tsBuildInfoFile": "../../../build/obj/keyboards/loaders/tsconfig.node.tsbuildinfo",
- "rootDir": "."
- },
- "references": [
- { "path": "../../../tsconfig.json" }
- ],
- "include": ["node-keyboard-loader.ts", "nodeKeyboardLoader.ts"],
-}
diff --git a/common/web/keyboard-processor/tests/dom/readme.md b/common/web/keyboard-processor/tests/dom/readme.md
deleted file mode 100644
index 153b7f4a1e..0000000000
--- a/common/web/keyboard-processor/tests/dom/readme.md
+++ /dev/null
@@ -1,5 +0,0 @@
-Automated tests in this subfolder and its children are designed to facilitate simple, browser-independent
-unit tests that are DOM-reliant.
-
-Tests for anything that may reasonably vary depending upon the browser used to run the code should go under
-the "integrated" folder instead.
\ No newline at end of file
diff --git a/common/web/keyboard-processor/tests/dom/web-test-runner.config.mjs b/common/web/keyboard-processor/tests/dom/web-test-runner.config.mjs
deleted file mode 100644
index 76651182ac..0000000000
--- a/common/web/keyboard-processor/tests/dom/web-test-runner.config.mjs
+++ /dev/null
@@ -1,62 +0,0 @@
-// @ts-check
-import { devices, playwrightLauncher } from '@web/test-runner-playwright';
-import { esbuildPlugin } from '@web/dev-server-esbuild';
-import { defaultReporter, summaryReporter } from '@web/test-runner';
-import { LauncherWrapper, sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs';
-import { importMapsPlugin } from '@web/dev-server-import-maps';
-import { dirname, resolve } from 'path';
-import { fileURLToPath } from 'url';
-
-const dir = dirname(fileURLToPath(import.meta.url));
-const KEYMAN_ROOT = resolve(dir, '../../../../..');
-
-/** @type {import('@web/test-runner').TestRunnerConfig} */
-export default {
- // debug: true,
- browsers: [
- new LauncherWrapper(playwrightLauncher({ product: 'chromium' })),
- new LauncherWrapper(playwrightLauncher({ product: 'firefox' })),
- new LauncherWrapper(playwrightLauncher({ product: 'webkit', concurrency: 1 })),
- ],
- concurrency: 10,
- nodeResolve: true,
- files: [
- 'build/tests/dom/**/*.spec.mjs'
- ],
- middleware: [
- // Rewrites short-hand paths for test resources, making them fully relative to the repo root.
- function rewriteResourcePath(context, next) {
- if(context.url.startsWith('/resources/')) {
- context.url = '/common/test' + context.url;
- }
-
- return next();
- }
- ],
- plugins: [
- esbuildPlugin({ts: true, target: 'auto'}),
- importMapsPlugin({
- inject: {
- importMap: {
- // Redirects `eventemitter3` imports to the bundled ESM library. The standard import is an
- // ESM wrapper around the CommonJS implementation, and WTR fails when it hits the CommonJS.
- imports: {
- 'eventemitter3': '/node_modules/eventemitter3/dist/eventemitter3.esm.js'
- }
- }
- }
- })
- ],
- reporters: [
- summaryReporter({}), /* local-dev mocha-style */
- sessionStabilityReporter({}),
- defaultReporter({})
- ],
- /*
- Un-comment the next two lines for easy interactive debugging; it'll launch the
- test page in your preferred browser.
- */
- // open: true,
- // manual: true,
- rootDir: KEYMAN_ROOT
-}
diff --git a/common/web/keyboard-processor/tests/tsconfig.json b/common/web/keyboard-processor/tests/tsconfig.json
deleted file mode 100644
index 0e978545b8..0000000000
--- a/common/web/keyboard-processor/tests/tsconfig.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "compilerOptions": {
- "baseUrl": "../",
- "outDir": "../build/tests/",
- "tsBuildInfoFile": "../build/tests/tsconfig.tsbuildinfo",
- "rootDir": "./"
- },
- "include": [ "./dom/**/*.ts"],
- "exclude": []
-}
diff --git a/common/web/keyboard-processor/tsconfig.json b/common/web/keyboard-processor/tsconfig.json
deleted file mode 100644
index 728935b74f..0000000000
--- a/common/web/keyboard-processor/tsconfig.json
+++ /dev/null
@@ -1,18 +0,0 @@
-// Is used by the keyboard-loader submodules.
-{
- "extends": "../tsconfig.kmw-main-base.json",
- "compilerOptions": {
- "baseUrl": "./",
- "outDir": "build/obj/",
- "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
- "rootDir": "./src/"
- },
- "references": [
- { "path": "../types" },
- { "path": "../../models/types" },
- { "path": "../keyman-version/" },
- { "path": "../utils/" }
- ],
- "include": [ "./src/**/*.ts"],
- "exclude": ["./src/keyboards/loaders/**/*.ts"]
-}
diff --git a/common/web/lm-message-types/tsconfig.json b/common/web/lm-message-types/tsconfig.json
deleted file mode 100644
index 559b764980..0000000000
--- a/common/web/lm-message-types/tsconfig.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "declaration": true,
- "outDir": "build/",
- "sourceMap": true,
- "lib": ["es6"],
- "target": "es6"
- },
- "include": ["./*.ts"],
- "exclude": ["test.ts"]
-}
diff --git a/common/web/lm-worker/src/main/correction/context-tracker.ts b/common/web/lm-worker/src/main/correction/context-tracker.ts
deleted file mode 100644
index 2f15165e5e..0000000000
--- a/common/web/lm-worker/src/main/correction/context-tracker.ts
+++ /dev/null
@@ -1,597 +0,0 @@
-import { applyTransform, tokenize } from '@keymanapp/models-templates';
-import { defaultWordbreaker } from '@keymanapp/models-wordbreakers';
-
-import { ClassicalDistanceCalculation } from './classical-calculation.js';
-import { SearchSpace } from './distance-modeler.js';
-import TransformUtils from '../transformUtils.js';
-
-function textToCharTransforms(text: string, transformId?: number) {
- let perCharTransforms: Transform[] = [];
-
- for(let i=0; i < text.kmwLength(); i++) {
- let char = text.kmwCharAt(i); // is SMP-aware
-
- let transform: Transform = {
- insert: char,
- deleteLeft: 0,
- id: transformId
- };
-
- perCharTransforms.push(transform);
- }
-
- return perCharTransforms;
-}
-
-export class TrackedContextSuggestion {
- suggestion: Suggestion;
- tokenWidth: number;
-}
-
-export class TrackedContextToken {
- raw: string;
- replacementText: string;
-
- transformDistributions: Distribution[] = [];
- replacements: TrackedContextSuggestion[];
- activeReplacementId: number = -1;
-
- get currentText(): string {
- if(this.replacementText === undefined || this.replacementText === null) {
- return this.raw;
- } else {
- return this.replacementText;
- }
- }
-
- get replacement(): TrackedContextSuggestion {
- let replacementId = this.activeReplacementId;
- return this.replacements.find(function(replacement) {
- return replacement.suggestion.id == replacementId;
- });
- }
-
- revert() {
- delete this.activeReplacementId;
- }
-}
-
-export class TrackedContextState {
- // Stores the post-transform Context. Useful as a debugging reference, but also used to
- // pre-validate context state matches in case of discarded changes from multitaps.
- taggedContext: Context;
- model: LexicalModel;
-
- tokens: TrackedContextToken[];
- /**
- * How many tokens were removed from the start of the best-matching ancestor.
- * Useful for restoring older states, e.g., when the user moves the caret backwards, we can recover the context at that position.
- */
- indexOffset: number;
-
- // Tracks all search spaces starting at the current token.
- // In the lm-layer's current form, this should only ever have one entry.
- // Leaves 'design space' for if/when we add support for phrase-level corrections/predictions.
- searchSpace: SearchSpace[] = [];
-
- constructor(source: TrackedContextState);
- constructor(model: LexicalModel);
- constructor(obj: TrackedContextState | LexicalModel) {
- if(obj instanceof TrackedContextState) {
- let source = obj;
- // Be sure to deep-copy the tokens! Pointer-aliasing is bad here.
- this.tokens = source.tokens.map(function(token) {
- let copy = new TrackedContextToken();
- copy.raw = token.raw;
- copy.replacements = [].concat(token.replacements);
- copy.activeReplacementId = token.activeReplacementId;
- copy.transformDistributions = [].concat(token.transformDistributions);
-
- if(token.replacementText) {
- copy.replacementText = token.replacementText;
- }
-
- return copy;
- });
-
- this.indexOffset = 0;
- const lexicalModel = this.model = obj.model;
- this.taggedContext = obj.taggedContext;
-
- if(lexicalModel?.traverseFromRoot) {
- // We need to construct a separate search space from other ContextStates.
- //
- // In case we are unable to perfectly track context (say, due to multitaps)
- // we need to ensure that only fully-utilized keystrokes are considered.
- this.searchSpace = obj.searchSpace.map((space) => new SearchSpace(space));
- }
- } else {
- let lexicalModel = obj;
- this.tokens = [];
- this.indexOffset = Number.MIN_SAFE_INTEGER;
- this.model = lexicalModel;
-
- if(lexicalModel && lexicalModel.traverseFromRoot) {
- this.searchSpace = [new SearchSpace(lexicalModel)];
- }
- }
- }
-
- get head(): TrackedContextToken {
- return this.tokens[0];
- }
-
- get tail(): TrackedContextToken {
- return this.tokens[this.tokens.length - 1];
- }
-
- popHead() {
- this.tokens.splice(0, 2);
- this.indexOffset -= 1;
- }
-
- pushTail(token: TrackedContextToken) {
- if(this.model && this.model.traverseFromRoot) {
- this.searchSpace = [new SearchSpace(this.model)]; // yeah, need to update SearchSpace for compatibility
- } else {
- this.searchSpace = [];
- }
- this.tokens.push(token);
-
- let state = this;
- if(state.searchSpace.length > 0) {
- token.transformDistributions.forEach(distrib => state.searchSpace[0].addInput(distrib));
- }
- }
-
- pushWhitespaceToTail(transformDistribution: Distribution = null) {
- let whitespaceToken = new TrackedContextToken();
-
- // Track the Transform that resulted in the whitespace 'token'.
- // Will be needed for phrase-level correction/prediction.
- whitespaceToken.transformDistributions = transformDistribution ? [transformDistribution] : [];
-
- whitespaceToken.raw = null;
- this.tokens.push(whitespaceToken);
- }
-
- /**
- * Used for 14.0's backspace workaround, which flattens all previous Distribution
- * entries because of limitations with direct use of backspace transforms.
- * @param tokenText
- * @param transformId
- */
- replaceTailForBackspace(tokenText: USVString, transformId: number) {
- this.tokens.pop();
-
- // It's a backspace transform; time for special handling!
- //
- // For now, with 14.0, we simply compress all remaining Transforms for the token into
- // multiple single-char transforms. Probabalistically modeling BKSP is quite complex,
- // so we simplify by assuming everything remaining after a BKSP is 'true' and 'intended' text.
- //
- // Note that we cannot just use a single, monolithic transform at this point b/c
- // of our current edit-distance optimization strategy; diagonalization is currently...
- // not very compatible with that.
- let backspacedTokenContext: Distribution[] = textToCharTransforms(tokenText, transformId).map(function(transform) {
- return [{sample: transform, p: 1.0}];
- });
-
- let compactedToken = new TrackedContextToken();
- compactedToken.raw = tokenText;
- compactedToken.transformDistributions = backspacedTokenContext;
- this.pushTail(compactedToken);
- }
-
- updateTail(transformDistribution: Distribution, tokenText?: USVString) {
- let editedToken = this.tail;
-
- // Preserve existing text if new text isn't specified.
- tokenText = tokenText || (tokenText === '' ? '' : editedToken.raw);
-
- if(transformDistribution && transformDistribution.length > 0) {
- editedToken.transformDistributions.push(transformDistribution);
- if(this.searchSpace) {
- this.searchSpace.forEach(space => space.addInput(transformDistribution));
- }
- }
- // Replace old token's raw-text with new token's raw-text.
- editedToken.raw = tokenText;
- }
-
- toRawTokenization() {
- let sequence: USVString[] = [];
-
- for(let token of this.tokens) {
- // Hide any tokens representing wordbreaks. (Thinking ahead to phrase-level possibilities)
- if(token.currentText !== null) {
- sequence.push(token.currentText);
- }
- }
-
- return sequence;
- }
-}
-
-class CircularArray- {
- static readonly DEFAULT_ARRAY_SIZE = 5;
- private circle: Item[];
- private currentHead: number=0;
- private currentTail: number=0;
-
- constructor(size: number = CircularArray.DEFAULT_ARRAY_SIZE) {
- this.circle = Array(size);
- }
-
- get count(): number {
- let diff = this.currentHead - this.currentTail;
-
- if(diff < 0) {
- diff = diff + this.circle.length;
- }
-
- return diff;
- }
-
- get maxCount(): number {
- return this.circle.length;
- }
-
- get oldest(): Item {
- if(this.count == 0) {
- return undefined;
- }
-
- return this.item(0);
- }
-
- get newest(): Item {
- if(this.count == 0) {
- return undefined;
- }
-
- return this.item(this.count - 1);
- }
-
- enqueue(item: Item): Item {
- var prevItem = null;
- let nextHead = (this.currentHead + 1) % this.maxCount;
-
- if(nextHead == this.currentTail) {
- prevItem = this.circle[this.currentTail];
- this.currentTail = (this.currentTail + 1) % this.maxCount;
- }
-
- this.circle[this.currentHead] = item;
- this.currentHead = nextHead;
-
- return prevItem;
- }
-
- dequeue(): Item {
- if(this.currentTail == this.currentHead) {
- return null;
- } else {
- let item = this.circle[this.currentTail];
- this.currentTail = (this.currentTail + 1) % this.maxCount;
- return item;
- }
- }
-
- popNewest(): Item {
- if(this.currentTail == this.currentHead) {
- return null;
- } else {
- let item = this.circle[this.currentHead];
- this.currentHead = (this.currentHead - 1 + this.maxCount) % this.maxCount;
- return item;
- }
- }
-
- /**
- * Returns items contained within the circular array, ordered from 'oldest' to 'newest' -
- * the same order in which the items will be dequeued.
- * @param index
- */
- item(index: number) {
- if(index >= this.count) {
- throw "Invalid array index";
- }
-
- let mappedIndex = (this.currentTail + index) % this.maxCount;
- return this.circle[mappedIndex];
- }
-}
-
-export class ContextTracker extends CircularArray {
- static attemptMatchContext(tokenizedContext: USVString[],
- matchState: TrackedContextState,
- transformDistribution?: Distribution): TrackedContextState {
- // Map the previous tokenized state to an edit-distance friendly version.
- let matchContext: USVString[] = matchState.toRawTokenization();
-
- // Inverted order, since 'match' existed before our new context.
- let mapping = ClassicalDistanceCalculation.computeDistance(matchContext.map(value => ({key: value})),
- tokenizedContext.map(value => ({key: value})),
- 1);
-
- let editPath = mapping.editPath();
-
- let poppedHead = false;
- let pushedTail = false;
-
- // Matters greatly when starting from a nil context.
- if(editPath.length > 1) {
- // First entry: may not be an 'insert' or a 'transpose' op.
- // 'insert' allowed if the next token is 'substitute', as this may occur with an edit path of length 2.
- if((editPath[0] == 'insert' && !(editPath[1] == 'substitute' && editPath.length == 2)) || editPath[0].indexOf('transpose') >= 0) {
- return null;
- } else if(editPath[0] == 'delete') {
- poppedHead = true; // a token from the previous state has been wholly removed.
- }
- }
-
- // Last entry: may not be a 'delete' or a 'transpose' op.
- let tailIndex = editPath.length -1;
- let ignorePenultimateMatch = false;
- if(editPath[tailIndex] == 'delete' || editPath[0].indexOf('transpose') >= 0) {
- return null;
- } else if(editPath[tailIndex] == 'insert') {
- pushedTail = true;
- } else if(tailIndex > 0 && editPath[tailIndex-1] == 'insert' && editPath[tailIndex] == 'substitute') {
- // Tends to happen when accepting suggestions.
- pushedTail = true;
- ignorePenultimateMatch = true;
- }
-
- // Can happen for the first text input after backspace deletes a wordbreaking character,
- // thus the new input continues a previous word while dropping the empty word after
- // that prior wordbreaking character.
- //
- // We can't handle it reliably from this match state, but a previous entry (without the empty token)
- // should still be in the cache and will be reliable for this example case.
- if(tailIndex > 0 && editPath[tailIndex-1] == 'delete' && editPath[tailIndex] == 'substitute') {
- return null;
- }
-
- // Now to check everything in-between: should be exclusively 'match'es.
- for(let index = 1; index < editPath.length - (ignorePenultimateMatch ? 2 : 1); index++) {
- if(editPath[index] != 'match') {
- return null;
- }
- }
-
- // If we've made it here... success! We have a context match!
- let state: TrackedContextState;
-
- if(pushedTail) {
- // On suggestion acceptance, we should update the previous final token.
- // We do it first so that the acceptance is replicated in the new TrackedContextState
- // as well.
- if(ignorePenultimateMatch) {
- // For this case, we were likely called by ModelCompositor.acceptSuggestion(), which
- // would have marked the accepted suggestion.
- matchState.tail.replacementText = tokenizedContext[tokenizedContext.length-2];
- }
-
- state = new TrackedContextState(matchState);
- } else {
- // We're continuing a previously-cached context; create a deep-copy of it.
- // We can't just re-use the old instance, unfortunately; predictions break
- // with multitaps otherwise - we should avoid tracking keystrokes that were
- // rewound.
- //
- // If there are no incoming transforms, though... yeah, re-use is safe then.
- state = !!transformDistribution ? new TrackedContextState(matchState) : matchState;
- }
-
- const hasDistribution = transformDistribution && Array.isArray(transformDistribution);
- let primaryInput = hasDistribution ? transformDistribution[0].sample : null;
- if(primaryInput && primaryInput.insert == "" && primaryInput.deleteLeft == 0 && !primaryInput.deleteRight) {
- primaryInput = null;
- }
-
- const isWhitespace = primaryInput && TransformUtils.isWhitespace(primaryInput);
- const isBackspace = primaryInput && TransformUtils.isBackspace(primaryInput);
- const finalToken = tokenizedContext[tokenizedContext.length-1];
-
- /* Assumption: This is an adequate check for its two sub-branches.
- *
- * Basis:
- * - Assumption: one keystroke may only cause a single token to rotate out of context.
- * - That is, no "reasonable" keystroke would emit enough code points to 'bump' two words simultaneously.
- * - ... This one may need to be loosened a bit... but it should be enough for initial correction testing as-is.
- * - Assumption: one keystroke may only cause a single token to be appended to the context
- * - That is, no "reasonable" keystroke would emit a Transform adding two separate word tokens
- * - For languages using whitespace to word-break, said keystroke would have to include said whitespace to break the assumption.
- */
-
- function maintainLastToken() {
- if(isWhitespace && editPath[tailIndex] == 'match') {
- /*
- We can land here if there are multiple whitespaces in a row.
- There's already an implied whitespace to the left, so we conceptually
- merge the new whitespace with that one.
- */
- return;
- } else if(isBackspace) {
- // Consider backspace entry for this case?
- state.replaceTailForBackspace(finalToken, primaryInput.id);
- } else {
- state.updateTail(primaryInput ? transformDistribution : null, finalToken);
- }
- }
-
- // If there is/was more than one context token available...
- if(editPath.length > 1) {
- // We're removing a context token, but at least one remains.
- if(poppedHead) {
- state.popHead();
- }
-
- // We're adding an additional context token.
- if(pushedTail) {
- const tokenizedTail = tokenizedContext[tokenizedContext.length - 1];
- /*
- * Common-case: most transforms that trigger this case are from pure-whitespace Transforms. MOST.
- *
- * Less-common, but noteworthy: some wordbreaks may occur without whitespace. Example:
- * `"o` => ['"', 'o']. Make sure to double-check against `tokenizedContext`!
- */
- let pushedToken = new TrackedContextToken();
- pushedToken.raw = tokenizedTail;
-
- if(isWhitespace || !primaryInput) {
- state.pushWhitespaceToTail(transformDistribution ?? []);
- // Continuing the earlier assumption, that 'pure-whitespace Transform' does not emit any initial characters
- // for the new word (token), so the input keystrokes do not correspond to the new text token.
- pushedToken.transformDistributions = [];
- } else {
- state.pushWhitespaceToTail();
- // Assumption: Since we only allow one-transform-at-a-time changes between states, we shouldn't be missing
- // any metadata used to construct the new context state token.
- pushedToken.transformDistributions = transformDistribution ? [transformDistribution] : [];
- }
-
- state.pushTail(pushedToken);
- } else {
- // We're editing the final context token.
- // TODO: Assumption: we didn't 'miss' any inputs somehow.
- // As is, may be prone to fragility should the lm-layer's tracked context 'desync' from its host's.
- maintainLastToken();
- }
- // There is only one word in the context.
- } else {
- // TODO: Assumption: we didn't 'miss' any inputs somehow.
- // As is, may be prone to fragility should the lm-layer's tracked context 'desync' from its host's.
-
- if(editPath[tailIndex] == 'insert') {
- // Construct appropriate initial token.
- let token = new TrackedContextToken();
- token.raw = tokenizedContext[0];
- token.transformDistributions = [transformDistribution];
- state.pushTail(token);
- } else {
- // Edit the lone context token.
- maintainLastToken();
- }
- }
- return state;
- }
-
- private static modelContextState(tokenizedContext: USVString[],
- transformDistribution: Distribution,
- lexicalModel: LexicalModel): TrackedContextState {
- let baseTokens = tokenizedContext.map(function(entry) {
- let token = new TrackedContextToken();
- token.raw = entry;
- if(token.raw) {
- token.transformDistributions = textToCharTransforms(token.raw).map(function(transform) {
- return [{sample: transform, p: 1.0}];
- });
- } else {
- // Helps model context-final wordbreaks.
- token.transformDistributions = [];
- }
- return token;
- });
-
- // And now build the final context state object, which includes whitespace 'tokens'.
- let state = new TrackedContextState(lexicalModel);
-
- if(baseTokens.length > 0) {
- state.pushTail(baseTokens.splice(0, 1)[0]);
- }
-
- while(baseTokens.length > 0) {
- state.pushWhitespaceToTail();
- state.pushTail(baseTokens.splice(0, 1)[0]);
- }
-
- if(state.tokens.length == 0) {
- let token = new TrackedContextToken();
- token.raw = '';
-
- state.pushTail(token);
- }
-
- return state;
- }
-
- /**
- * Compares the current, post-input context against the most recently-seen contexts from previous prediction calls, returning
- * the most information-rich `TrackedContextState` possible. If a match is found, the state will be annotated with the
- * input information provided to previous prediction calls and persisted correction-search calculations for re-use.
- *
- * @param model
- * @param context
- * @param transformDistribution
- */
- analyzeState(model: LexicalModel,
- context: Context,
- transformDistribution?: Distribution): TrackedContextState {
- if(!model.traverseFromRoot) {
- // Assumption: LexicalModel provides a valid traverseFromRoot function. (Is technically optional)
- // Without it, no 'corrections' may be made; the model can only be used to predict, not correct.
- throw "This lexical model does not provide adequate data for correction algorithms and context reuse";
- }
-
- let tokenizedContext = tokenize(model.wordbreaker || defaultWordbreaker, context);
-
- if(tokenizedContext.left.length > 0) {
- for(let i = this.count - 1; i >= 0; i--) {
- const priorMatchState = this.item(i);
- const priorTaggedContext = priorMatchState.taggedContext;
- if(priorTaggedContext && transformDistribution && transformDistribution.length > 0) {
- // Using the potential `matchState` + the incoming transform, do the results line up for
- // our observed context? If not, skip it.
- //
- // Necessary to properly handle multitaps, as there are context rewinds that the
- // predictive-text engine is not otherwise warned about.
- //
- // `priorTaggedContext` must not be `null`!
- const doublecheckContext = applyTransform(transformDistribution[0].sample, priorTaggedContext);
- if(doublecheckContext.left != context.left) {
- continue;
- }
- } else if(priorTaggedContext?.left != context.left) {
- continue;
- }
-
- let resultState = ContextTracker.attemptMatchContext(tokenizedContext.left, this.item(i), transformDistribution);
-
- if(resultState) {
- // Keep it reasonably current! And it's probably fine to have it more than once
- // in the history. However, if it's the most current already, there's no need
- // to refresh it.
- if(this.newest != resultState && this.newest != priorMatchState) {
- // Already has a taggedContext.
- this.enqueue(priorMatchState);
- }
-
- resultState.taggedContext = context;
- if(resultState != this.item(i)) {
- this.enqueue(resultState);
- }
- return resultState;
- }
- }
- }
-
- // Else: either empty OR we've detected a 'new context'. Initialize from scratch; no prior input information is
- // available. Only the results of the prior inputs are known.
- //
- // Assumption: as a caret needs to move to context before any actual transform distributions occur,
- // this state is only reached on caret moves; thus, transformDistribution is actually just a single null transform.
- let state = ContextTracker.modelContextState(tokenizedContext.left, transformDistribution, model);
- state.taggedContext = context;
- this.enqueue(state);
- return state;
- }
-
- clearCache() {
- while(this.count > 0) {
- this.dequeue();
- }
- }
-}
diff --git a/common/web/lm-worker/src/test/mocha/cases/edit-distance/context-tracker.js b/common/web/lm-worker/src/test/mocha/cases/edit-distance/context-tracker.js
deleted file mode 100644
index 5c9d4812b6..0000000000
--- a/common/web/lm-worker/src/test/mocha/cases/edit-distance/context-tracker.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import { assert } from 'chai';
-
-import { ContextTracker } from '#./correction/context-tracker.js';
-import ModelCompositor from '#./model-compositor.js';
-import * as models from '#./models/index.js';
-
-import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
-
-describe('ContextTracker', function() {
- function toWrapperDistribution(transform) {
- return [{
- sample: transform,
- p: 1.0
- }];
- }
-
- describe('attemptMatchContext', function() {
- it("properly matches and aligns when lead token is removed", function() {
- let existingContext = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
- let transform = {
- insert: '',
- deleteLeft: 0
- }
- let newContext = existingContext.slice(0);
- newContext.splice(0, 1);
- let rawTokens = ["apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor"];
-
- let existingState = ContextTracker.modelContextState(existingContext);
- let state = ContextTracker.attemptMatchContext(newContext, existingState, null, toWrapperDistribution(transform));
- assert.isNotNull(state);
- assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
- });
-
- it("properly matches and aligns when final token is edited", function() {
- let existingContext = ["an", "apple", "a", "day", "keeps", "the", "docto"];
- let transform = {
- insert: 'r',
- deleteLeft: 0
- }
- let newContext = existingContext.slice(0);
- newContext[newContext.length - 1] = 'doctor';
- let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor"];
-
- let existingState = ContextTracker.modelContextState(existingContext);
- let state = ContextTracker.attemptMatchContext(newContext, existingState, null, toWrapperDistribution(transform));
- assert.isNotNull(state);
- assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
- });
-
- it("properly matches and aligns when a 'wordbreak' is added", function() {
- let existingContext = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
- let transform = {
- insert: ' ',
- deleteLeft: 0
- }
- let newContext = existingContext.slice(0);
- newContext.push('');
- let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor", null, ""];
-
- let existingState = ContextTracker.modelContextState(existingContext);
- let state = ContextTracker.attemptMatchContext(newContext, existingState, toWrapperDistribution(transform));
- assert.isNotNull(state);
- assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
-
- // The 'wordbreak' transform
- assert.isNotEmpty(state.tokens[state.tokens.length - 2].transformDistributions);
- assert.isEmpty(state.tokens[state.tokens.length - 1].transformDistributions);
- });
-
- it("properly matches and aligns when an implied 'wordbreak' occurs (as when following \"'\")", function() {
- let existingContext = ["'"];
- let transform = {
- insert: 'a',
- deleteLeft: 0
- }
- let newContext = existingContext.slice(0);
- newContext.push('a'); // The incoming transform should produce a new token WITH TEXT.
- let rawTokens = ["'", null, "a"];
-
- let existingState = ContextTracker.modelContextState(existingContext);
- let state = ContextTracker.attemptMatchContext(newContext, existingState, toWrapperDistribution(transform));
- assert.isNotNull(state);
- assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
-
- // The 'wordbreak' transform
- assert.isEmpty(state.tokens[state.tokens.length - 2].transformDistributions);
- assert.isNotEmpty(state.tokens[state.tokens.length - 1].transformDistributions);
- });
-
- it("properly matches and aligns when lead token is removed AND a 'wordbreak' is added'", function() {
- let existingContext = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
- let transform = {
- insert: ' ',
- deleteLeft: 0
- }
- let newContext = existingContext.slice(0);
- newContext.splice(0, 1);
- newContext.push('');
- let rawTokens = ["apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor", null, ""];
-
- let existingState = ContextTracker.modelContextState(existingContext);
- let state = ContextTracker.attemptMatchContext(newContext, existingState, toWrapperDistribution(transform));
- assert.isNotNull(state);
- assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
-
- // The 'wordbreak' transform
- assert.isNotEmpty(state.tokens[state.tokens.length - 2].transformDistributions);
- assert.isEmpty(state.tokens[state.tokens.length - 1].transformDistributions);
- });
- });
-
- describe('modelContextState', function() {
- it('models without final wordbreak', function() {
- let context = ["an", "apple", "a", "day", "keeps", "the", "doctor"];
- let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor"];
-
- let state = ContextTracker.modelContextState(context);
- assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
- });
-
- it('models with final wordbreak', function() {
- let context = ["an", "apple", "a", "day", "keeps", "the", "doctor", ""];
- let rawTokens = ["an", null, "apple", null, "a", null, "day", null, "keeps", null, "the", null, "doctor", null, ""];
-
- let state = ContextTracker.modelContextState(context);
- assert.deepEqual(state.tokens.map(token => token.raw), rawTokens);
- });
- });
-
- describe('suggestion acceptance tracking', function() {
- let englishPunctuation = {
- quotesForKeepSuggestion: { open: `“`, close: `”`},
- insertAfterWord: ' '
- };
-
- it('tracks an accepted suggestion', function() {
- let baseSuggestion = {
- transform: {
- insert: 'world ',
- deleteLeft: 3,
- id: 0
- },
- transformId: 0,
- id: 1,
- displayAs: 'world'
- };
-
- let baseContext = {
- left: 'hello wor', startOfBuffer: true, endOfBuffer: true
- };
-
- // Represents the keystroke that triggered the suggestion. It's not technically part
- // of the Context when the suggestion is built.
- let postTransform = {
- insert: 'l',
- deleteLeft: 0
- };
-
- let options = {
- punctuation: englishPunctuation
- };
-
- let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), options);
- let compositor = new ModelCompositor(model);
- let baseContextState = compositor.contextTracker.analyzeState(model, baseContext);
-
- baseContextState.tail.replacements = [{
- suggestion: baseSuggestion,
- tokenWidth: 1
- }];
-
- let reversion = compositor.acceptSuggestion(baseSuggestion, baseContext, postTransform);
-
- // Actual test assertion - was the replacement tracked?
- assert.equal(baseContextState.tail.activeReplacementId, baseSuggestion.id);
- assert.equal(reversion.id, -baseSuggestion.id);
-
- // Next step - on the followup context, is the replacement still active?
- let postContext = models.applyTransform(baseSuggestion.transform, baseContext);
- let postContextState = compositor.contextTracker.analyzeState(model, postContext);
-
- // Penultimate token corresponds to whitespace, which does not have a 'raw' representation.
- assert.isNull(postContextState.tokens[postContextState.tokens.length - 2].raw);
-
- // Final token is empty (follows a wordbreak)
- assert.equal(postContextState.tail.raw, '');
- });
- });
-});
\ No newline at end of file
diff --git a/common/web/lm-worker/src/test/test-runner/web-test-runner.CI.config.mjs b/common/web/lm-worker/src/test/test-runner/web-test-runner.CI.config.mjs
deleted file mode 100644
index d18a8a9cb0..0000000000
--- a/common/web/lm-worker/src/test/test-runner/web-test-runner.CI.config.mjs
+++ /dev/null
@@ -1,13 +0,0 @@
-// @ts-check
-import BASE_CONFIG from './web-test-runner.config.mjs';
-import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs';
-import { sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs';
-
-/** @type {import('@web/test-runner').TestRunnerConfig} */
-export default {
- ...BASE_CONFIG,
- reporters: [
- teamcityReporter(), /* custom-written, for CI-friendly reports */
- sessionStabilityReporter({ciMode: true})
- ]
-}
\ No newline at end of file
diff --git a/common/web/lm-worker/tsconfig.json b/common/web/lm-worker/tsconfig.json
deleted file mode 100644
index f439e96940..0000000000
--- a/common/web/lm-worker/tsconfig.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "extends": "../../models/tsconfig.kmw-worker-base.json",
-
- "compilerOptions": {
- "baseUrl": "./",
- "outDir": "build/obj",
- "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
- "rootDir": "./src/main",
-
- // As this one is the one that directly interfaces with the worker (from the inside)
- "lib": ["webworker", "es6"],
- },
- "references": [
- // types
- { "path": "../../models/types" },
- { "path": "../lm-message-types" },
- // modules
- { "path": "../keyman-version" },
- { "path": "../utils" },
- { "path": "../../models/templates" },
- { "path": "../../models/wordbreakers" },
- ],
- "include": [
- "src/main/**/*.ts"
- ]
-}
diff --git a/common/web/recorder/tsconfig.json b/common/web/recorder/tsconfig.json
deleted file mode 100644
index 1a38d48f03..0000000000
--- a/common/web/recorder/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "extends": "../tsconfig.kmw-main-base.json",
-
- "compilerOptions": {
- "types": ["node"],
- "baseUrl": "./",
- "outDir": "build/obj/",
- "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo",
- "rootDir": "./src"
- },
-
- "include": [
- "src/**/*.ts"
- ],
-
- "references": [
- { "path": "../keyman-version" },
- { "path": "../utils/" },
- { "path": "../keyboard-processor/" },
- { "path": "../lm-message-types" }
- ],
-}
diff --git a/common/web/types/build.sh b/common/web/types/build.sh
index df11540b42..7141bc1072 100755
--- a/common/web/types/build.sh
+++ b/common/web/types/build.sh
@@ -73,40 +73,25 @@ function compile_schemas() {
node tools/schema-bundler.js
}
-function copy_cldr_imports() {
- # Store CLDR imports
- # load all versions that have a cldr_info.json
- for CLDR_INFO_PATH in "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/"*/cldr_info.json
- do
- # TODO-LDML: developer/src/inst/download.in.mak needs these also...
- CLDR_PATH=$(dirname "$CLDR_INFO_PATH")
- CLDR_VER=$(basename "$CLDR_PATH")
- mkdir -p "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER"
- # TODO-LDML: When these are copied, the DOCTYPE will break due to the wrong path. We don't use the DTD so it should be OK.
- cp "$CLDR_INFO_PATH" "$CLDR_PATH/import/"*.xml "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER/"
- done
-}
function do_configure() {
compile_schemas
verify_npm_setup
}
-function do_build() {
- copy_cldr_imports
- tsc --build
-}
-
function do_test() {
eslint .
tsc --build test
- c8 --skip-full --reporter=lcov --reporter=text mocha "${builder_extra_params[@]}"
+ 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[@]}"
+ 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 configure do_configure
-builder_run_action build do_build
+builder_run_action build tsc --build
builder_run_action test do_test
builder_run_action publish builder_publish_npm
diff --git a/common/web/types/package.json b/common/web/types/package.json
index 9327e48590..20f84bc140 100644
--- a/common/web/types/package.json
+++ b/common/web/types/package.json
@@ -31,24 +31,17 @@
"dependencies": {
"@keymanapp/ldml-keyboard-constants": "*",
"@keymanapp/keyman-version": "*",
- "restructure": "3.0.1",
- "semver": "^7.5.2",
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
+ "restructure": "3.0.1"
},
"devDependencies": {
"@types/chai": "^4.1.7",
- "@types/git-diff": "^2.0.3",
"@types/mocha": "^5.2.7",
"@types/node": "^20.4.1",
- "@types/semver": "^7.3.12",
- "@types/xml2js": "^0.4.5",
"ajv": "^8.12.0",
"ajv-cli": "^5.0.0",
"ajv-formats": "^2.1.1",
"c8": "^7.12.0",
"chalk": "^2.4.2",
- "git-diff": "^2.0.6",
"hexy": "^0.3.4",
"mocha": "^8.4.0",
"typescript": "^5.4.5"
diff --git a/common/web/types/src/consts/modifier-key-constants.ts b/common/web/types/src/consts/modifier-key-constants.ts
new file mode 100644
index 0000000000..245cbf70f3
--- /dev/null
+++ b/common/web/types/src/consts/modifier-key-constants.ts
@@ -0,0 +1,33 @@
+/*
+ * Keyman is copyright (C) SIL International. MIT License.
+ *
+ * Modifier key bit-flags
+ */
+
+export const ModifierKeyConstants = {
+ // Define Keyman Developer modifier bit-flags (exposed for use by other modules)
+ // Compare against /common/include/kmx_file.h. CTRL+F "#define LCTRLFLAG" to find the secton.
+ LCTRLFLAG: 0x0001, // Left Control flag
+ RCTRLFLAG: 0x0002, // Right Control flag
+ LALTFLAG: 0x0004, // Left Alt flag
+ RALTFLAG: 0x0008, // Right Alt flag
+ K_SHIFTFLAG: 0x0010, // Either shift flag
+ K_CTRLFLAG: 0x0020, // Either ctrl flag
+ K_ALTFLAG: 0x0040, // Either alt flag
+ K_METAFLAG: 0x0080, // Either Meta-key flag (tentative). Not usable in keyboard rules;
+ // Used internally (currently, only by KMW) to ensure Meta-key
+ // shortcuts safely bypass rules
+ // Meta key = Command key on macOS, Windows key on Windows/Linux
+ CAPITALFLAG: 0x0100, // Caps lock on
+ NOTCAPITALFLAG: 0x0200, // Caps lock NOT on
+ NUMLOCKFLAG: 0x0400, // Num lock on
+ NOTNUMLOCKFLAG: 0x0800, // Num lock NOT on
+ SCROLLFLAG: 0x1000, // Scroll lock on
+ NOTSCROLLFLAG: 0x2000, // Scroll lock NOT on
+ ISVIRTUALKEY: 0x4000, // It is a Virtual Key Sequence
+ VIRTUALCHARKEY: 0x8000, // Keyman 6.0: Virtual Key Cap Sequence NOT YET
+
+ // Note: OTHER_MODIFIER = 0x10000, used by KMX+ for the
+ // other modifier flag in layers, > 16 bit so not available here.
+ // See keys_mod_other in keyman_core_ldml.ts
+}
diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts
index 3347660f01..7b083241bd 100644
--- a/common/web/types/src/consts/virtual-key-constants.ts
+++ b/common/web/types/src/consts/virtual-key-constants.ts
@@ -1,6 +1,5 @@
// Define standard keycode numbers (exposed for use by other modules)
-// TODO-LDML: merge with common\web\keyboard-processor\src\text\codes.ts
/**
* May include non-US virtual key codes
@@ -128,7 +127,7 @@ export const USVirtualKeyCodes = {
'k_?C1':193,
K_oDF:0xDF,
K_ODF:0xDF,
- /*K_LOPT:50001,
+ K_LOPT:50001,
K_ROPT:50002,
K_NUMERALS:50003,
K_SYMBOLS:50004,
@@ -139,7 +138,7 @@ export const USVirtualKeyCodes = {
K_SHIFTED:50009,
K_ALTGR:50010,
K_TABBACK:50011,
- K_TABFWD:50012*/
+ K_TABFWD:50012
};
const k = USVirtualKeyCodes;
diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts
index 757316a70a..786d9213e2 100644
--- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts
+++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts
@@ -58,7 +58,7 @@ export const PRIVATE_USE_IDS = [
] as const;
/* A map of key field names with values matching the `typeof` the corresponding property
- * exists in common/web/keyboard-processor, keyboards/activeLayout.ts.
+ * exists in /web/src/engine/keyboard/src/keyboards/activeLayout.ts.
*
* Make sure that when one is updated, the other also is. TS types are compile-time only,
* so the run-time-accessible mapping in activeLayout.ts cannot be auto-generated by TS. */
diff --git a/common/web/types/src/kmx/element-string.ts b/common/web/types/src/kmx/kmx-plus/element-string.ts
similarity index 94%
rename from common/web/types/src/kmx/element-string.ts
rename to common/web/types/src/kmx/kmx-plus/element-string.ts
index 1b24c3eb45..c7597b4f6b 100644
--- a/common/web/types/src/kmx/element-string.ts
+++ b/common/web/types/src/kmx/kmx-plus/element-string.ts
@@ -1,8 +1,9 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { DependencySections, StrsItem, UsetItem } from './kmx-plus.js';
-import { ElementParser, ElementSegment, ElementType } from '../ldml-keyboard/pattern-parser.js';
-import { MATCH_HEX_ESCAPE } from '../util/consts.js';
-import { unescapeOneQuadString } from '../util/util.js';
+import { ElementParser, ElementSegment, ElementType } from '../../ldml-keyboard/pattern-parser.js';
+import { util } from '@keymanapp/common-types';
+import MATCH_HEX_ESCAPE = util.MATCH_HEX_ESCAPE;
+import unescapeOneQuadString = util.unescapeOneQuadString;
export enum ElemElementFlags {
none = 0,
diff --git a/common/web/types/src/kmx/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
similarity index 97%
rename from common/web/types/src/kmx/kmx-plus.ts
rename to common/web/types/src/kmx/kmx-plus/kmx-plus.ts
index 6e3c73d33c..1d19cdd4d0 100644
--- a/common/web/types/src/kmx/kmx-plus.ts
+++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts
@@ -1,12 +1,18 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
import * as r from 'restructure';
import { ElementString } from './element-string.js';
-import { ListItem } from './string-list.js';
-import { isOneChar, toOneChar, unescapeString, escapeStringForRegex } from '../util/util.js';
-import { KMXFile } from './kmx.js';
-import { UnicodeSetParser, UnicodeSet } from '@keymanapp/common-types';
-import { VariableParser } from '../ldml-keyboard/pattern-parser.js';
-import { MarkerParser } from '../ldml-keyboard/pattern-parser.js';
+import { ListItem } from '../../ldml-keyboard/string-list.js';
+import { util } from '@keymanapp/common-types';
+import { KMX } from '@keymanapp/common-types';
+import { UnicodeSetParser, UnicodeSet } from '../../ldml-keyboard/unicodeset-parser-api.js';
+import { VariableParser } from '../../ldml-keyboard/pattern-parser.js';
+import { MarkerParser } from '../../ldml-keyboard/pattern-parser.js';
+
+import isOneChar = util.isOneChar;
+import toOneChar = util.toOneChar;
+import unescapeString = util.unescapeString;
+import escapeStringForRegex = util.escapeStringForRegex;
+import KMXFile = KMX.KMXFile;
// Implementation of file structures from /core/src/ldml/C7043_ldml.md
// Writer in kmx-builder.ts
diff --git a/common/web/types/src/kmx/kmx.ts b/common/web/types/src/kmx/kmx.ts
index 35c0c61c3d..4f53e29fde 100644
--- a/common/web/types/src/kmx/kmx.ts
+++ b/common/web/types/src/kmx/kmx.ts
@@ -1,4 +1,5 @@
import * as r from 'restructure';
+import { ModifierKeyConstants } from '../consts/modifier-key-constants.js';
/* Definitions from kmx_file.h. Must be kept in sync */
@@ -343,25 +344,25 @@ export class KMXFile {
public static readonly HK_CTRL = 0x00020000;
public static readonly HK_SHIFT = 0x00040000;
- public static readonly LCTRLFLAG = 0x0001; // Left Control flag
- public static readonly RCTRLFLAG = 0x0002; // Right Control flag
- public static readonly LALTFLAG = 0x0004; // Left Alt flag
- public static readonly RALTFLAG = 0x0008; // Right Alt flag
- public static readonly K_SHIFTFLAG = 0x0010; // Either shift flag
- public static readonly K_CTRLFLAG = 0x0020; // Either ctrl flag
- public static readonly K_ALTFLAG = 0x0040; // Either alt flag
+ public static readonly LCTRLFLAG = ModifierKeyConstants.LCTRLFLAG; // Left Control flag
+ public static readonly RCTRLFLAG = ModifierKeyConstants.RCTRLFLAG; // Right Control flag
+ public static readonly LALTFLAG = ModifierKeyConstants.LALTFLAG; // Left Alt flag
+ public static readonly RALTFLAG = ModifierKeyConstants.RALTFLAG; // Right Alt flag
+ public static readonly K_SHIFTFLAG = ModifierKeyConstants.K_SHIFTFLAG; // Either shift flag
+ public static readonly K_CTRLFLAG = ModifierKeyConstants.K_CTRLFLAG; // Either ctrl flag
+ public static readonly K_ALTFLAG = ModifierKeyConstants.K_ALTFLAG; // Either alt flag
//public static readonly K_METAFLAG = 0x0080; // Either Meta-key flag (tentative). Not usable in keyboard rules;
// Used internally (currently, only by KMW) to ensure Meta-key
// shortcuts safely bypass rules
// Meta key = Command key on macOS, Windows key on Windows
- public static readonly CAPITALFLAG = 0x0100; // Caps lock on
- public static readonly NOTCAPITALFLAG = 0x0200; // Caps lock NOT on
- public static readonly NUMLOCKFLAG = 0x0400; // Num lock on
- public static readonly NOTNUMLOCKFLAG = 0x0800; // Num lock NOT on
- public static readonly SCROLLFLAG = 0x1000; // Scroll lock on
- public static readonly NOTSCROLLFLAG = 0x2000; // Scroll lock NOT on
- public static readonly ISVIRTUALKEY = 0x4000; // It is a Virtual Key Sequence
- public static readonly VIRTUALCHARKEY = 0x8000; // Keyman 6.0: Virtual Key Cap Sequence NOT YET
+ public static readonly CAPITALFLAG = ModifierKeyConstants.CAPITALFLAG; // Caps lock on
+ public static readonly NOTCAPITALFLAG = ModifierKeyConstants.NOTCAPITALFLAG; // Caps lock NOT on
+ public static readonly NUMLOCKFLAG = ModifierKeyConstants.NUMLOCKFLAG; // Num lock on
+ public static readonly NOTNUMLOCKFLAG = ModifierKeyConstants.NOTNUMLOCKFLAG; // Num lock NOT on
+ public static readonly SCROLLFLAG = ModifierKeyConstants.SCROLLFLAG; // Scroll lock on
+ public static readonly NOTSCROLLFLAG = ModifierKeyConstants.NOTSCROLLFLAG; // Scroll lock NOT on
+ public static readonly ISVIRTUALKEY = ModifierKeyConstants.ISVIRTUALKEY; // It is a Virtual Key Sequence
+ public static readonly VIRTUALCHARKEY = ModifierKeyConstants.VIRTUALCHARKEY; // Keyman 6.0: Virtual Key Cap Sequence NOT YET
// Note: OTHER_MODIFIER = 0x10000, used by KMX+ for the
// other modifier flag in layers, > 16 bit so not available here.
diff --git a/common/web/types/src/kmx/string-list.ts b/common/web/types/src/ldml-keyboard/string-list.ts
similarity index 94%
rename from common/web/types/src/kmx/string-list.ts
rename to common/web/types/src/ldml-keyboard/string-list.ts
index f68e905369..5d11a5ca7c 100644
--- a/common/web/types/src/kmx/string-list.ts
+++ b/common/web/types/src/ldml-keyboard/string-list.ts
@@ -1,5 +1,5 @@
-import { OrderedStringList } from '../ldml-keyboard/pattern-parser.js';
-import { DependencySections, StrsItem, StrsOptions } from './kmx-plus.js';
+import { OrderedStringList } from './pattern-parser.js';
+import { DependencySections, StrsItem, StrsOptions } from '../kmx/kmx-plus/kmx-plus.js';
/**
* A single entry in a ListItem.
diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts
index 0ddee928c2..7e0dc2f7b2 100644
--- a/common/web/types/src/main.ts
+++ b/common/web/types/src/main.ts
@@ -1,53 +1,19 @@
export * as KMX from './kmx/kmx.js';
-export * as KMXPlus from './kmx/kmx-plus.js';
-export { default as KMXBuilder } from './kmx/kmx-builder.js';
export { KmxFileReader, KmxFileReaderError } from './kmx/kmx-file-reader.js';
export * as KeymanTargets from './kmx/keyman-targets.js';
export * as VisualKeyboard from './kvk/visual-keyboard.js';
-export { default as KMXPlusBuilder} from './kmx/kmx-plus-builder/kmx-plus-builder.js';
export { default as KvkFileReader } from './kvk/kvk-file-reader.js';
-export { default as KvksFileReader } from './kvk/kvks-file-reader.js';
export { default as KvkFileWriter } from './kvk/kvk-file-writer.js';
-export { default as KvksFileWriter } from './kvk/kvks-file-writer.js';
export * as KvkFile from './kvk/kvk-file.js';
-export * as KvksFile from './kvk/kvks-file.js';
-export * as LDMLKeyboard from './ldml-keyboard/ldml-keyboard-xml.js';
-export { LDMLKeyboardTestDataXMLSourceFile } from './ldml-keyboard/ldml-keyboard-testdata-xml.js';
-export { UnicodeSetParser, UnicodeSet } from './ldml-keyboard/unicodeset-parser-api.js';
-export { VariableParser, MarkerParser } from './ldml-keyboard/pattern-parser.js';
-export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from './ldml-keyboard/ldml-keyboard-xml-reader.js';
+export { USVirtualKeyCodes } from './consts/virtual-key-constants.js';
export * as Constants from './consts/virtual-key-constants.js';
-
-export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, CompilerOptions, CompilerEvent, CompilerErrorNamespace,
- CompilerErrorSeverity, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerCallbackOptions,
- CompilerError, CompilerMessageSpec, CompilerMessageSpecWithException, compilerErrorSeverity, CompilerErrorMask, CompilerFileCallbacks, compilerErrorSeverityName,
- compilerErrorFormatCode, CompilerMessageDef,
- compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS,
- ALL_COMPILER_LOG_FORMATS, CompilerLogFormat,
- CompilerMessageOverride,
- CompilerMessageOverrideMap,
-
- KeymanCompilerArtifact,
- KeymanCompilerArtifactOptional,
- KeymanCompilerArtifacts,
- KeymanCompilerResult,
- KeymanCompiler
-
- } from './util/compiler-interfaces.js';
-export { CommonTypesMessages } from './util/common-events.js';
+export { ModifierKeyConstants } from './consts/modifier-key-constants.js';
export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js';
-export { TouchLayoutFileReader } from './keyman-touch-layout/keyman-touch-layout-file-reader.js';
-export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './keyman-touch-layout/keyman-touch-layout-file-writer.js';
-export * as KPJ from './kpj/kpj-file.js';
-export { KPJFileReader } from './kpj/kpj-file-reader.js';
-export { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, } from './kpj/keyman-developer-project.js';
-
-export * as KpsFile from './package/kps-file.js';
export * as KmpJsonFile from './package/kmp-json-file.js';
export { Uni_IsSurrogate1, Uni_IsSurrogate2 } from './util/util.js';
@@ -58,4 +24,8 @@ export * as KeymanFileTypes from './util/file-types.js';
export * as Schemas from './schemas.js';
export * as SchemaValidators from './schema-validators.js';
-export * as xml2js from './deps/xml2js/xml2js.js';
\ No newline at end of file
+export * as KMXPlus from './kmx/kmx-plus/kmx-plus.js';
+// TODO: these exports are really not well named
+export { UnicodeSetParser, UnicodeSet } from './ldml-keyboard/unicodeset-parser-api.js';
+export { VariableParser, MarkerParser } from './ldml-keyboard/pattern-parser.js';
+export { ElementString } from './kmx/kmx-plus/element-string.js';
diff --git a/common/web/types/test/helpers/index.ts b/common/web/types/test/helpers/index.ts
index 9b2be1d605..3e305eeca8 100644
--- a/common/web/types/test/helpers/index.ts
+++ b/common/web/types/test/helpers/index.ts
@@ -1,5 +1,4 @@
import * as path from "path";
-import * as fs from "fs";
import { fileURLToPath } from "url";
/**
@@ -12,26 +11,3 @@ import { fileURLToPath } from "url";
export function makePathToFixture(...components: string[]): string {
return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url));
}
-
-export function loadFile(filename: string | URL): Buffer {
- return fs.readFileSync(filename);
-}
-
-export function resolveFilename(baseFilename: string, filename: 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;
-}
diff --git a/common/web/types/tsconfig.json b/common/web/types/tsconfig.json
index 0473ae92ee..cd779632a0 100644
--- a/common/web/types/tsconfig.json
+++ b/common/web/types/tsconfig.json
@@ -9,7 +9,6 @@
"preserveConstEnums": true,
},
"include": [
- "src/deps/xml2js/*.js",
"src/**/*.ts",
"src/schemas/*.mjs", // Import the validators
],
diff --git a/core/cross-mac-arm64.build b/core/cross-mac-arm64.build
index 720e9f3100..faf3e6e9c1 100644
--- a/core/cross-mac-arm64.build
+++ b/core/cross-mac-arm64.build
@@ -5,9 +5,9 @@ cpu = 'arm64'
endian = 'little'
[binaries]
-c = ['clang', '-arch', 'arm64']
-objc = ['clang', '-arch', 'arm64']
-cpp = ['clang++', '-arch', 'arm64']
+c = ['clang', '-arch', 'arm64', '-mmacosx-version-min=10.13']
+objc = ['clang', '-arch', 'arm64', '-mmacosx-version-min=10.13']
+cpp = ['clang++', '-arch', 'arm64', '-mmacosx-version-min=10.13']
ar = 'ar'
ld = 'ld'
strip = 'strip'
diff --git a/core/cross-mac-x86_64.build b/core/cross-mac-x86_64.build
index 2ab2c62a60..3378fe11a4 100644
--- a/core/cross-mac-x86_64.build
+++ b/core/cross-mac-x86_64.build
@@ -5,9 +5,9 @@ cpu = 'x86_64'
endian = 'little'
[binaries]
-c = ['clang', '-arch', 'x86_64']
-objc = ['clang', '-arch', 'x86_64']
-cpp = ['clang++', '-arch', 'x86_64']
+c = ['clang', '-arch', 'x86_64', '-mmacosx-version-min=10.13']
+objc = ['clang', '-arch', 'x86_64', '-mmacosx-version-min=10.13']
+cpp = ['clang++', '-arch', 'x86_64', '-mmacosx-version-min=10.13']
ar = 'ar'
ld = 'ld'
strip = 'strip'
diff --git a/core/doc/meson.build b/core/doc/meson.build
index 6039e2e2dd..a0f2bc4e6d 100644
--- a/core/doc/meson.build
+++ b/core/doc/meson.build
@@ -20,7 +20,7 @@ if hotdoc.found()
deps = files(
'../include/keyman/keyman_core_api.h',
'../src/jsonpp.hpp',
- '../src/utfcodec.hpp'
+ '../../common/cpp/utfcodec.hpp'
)
docs = custom_target('docs',
diff --git a/core/src/meson.build b/core/src/meson.build
index 212b56f1c2..41c198543c 100644
--- a/core/src/meson.build
+++ b/core/src/meson.build
@@ -126,7 +126,7 @@ core_files = files(
'keyboard.cpp',
'state.cpp',
'jsonpp.cpp',
- 'utfcodec.cpp',
+ '../../common/cpp/utfcodec.cpp',
)
mock_files = files(
diff --git a/core/tests/meson.build b/core/tests/meson.build
index 84c17d4220..90e0ab2a54 100644
--- a/core/tests/meson.build
+++ b/core/tests/meson.build
@@ -10,7 +10,10 @@
cmpfiles = ['-c', 'import sys; a = open(sys.argv[1], \'r\').read(); b = open(sys.argv[2], \'r\').read(); exit(not (a==b))']
stnds = join_paths(meson.current_source_dir(), 'standards')
-libsrc = include_directories(join_paths('../', 'src'))
+libsrc = include_directories(
+ '../src',
+ '../../common/include'
+)
# kmx_test_source is required for linux builds, so always enable it even when we
# disable all other tests
diff --git a/core/tests/unit/utftest/meson.build b/core/tests/unit/utftest/meson.build
index eff79c10b7..4ab6cb74ca 100644
--- a/core/tests/unit/utftest/meson.build
+++ b/core/tests/unit/utftest/meson.build
@@ -5,6 +5,6 @@
#
e = executable('utftest', 'utftest.cpp',
- objects: lib.extract_objects('utfcodec.cpp'),
+ objects: lib.extract_objects('../../common/cpp/utfcodec.cpp'),
include_directories: [libsrc])
test('utftest', e)
diff --git a/core/wasm.build.linux.in b/core/wasm.build.linux.in
index d449dcf94a..27cb7ba68f 100644
--- a/core/wasm.build.linux.in
+++ b/core/wasm.build.linux.in
@@ -1,4 +1,4 @@
[binaries]
-c = ['$EMSCRIPTEN_BASE/emcc.py']
-cpp = ['$EMSCRIPTEN_BASE/em++.py']
-ar = ['$EMSCRIPTEN_BASE/emar.py']
\ No newline at end of file
+c = ['$EMSCRIPTEN_BASE/emcc']
+cpp = ['$EMSCRIPTEN_BASE/em++']
+ar = ['$EMSCRIPTEN_BASE/emar']
diff --git a/core/wasm.build.mac.in b/core/wasm.build.mac.in
index d449dcf94a..27cb7ba68f 100644
--- a/core/wasm.build.mac.in
+++ b/core/wasm.build.mac.in
@@ -1,4 +1,4 @@
[binaries]
-c = ['$EMSCRIPTEN_BASE/emcc.py']
-cpp = ['$EMSCRIPTEN_BASE/em++.py']
-ar = ['$EMSCRIPTEN_BASE/emar.py']
\ No newline at end of file
+c = ['$EMSCRIPTEN_BASE/emcc']
+cpp = ['$EMSCRIPTEN_BASE/em++']
+ar = ['$EMSCRIPTEN_BASE/emar']
diff --git a/core/wasm.defs.build b/core/wasm.defs.build
index 4a598a1e1c..f41af337a3 100644
--- a/core/wasm.defs.build
+++ b/core/wasm.defs.build
@@ -1,7 +1,7 @@
[binaries]
-c = ['emcc.py']
-cpp = ['em++.py']
-ar = ['emar.py']
+c = ['emcc']
+cpp = ['em++']
+ar = ['emar']
exe_wrapper = 'node'
[properties]
diff --git a/crowdin.yml b/crowdin.yml
index 3c95e40186..812cfa21c7 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -127,6 +127,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/en.lproj/preferences.strings
dest: /mac/app/preferences.strings
@@ -134,6 +135,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /mac/Keyman4MacIM/Keyman4MacIM/KMInfoWindow/en.lproj/KMInfoWindowController.strings
dest: /mac/app/KMInfoWindowController.strings
@@ -141,6 +143,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/en.lproj/KMKeyboardHelpWindowController.strings
dest: /mac/app/KMKeyboardHelpWindowController.strings
@@ -148,6 +151,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings
dest: /mac/app/Localizable.strings
@@ -155,6 +159,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /mac/Keyman4MacIM/Keyman4MacIM/en.lproj/MainMenu.strings
dest: /mac/app/MainMenu.strings
@@ -162,6 +167,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
# crowdin parameters descriptions:
diff --git a/developer/src/.build-builder b/developer/src/.build-builder
new file mode 100644
index 0000000000..a06440b36f
--- /dev/null
+++ b/developer/src/.build-builder
@@ -0,0 +1 @@
+The presence of this file tells CI that we can build Developer on mac and linux, as of 18.0. We will be able to remove this file in 19.0.
diff --git a/developer/src/build.sh b/developer/src/build.sh
index de7ab49171..b45dea38f5 100755
--- a/developer/src/build.sh
+++ b/developer/src/build.sh
@@ -42,11 +42,29 @@ builder_describe \
"--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \
"--dry-run,-n+ Don't actually publish anything to external endpoints, just dry run"
+builder_describe_platform \
+ :ext win,delphi \
+ :kmanalyze win \
+ :kmdbrowserhost win,delphi \
+ :kmdecomp win \
+ :kmconvert win,delphi \
+ :samples win \
+ :server win \
+ :setup win,delphi \
+ :test win,delphi \
+ :tike win,delphi \
+ :inst win,delphi
+
+# TODO: in future :server could be built on other platforms, potentially, but it
+# has addons that are Windows-specific currently
+
builder_parse "$@"
#-------------------------------------------------------------------------------------------------------------------
-source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh"
+if [[ $BUILDER_OS == win ]]; then
+ source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh"
+fi
#
# We want to do some checks before we head down the long publish path
diff --git a/developer/src/common/build.sh b/developer/src/common/build.sh
index 53358c80b0..b37c2bc948 100755
--- a/developer/src/common/build.sh
+++ b/developer/src/common/build.sh
@@ -11,5 +11,8 @@ builder_describe \
":delphi Delphi components" \
":web Web components"
+builder_describe_platform \
+ :delphi win,delphi
+
builder_parse "$@"
builder_run_child_actions clean configure build test
diff --git a/developer/src/common/web/test-helpers/build.sh b/developer/src/common/web/test-helpers/build.sh
index 175bce6616..c1664cfddd 100755
--- a/developer/src/common/web/test-helpers/build.sh
+++ b/developer/src/common/web/test-helpers/build.sh
@@ -8,7 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
builder_describe "Keyman Developer unit test helpers" \
- "@/common/web/types" \
+ "@/developer/src/common/web/utils" \
clean configure build test
builder_describe_outputs \
diff --git a/developer/src/common/web/test-helpers/index.ts b/developer/src/common/web/test-helpers/index.ts
index be9157f69c..93dfb89b2c 100644
--- a/developer/src/common/web/test-helpers/index.ts
+++ b/developer/src/common/web/test-helpers/index.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
-import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerError } from '@keymanapp/common-types';
+import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerError } from '@keymanapp/developer-utils';
export { verifyCompilerMessagesObject } from './verifyCompilerMessagesObject.js';
/**
diff --git a/developer/src/common/web/test-helpers/package.json b/developer/src/common/web/test-helpers/package.json
index 5aa6ec301f..d541894830 100644
--- a/developer/src/common/web/test-helpers/package.json
+++ b/developer/src/common/web/test-helpers/package.json
@@ -17,6 +17,6 @@
},
"license": "MIT",
"dependencies": {
- "@keymanapp/common-types": "*"
+ "@keymanapp/developer-utils": "*"
}
}
diff --git a/developer/src/common/web/test-helpers/verifyCompilerMessagesObject.ts b/developer/src/common/web/test-helpers/verifyCompilerMessagesObject.ts
index e9ed7b45a7..7f0d9aee76 100644
--- a/developer/src/common/web/test-helpers/verifyCompilerMessagesObject.ts
+++ b/developer/src/common/web/test-helpers/verifyCompilerMessagesObject.ts
@@ -1,4 +1,4 @@
-import { CompilerError, CompilerErrorMask } from '@keymanapp/common-types';
+import { CompilerError, CompilerErrorMask } from '@keymanapp/developer-utils';
import {assert, expect} from 'chai';
//
diff --git a/developer/src/common/web/utils/build.sh b/developer/src/common/web/utils/build.sh
index e58d585c5d..8829765237 100755
--- a/developer/src/common/web/utils/build.sh
+++ b/developer/src/common/web/utils/build.sh
@@ -21,20 +21,42 @@ builder_describe "Build Keyman Developer web utility module" \
builder_describe_outputs \
configure /node_modules \
- build /developer/src/common/web/utils/build/index.js
+ build /developer/src/common/web/utils/build/src/index.js
builder_parse "$@"
#-------------------------------------------------------------------------------------------------------------------
+function copy_cldr_imports() {
+ # Store CLDR imports
+ # load all versions that have a cldr_info.json
+ for CLDR_INFO_PATH in "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/"*/cldr_info.json
+ do
+ # TODO-LDML: developer/src/inst/download.in.mak needs these also...
+ CLDR_PATH=$(dirname "$CLDR_INFO_PATH")
+ CLDR_VER=$(basename "$CLDR_PATH")
+ mkdir -p "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER"
+ # TODO-LDML: When these are copied, the DOCTYPE will break due to the wrong path. We don't use the DTD so it should be OK.
+ cp "$CLDR_INFO_PATH" "$CLDR_PATH/import/"*.xml "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER/"
+ done
+}
+
+function do_build() {
+ copy_cldr_imports
+ tsc --build
+}
+
builder_run_action clean rm -rf ./build/
builder_run_action configure verify_npm_setup
-builder_run_action build tsc --build
+builder_run_action build do_build
if builder_start_action test; then
eslint .
tsc --build test
- c8 --reporter=lcov --reporter=text --exclude-after-remap mocha
+ 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
diff --git a/developer/src/common/web/utils/index.ts b/developer/src/common/web/utils/index.ts
deleted file mode 100644
index 7a792af2a1..0000000000
--- a/developer/src/common/web/utils/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export { validateMITLicense } from './src/validate-mit-license.js';
-export { KeymanSentry, SentryNodeOptions } from './src/KeymanSentry.js';
-export { getOption, loadOptions, clearOptions } from './src/options.js';
-export { escapeMarkdownChar } from './src/markdown.js';
-export { KeymanUrls } from './src/keyman-urls.js';
-export { isValidEmail } from './src/is-valid-email.js';
\ No newline at end of file
diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json
index b93cca8efa..1ab9daddbf 100644
--- a/developer/src/common/web/utils/package.json
+++ b/developer/src/common/web/utils/package.json
@@ -3,19 +3,29 @@
"description": "Keyman Developer utilities",
"type": "module",
"exports": {
- ".": "./build/index.js"
+ ".": "./build/src/index.js"
},
"files": [
"/build/"
],
"dependencies": {
- "@sentry/node": "^7.57.0"
+ "@sentry/node": "^7.57.0",
+ "@keymanapp/common-types": "*",
+ "eventemitter3": "^5.0.0",
+ "restructure": "^3.0.1",
+ "semver": "^7.5.4",
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
},
"devDependencies": {
+ "@types/git-diff": "^2.0.3",
"@types/node": "^20.4.1",
- "typescript": "^5.4.5",
+ "@types/semver": "^7.3.12",
+ "@types/xml2js": "^0.4.5",
"c8": "^7.12.0",
- "mocha": "^8.4.0"
+ "git-diff": "^2.0.6",
+ "mocha": "^8.4.0",
+ "typescript": "^5.4.5"
},
"scripts": {
"build": "tsc -b"
diff --git a/common/web/types/src/util/common-events.ts b/developer/src/common/web/utils/src/common-events.ts
similarity index 100%
rename from common/web/types/src/util/common-events.ts
rename to developer/src/common/web/utils/src/common-events.ts
diff --git a/common/web/types/src/util/compiler-interfaces.ts b/developer/src/common/web/utils/src/compiler-interfaces.ts
similarity index 99%
rename from common/web/types/src/util/compiler-interfaces.ts
rename to developer/src/common/web/utils/src/compiler-interfaces.ts
index df8ab48f33..e4ed62d750 100644
--- a/common/web/types/src/util/compiler-interfaces.ts
+++ b/developer/src/common/web/utils/src/compiler-interfaces.ts
@@ -181,7 +181,7 @@ export class CompilerError {
*/
static severityNameToValue(name: string): CompilerErrorSeverity {
name = name.toLowerCase();
- for(let level of CompilerErrorSeverityValues) {
+ for(const level of CompilerErrorSeverityValues) {
if(errorSeverityName[level].startsWith(name)) {
return level;
}
diff --git a/common/web/types/src/deps/xml2js/LICENSE b/developer/src/common/web/utils/src/deps/xml2js/LICENSE
similarity index 100%
rename from common/web/types/src/deps/xml2js/LICENSE
rename to developer/src/common/web/utils/src/deps/xml2js/LICENSE
diff --git a/common/web/types/src/deps/xml2js/README.md b/developer/src/common/web/utils/src/deps/xml2js/README.md
similarity index 100%
rename from common/web/types/src/deps/xml2js/README.md
rename to developer/src/common/web/utils/src/deps/xml2js/README.md
diff --git a/common/web/types/src/deps/xml2js/bom.js b/developer/src/common/web/utils/src/deps/xml2js/bom.js
similarity index 100%
rename from common/web/types/src/deps/xml2js/bom.js
rename to developer/src/common/web/utils/src/deps/xml2js/bom.js
diff --git a/common/web/types/src/deps/xml2js/builder.js b/developer/src/common/web/utils/src/deps/xml2js/builder.js
similarity index 100%
rename from common/web/types/src/deps/xml2js/builder.js
rename to developer/src/common/web/utils/src/deps/xml2js/builder.js
diff --git a/common/web/types/src/deps/xml2js/defaults.js b/developer/src/common/web/utils/src/deps/xml2js/defaults.js
similarity index 100%
rename from common/web/types/src/deps/xml2js/defaults.js
rename to developer/src/common/web/utils/src/deps/xml2js/defaults.js
diff --git a/common/web/types/src/deps/xml2js/parser.js b/developer/src/common/web/utils/src/deps/xml2js/parser.js
similarity index 100%
rename from common/web/types/src/deps/xml2js/parser.js
rename to developer/src/common/web/utils/src/deps/xml2js/parser.js
diff --git a/common/web/types/src/deps/xml2js/processors.js b/developer/src/common/web/utils/src/deps/xml2js/processors.js
similarity index 100%
rename from common/web/types/src/deps/xml2js/processors.js
rename to developer/src/common/web/utils/src/deps/xml2js/processors.js
diff --git a/common/web/types/src/deps/xml2js/xml2js.js b/developer/src/common/web/utils/src/deps/xml2js/xml2js.js
similarity index 100%
rename from common/web/types/src/deps/xml2js/xml2js.js
rename to developer/src/common/web/utils/src/deps/xml2js/xml2js.js
diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts
new file mode 100644
index 0000000000..0e462c7c05
--- /dev/null
+++ b/developer/src/common/web/utils/src/index.ts
@@ -0,0 +1,47 @@
+export { validateMITLicense } from './utils/validate-mit-license.js';
+export { KeymanSentry, SentryNodeOptions } from './utils/KeymanSentry.js';
+export { getOption, loadOptions, clearOptions } from './utils/options.js';
+export { escapeMarkdownChar } from './utils/markdown.js';
+export { KeymanUrls } from './utils/keyman-urls.js';
+
+export * as KPJ from './types/kpj/kpj-file.js';
+export { KPJFileReader } from './types/kpj/kpj-file-reader.js';
+export { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, } from './types/kpj/keyman-developer-project.js';
+export { isValidEmail } from './is-valid-email.js';
+
+export * as KpsFile from './types/kps/kps-file.js';
+
+export { default as KvksFileReader } from './types/kvks/kvks-file-reader.js';
+export { default as KvksFileWriter } from './types/kvks/kvks-file-writer.js';
+export * as KvksFile from './types/kvks/kvks-file.js';
+
+export { TouchLayoutFileReader } from './types/keyman-touch-layout/keyman-touch-layout-file-reader.js';
+export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './types/keyman-touch-layout/keyman-touch-layout-file-writer.js';
+
+export { default as KMXBuilder } from './types/kmx/kmx-builder.js';
+export { default as KMXPlusBuilder} from './types/kmx/kmx-plus-builder/kmx-plus-builder.js';
+
+export * as LDMLKeyboard from './types/ldml-keyboard/ldml-keyboard-xml.js';
+export { LDMLKeyboardTestDataXMLSourceFile } from './types/ldml-keyboard/ldml-keyboard-testdata-xml.js';
+export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from './types/ldml-keyboard/ldml-keyboard-xml-reader.js';
+
+export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, CompilerOptions, CompilerEvent, CompilerErrorNamespace,
+ CompilerErrorSeverity, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerCallbackOptions,
+ CompilerError, CompilerMessageSpec, CompilerMessageSpecWithException, compilerErrorSeverity, CompilerErrorMask, CompilerFileCallbacks, compilerErrorSeverityName,
+ compilerErrorFormatCode, CompilerMessageDef,
+ compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS,
+ ALL_COMPILER_LOG_FORMATS, CompilerLogFormat,
+ CompilerMessageOverride,
+ CompilerMessageOverrideMap,
+
+ KeymanCompilerArtifact,
+ KeymanCompilerArtifactOptional,
+ KeymanCompilerArtifacts,
+ KeymanCompilerResult,
+ KeymanCompiler
+
+ } from './compiler-interfaces.js';
+
+export { CommonTypesMessages } from './common-events.js';
+
+export * as xml2js from './deps/xml2js/xml2js.js';
diff --git a/developer/src/common/web/utils/src/restructure.d.ts b/developer/src/common/web/utils/src/restructure.d.ts
new file mode 100644
index 0000000000..9750d24473
--- /dev/null
+++ b/developer/src/common/web/utils/src/restructure.d.ts
@@ -0,0 +1,2 @@
+// TODO: consider defining more types?
+declare module 'restructure';
\ No newline at end of file
diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts b/developer/src/common/web/utils/src/types/keyman-touch-layout/keyman-touch-layout-file-reader.ts
similarity index 82%
rename from common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts
rename to developer/src/common/web/utils/src/types/keyman-touch-layout/keyman-touch-layout-file-reader.ts
index e0f4ea7917..81be30e387 100644
--- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts
+++ b/developer/src/common/web/utils/src/types/keyman-touch-layout/keyman-touch-layout-file-reader.ts
@@ -1,5 +1,6 @@
-import { TouchLayoutFile } from "./keyman-touch-layout-file.js";
-import SchemaValidators from '../schema-validators.js';
+import { TouchLayout } from "@keymanapp/common-types";
+import TouchLayoutFile = TouchLayout.TouchLayoutFile;
+import { SchemaValidators } from '@keymanapp/common-types';
export class TouchLayoutFileReader {
public read(source: Uint8Array): TouchLayoutFile {
@@ -22,7 +23,7 @@ export class TouchLayoutFileReader {
// `row.id` should be number, but may have been stringified; we use
// presence of `key` property to recognise this as a `TouchLayoutRow`.
if(this.key && key == 'id' && typeof value == 'string') {
- let newValue = parseInt(value, 10);
+ const newValue = parseInt(value, 10);
/* c8 ignore next 3 */
if(isNaN(newValue)) {
throw new TypeError(`Invalid row.id: "${value}"`);
@@ -39,7 +40,7 @@ export class TouchLayoutFileReader {
return undefined;
}
- let newValue = parseInt(value, 10);
+ const newValue = parseInt(value, 10);
/* c8 ignore next 3 */
if(isNaN(newValue)) {
throw new TypeError(`Invalid [sub]key.${key}: "${value}"`);
@@ -67,10 +68,10 @@ export class TouchLayoutFileReader {
}
public validate(source: TouchLayoutFile): void {
- if(!SchemaValidators.touchLayoutClean(source))
+ if(!SchemaValidators.default.touchLayoutClean(source))
/* c8 ignore next 3 */
{
- throw new Error(JSON.stringify((SchemaValidators.touchLayoutClean).errors));
+ throw new Error(JSON.stringify((SchemaValidators.default.touchLayoutClean).errors));
}
}
diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-writer.ts b/developer/src/common/web/utils/src/types/keyman-touch-layout/keyman-touch-layout-file-writer.ts
similarity index 86%
rename from common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-writer.ts
rename to developer/src/common/web/utils/src/types/keyman-touch-layout/keyman-touch-layout-file-writer.ts
index 5de702eb1c..6261f80d4b 100644
--- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-writer.ts
+++ b/developer/src/common/web/utils/src/types/keyman-touch-layout/keyman-touch-layout-file-writer.ts
@@ -1,4 +1,8 @@
-import { TouchLayoutFile, TouchLayoutPlatform, TouchLayoutKey, TouchLayoutSubKey } from "./keyman-touch-layout-file.js";
+import { TouchLayout } from "@keymanapp/common-types";
+import TouchLayoutFile = TouchLayout.TouchLayoutFile;
+import TouchLayoutPlatform = TouchLayout.TouchLayoutPlatform;;
+import TouchLayoutKey = TouchLayout.TouchLayoutKey;
+import TouchLayoutSubKey = TouchLayout.TouchLayoutSubKey;
export interface TouchLayoutFileWriterOptions {
formatted?: boolean;
@@ -87,24 +91,24 @@ export class TouchLayoutFileWriter {
// displayUnderlying is always written out by kmcomp, so we do the same for kmc:
platform.displayUnderlying = !!platform.displayUnderlying;
- for(let layer of platform.layer) {
- for(let row of layer.row) {
+ for(const layer of platform.layer) {
+ for(const row of layer.row) {
// this matches the old spec for touch layout files
(row.id as any) = row.id.toString();
- for(let key of row.key) {
+ for(const key of row.key) {
fixupKey(key);
if(key.sk) {
- for(let sk of key.sk) {
+ for(const sk of key.sk) {
fixupKey(sk);
}
}
if(key.multitap) {
- for(let sk of key.multitap) {
+ for(const sk of key.multitap) {
fixupKey(sk);
}
}
if(key.flick) {
- for(let id of Object.keys(key.flick)) {
+ for(const id of Object.keys(key.flick)) {
fixupKey((key.flick as any)[id] as TouchLayoutSubKey);
}
}
diff --git a/common/web/types/src/kmx/kmx-builder.ts b/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts
similarity index 85%
rename from common/web/types/src/kmx/kmx-builder.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-builder.ts
index 4a319bfe45..a8ce169d30 100644
--- a/common/web/types/src/kmx/kmx-builder.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-builder.ts
@@ -1,8 +1,18 @@
+import { KMXPlus, KMX } from '@keymanapp/common-types';
import * as r from 'restructure';
-import { KMXFile, GROUP, KEY, STORE, BUILDER_COMP_KEYBOARD, BUILDER_COMP_KEYBOARD_KMXPLUSINFO, BUILDER_COMP_STORE, BUILDER_COMP_GROUP, BUILDER_COMP_KEY } from './kmx.js';
-import { KMXPlusFile } from './kmx-plus.js';
import KMXPlusBuilder from './kmx-plus-builder/kmx-plus-builder.js';
+import KMXPlusFile = KMXPlus.KMXPlusFile;
+import KMXFile = KMX.KMXFile;
+import GROUP = KMX.GROUP;
+import KEY = KMX.KEY;
+import STORE = KMX.STORE;
+import BUILDER_COMP_KEYBOARD = KMX.BUILDER_COMP_KEYBOARD;
+import BUILDER_COMP_KEYBOARD_KMXPLUSINFO = KMX.BUILDER_COMP_KEYBOARD_KMXPLUSINFO;
+import BUILDER_COMP_STORE = KMX.BUILDER_COMP_STORE;
+import BUILDER_COMP_GROUP = KMX.BUILDER_COMP_GROUP;
+import BUILDER_COMP_KEY = KMX.BUILDER_COMP_KEY;
+
export default class KMXBuilder {
file: KMXFile;
base_keyboard: number = 0;
@@ -69,8 +79,8 @@ export default class KMXBuilder {
this.comp_header.dpStoreArray = this.comp_header.cxStoreArray ? size : 0;
let storeBase = size;
size += this.file.keyboard.stores.length * KMXFile.COMP_STORE_SIZE;
- for(let store of this.file.keyboard.stores) {
- let comp_store: BUILDER_COMP_STORE = {
+ for(const store of this.file.keyboard.stores) {
+ const comp_store: BUILDER_COMP_STORE = {
dwSystemID: store.dwSystemID,
dpName: 0,
dpString: 0
@@ -90,8 +100,8 @@ export default class KMXBuilder {
this.comp_header.dpGroupArray = this.comp_header.cxGroupArray ? size : 0;
let groupBase = size;
size += this.file.keyboard.groups.length * KMXFile.COMP_GROUP_SIZE;
- for(let group of this.file.keyboard.groups) {
- let comp_group: BUILDER_COMP_GROUP = {
+ for(const group of this.file.keyboard.groups) {
+ const comp_group: BUILDER_COMP_GROUP = {
dpName: 0,
dpKeyArray: 0,
dpMatch: 0,
@@ -100,7 +110,7 @@ export default class KMXBuilder {
fUsingKeys: group.fUsingKeys ? 1 : 0
};
- let comp_keys: {base: number, key: KEY, obj: BUILDER_COMP_KEY}[] = [];
+ const comp_keys: {base: number, key: KEY, obj: BUILDER_COMP_KEY}[] = [];
this.comp_groups.push({base: groupBase, group: group, obj: comp_group, keys: comp_keys});
@@ -116,8 +126,8 @@ export default class KMXBuilder {
let keyBase = size;
size += group.keys.length * KMXFile.COMP_KEY_SIZE;
- for(let key of group.keys) {
- let comp_key: BUILDER_COMP_KEY = {
+ for(const key of group.keys) {
+ const comp_key: BUILDER_COMP_KEY = {
Key: key.Key,
_padding: 0,
Line: key.Line,
@@ -164,11 +174,11 @@ export default class KMXBuilder {
if(requireString && !str.length) {
// Just write zero terminator, as r.String for a zero-length string
// seems to fail.
- let sbuf = r.uint16le;
+ const sbuf = r.uint16le;
file.set(sbuf.toBuffer(0), pos);
}
else if(pos && str.length) {
- let sbuf = new r.String(null, 'utf16le'); // null-terminated string
+ const sbuf = new r.String(null, 'utf16le'); // null-terminated string
file.set(sbuf.toBuffer(str), pos);
}
}
@@ -176,7 +186,7 @@ export default class KMXBuilder {
compile(): Uint8Array {
const fileSize = this.build();
- let file: Uint8Array = new Uint8Array(fileSize);
+ const file: Uint8Array = new Uint8Array(fileSize);
// Write headers
@@ -190,7 +200,7 @@ export default class KMXBuilder {
// Write store array and data
- for(let store of this.comp_stores) {
+ for(const store of this.comp_stores) {
file.set(this.file.COMP_STORE.toBuffer(store.obj), store.base);
if(this.writeDebug) {
this.setString(file, store.obj.dpName, store.store.dpName);
@@ -200,7 +210,7 @@ export default class KMXBuilder {
// Write group array and data
- for(let group of this.comp_groups) {
+ for(const group of this.comp_groups) {
file.set(this.file.COMP_GROUP.toBuffer(group.obj), group.base);
if(this.writeDebug) {
this.setString(file, group.obj.dpName, group.group.dpName);
@@ -208,7 +218,7 @@ export default class KMXBuilder {
this.setString(file, group.obj.dpMatch, group.group.dpMatch);
this.setString(file, group.obj.dpNoMatch, group.group.dpNoMatch);
- for(let key of group.keys) {
+ for(const key of group.keys) {
file.set(this.file.COMP_KEY.toBuffer(key.obj), key.base);
// for back-compat reasons, these are never NULL strings
this.setString(file, key.obj.dpContext, key.key.dpContext, true);
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-disp.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts
similarity index 88%
rename from common/web/types/src/kmx/kmx-plus-builder/build-disp.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts
index 712dafac27..6984aa2cfa 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-disp.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-disp.ts
@@ -1,8 +1,10 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
-import { KMXPlusData } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_SECTION } from './builder-section.js';
+import KMXPlusData = KMXPlus.KMXPlusData;
+
/* ------------------------------------------------------------------
* disp section
------------------------------------------------------------------ */
@@ -27,7 +29,7 @@ export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
return null;
}
- let disp: BUILDER_DISP = {
+ const disp: BUILDER_DISP = {
ident: constants.hex_section_id(constants.section.disp),
size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length,
_offset: 0,
@@ -36,7 +38,7 @@ export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
items: []
};
- for(let item of kmxplus.disp.disps) {
+ for(const item of kmxplus.disp.disps) {
disp.items.push({
to: build_strs_index(sect_strs, item.to),
id: build_strs_index(sect_strs, item.id),
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-elem.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts
similarity index 94%
rename from common/web/types/src/kmx/kmx-plus-builder/build-elem.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts
index 8e325f9591..8dafccced2 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-elem.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-elem.ts
@@ -1,10 +1,11 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { ElementString } from "../element-string.js";
-import { Elem } from "../kmx-plus.js";
+import { KMXPlus, ElementString } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_SECTION, BUILDER_U32CHAR } from "./builder-section.js";
import { build_uset_index, BUILDER_USET, BUILDER_USET_REF } from "./build-uset.js";
+import Elem = KMXPlus.Elem;
+
/* ------------------------------------------------------------------
* elem section
------------------------------------------------------------------ */
@@ -46,7 +47,7 @@ function binaryElemCompare(a: BUILDER_ELEM_STRING, b: BUILDER_ELEM_STRING): numb
}
export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset: BUILDER_USET): BUILDER_ELEM {
- let result: BUILDER_ELEM = {
+ const result: BUILDER_ELEM = {
ident: constants.hex_section_id(constants.section.elem),
size: 0, // finalized below
_offset: 0,
@@ -55,7 +56,7 @@ export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset
};
result.strings = source_elem.strings.map(item => {
- let res: BUILDER_ELEM_STRING = {
+ const res: BUILDER_ELEM_STRING = {
offset: 0, // finalized below
length: item.length,
items: [],
@@ -87,7 +88,7 @@ export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset
/* Calculate offsets and total size */
let offset = constants.length_elem + constants.length_elem_item * result.count;
- for(let item of result.strings) {
+ for(const item of result.strings) {
if (item.length === 0) {
// no length gets a zero offset
item.offset = 0;
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-keys.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-keys.ts
similarity index 93%
rename from common/web/types/src/kmx/kmx-plus-builder/build-keys.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-keys.ts
index ec7e505c57..fd48ec49ec 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-keys.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-keys.ts
@@ -1,10 +1,14 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { KeysFlick, KMXPlusData, StrsItem } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { build_list_index, BUILDER_LIST, BUILDER_LIST_REF } from "./build-list.js";
import { BUILDER_SECTION, BUILDER_U32CHAR } from "./builder-section.js";
+import KeysFlick = KMXPlus.KeysFlick;
+import KMXPlusData = KMXPlus.KMXPlusData;
+import StrsItem = KMXPlus.StrsItem;
+
/* ------------------------------------------------------------------
* keys section
------------------------------------------------------------------ */
@@ -71,7 +75,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
return null;
}
- let keys: BUILDER_KEYS = {
+ const keys: BUILDER_KEYS = {
ident: constants.hex_section_id(constants.section.keys),
size: 0,
keyCount: kmxplus.keys.keys.length,
@@ -89,7 +93,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
// Note that per the Keys class and spec, there is always a flicks=0 meaning 'no flicks'
keys.flicks = kmxplus.keys.flicks.map((flicks) => {
- let result : BUILDER_KEYS_FLICKS = {
+ const result : BUILDER_KEYS_FLICKS = {
count: flicks.flicks.length,
flick: keys.flick.length, // index of first flick
id: build_strs_index(sect_strs, flicks.id),
@@ -113,7 +117,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
// now, keys
keys.keys = kmxplus.keys.keys.map((key) => {
- let result : BUILDER_KEYS_KEY = {
+ const result : BUILDER_KEYS_KEY = {
to: build_strs_index(sect_strs, key.to),
flags: key.flags,
id: build_strs_index(sect_strs, key.id),
@@ -136,7 +140,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
// finally, kmap
keys.kmap = kmxplus.keys.kmap.map(({vkey, mod, key}) => {
- let result : BUILDER_KEYS_KMAP = {
+ const result : BUILDER_KEYS_KMAP = {
vkey,
mod,
key: keys.keys.findIndex(k => k._id === key),
@@ -160,7 +164,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
return rc;
});
- let offset = constants.length_keys +
+ const offset = constants.length_keys +
(constants.length_keys_key * keys.keyCount) +
(constants.length_keys_flick_element * keys.flickCount) +
(constants.length_keys_flick_list * keys.flicksCount) +
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-layr.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts
similarity index 94%
rename from common/web/types/src/kmx/kmx-plus-builder/build-layr.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts
index c0038246e2..07249368f4 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-layr.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-layr.ts
@@ -1,10 +1,15 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { KMXPlusData, LayrEntry, LayrRow, StrsItem } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_LIST } from "./build-list.js";
import { BUILDER_SECTION } from "./builder-section.js";
+import KMXPlusData = KMXPlus.KMXPlusData;
+import LayrEntry = KMXPlus.LayrEntry;
+import LayrRow = KMXPlus.LayrRow;
+import StrsItem = KMXPlus.StrsItem;
+
/* ------------------------------------------------------------------
* layr section -
------------------------------------------------------------------ */
@@ -66,7 +71,7 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
return null; // if there aren't any layers at all (which should be an invalid keyboard)
}
- let layr: BUILDER_LAYR = {
+ const layr: BUILDER_LAYR = {
ident: constants.hex_section_id(constants.section.layr),
size: constants.length_layr,
_offset: 0,
@@ -145,7 +150,7 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
layr.rowCount = layr.rows.length;
layr.keyCount = layr.keys.length;
- let offset = constants.length_layr +
+ const offset = constants.length_layr +
(constants.length_layr_list * layr.listCount) +
(constants.length_layr_entry * layr.layerCount) +
(constants.length_layr_row * layr.rowCount) +
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-list.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-list.ts
similarity index 88%
rename from common/web/types/src/kmx/kmx-plus-builder/build-list.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-list.ts
index 26659f8eb6..759f6498f4 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-list.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-list.ts
@@ -1,8 +1,11 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { List, ListItem } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_SECTION } from "./builder-section.js";
+import List = KMXPlus.List;
+import ListItem = KMXPlus.ListItem;
+
/* ------------------------------------------------------------------
* list section
------------------------------------------------------------------ */
@@ -40,7 +43,7 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_
return null;
}
- let result: BUILDER_LIST = {
+ const result: BUILDER_LIST = {
ident: constants.hex_section_id(constants.section.list),
size: 0,
_offset: 0,
@@ -51,13 +54,13 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_
};
result.lists = source_list.lists.map(array => {
- let list : BUILDER_LIST_LIST = {
+ const list : BUILDER_LIST_LIST = {
index: result.indices.length, // the next indexcount
count: array.length,
_value: array
};
array.forEach((i) => {
- let index : BUILDER_LIST_INDEX = {
+ const index : BUILDER_LIST_INDEX = {
// Get the final string index
str: build_strs_index(sect_strs, i.value),
_value: i.value.value, // unwrap the actual string value
@@ -71,7 +74,7 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_
// Sort the lists.
result.lists.sort((a,b) => a._value.compareTo(b._value));
- let offset = constants.length_list +
+ const offset = constants.length_list +
(constants.length_list_item * result.listCount) +
(constants.length_list_index * result.indexCount);
result.size = offset;
@@ -86,14 +89,14 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_
* @returns
*/
export function build_list_index(sect_list: BUILDER_LIST, value: ListItem) : BUILDER_LIST_REF {
- if (!value) {
+ if (!value) {
return 0; // empty list
}
if(!(value instanceof ListItem)) {
throw new Error('unexpected value '+ value);
}
- let result = sect_list.lists.findIndex(v => v._value === value);
+ const result = sect_list.lists.findIndex(v => v._value === value);
if(result < 0) {
throw new Error('unexpectedly missing ListItem ' + value); // TODO-LDML: it's an array of strs
}
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-loca.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-loca.ts
similarity index 84%
rename from common/web/types/src/kmx/kmx-plus-builder/build-loca.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-loca.ts
index 9a72402c8c..4a81fcd9ab 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-loca.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-loca.ts
@@ -4,10 +4,12 @@
------------------------------------------------------------------ */
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { KMXPlusData } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_SECTION } from "./builder-section.js";
+import KMXPlusData = KMXPlus.KMXPlusData;
+
/**
* Builder for the 'loca' section
*/
@@ -17,7 +19,7 @@ export interface BUILDER_LOCA extends BUILDER_SECTION {
};
export function build_loca(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_LOCA {
- let loca: BUILDER_LOCA = {
+ const loca: BUILDER_LOCA = {
ident: constants.hex_section_id(constants.section.loca),
size: constants.length_loca + constants.length_loca_item * kmxplus.loca.locales.length,
_offset: 0,
@@ -25,7 +27,7 @@ export function build_loca(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
items: []
};
- for(let item of kmxplus.loca.locales) {
+ for(const item of kmxplus.loca.locales) {
loca.items.push(build_strs_index(sect_strs, item));
}
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-meta.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-meta.ts
similarity index 93%
rename from common/web/types/src/kmx/kmx-plus-builder/build-meta.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-meta.ts
index 23dd303a7d..208051356e 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-meta.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-meta.ts
@@ -4,10 +4,12 @@
------------------------------------------------------------------ */
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { KMXPlusData } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_SECTION } from "./builder-section.js";
+import KMXPlusData = KMXPlus.KMXPlusData;
+
/**
* Builder for the 'meta' section
*/
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-sect.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts
similarity index 100%
rename from common/web/types/src/kmx/kmx-plus-builder/build-sect.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-sect.ts
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-strs.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts
similarity index 89%
rename from common/web/types/src/kmx/kmx-plus-builder/build-strs.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts
index 503de2d404..89d733f22d 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-strs.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-strs.ts
@@ -1,7 +1,10 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { Strs, StrsItem } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { BUILDER_SECTION } from "./builder-section.js";
+import Strs = KMXPlus.Strs;
+import StrsItem = KMXPlus.StrsItem;
+
/** reference from build_strs_index */
export type BUILDER_STR_REF = number;
@@ -26,7 +29,7 @@ export interface BUILDER_STRS extends BUILDER_SECTION {
};
export function build_strs(source_strs: Strs): BUILDER_STRS {
- let result: BUILDER_STRS = {
+ const result: BUILDER_STRS = {
ident: constants.hex_section_id(constants.section.strs),
size: 0, // finalized later
_offset: 0,
@@ -39,7 +42,7 @@ export function build_strs(source_strs: Strs): BUILDER_STRS {
let offset = constants.length_strs + constants.length_strs_item * result.count;
// TODO: consider padding
- for(let item of result.items) {
+ for(const item of result.items) {
item.offset = offset;
offset += item.length * 2 + 2; /* UTF-16 code units + sizeof null terminator */
}
@@ -64,7 +67,7 @@ export function build_strs_index(sect_strs: BUILDER_STRS, value: StrsItem) : BUI
return value.char;
}
- let result = sect_strs.items.findIndex(v => v._value === value.value);
+ const result = sect_strs.items.findIndex(v => v._value === value.value);
if(result < 0) {
throw new Error('unexpectedly missing StrsItem '+value.value);
}
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-tran.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-tran.ts
similarity index 91%
rename from common/web/types/src/kmx/kmx-plus-builder/build-tran.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-tran.ts
index 316df69853..2473651b88 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-tran.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-tran.ts
@@ -1,10 +1,13 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { Bksp, Tran } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { BUILDER_ELEM, BUILDER_ELEM_REF, build_elem_index } from "./build-elem.js";
import { BUILDER_STRS, BUILDER_STR_REF, build_strs_index } from "./build-strs.js";
import { BUILDER_SECTION } from "./builder-section.js";
+import Bksp = KMXPlus.Bksp;
+import Tran = KMXPlus.Tran;
+
/* ------------------------------------------------------------------
* tran section
------------------------------------------------------------------ */
@@ -44,7 +47,7 @@ export function build_tran(source_tran: Tran | Bksp, sect_strs: BUILDER_STRS, se
return null;
}
- let tran: BUILDER_TRAN = {
+ const tran: BUILDER_TRAN = {
ident: constants.hex_section_id(source_tran.id),
size: 0, // need to compute total transforms + reorders
_offset: 0,
@@ -56,14 +59,14 @@ export function build_tran(source_tran: Tran | Bksp, sect_strs: BUILDER_STRS, se
reorders: [],
};
- for (let group of source_tran.groups) {
+ for (const group of source_tran.groups) {
if (group.type === constants.tran_group_type_transform) {
tran.groups.push({
type: group.type,
count: group.transforms.length,
index: tran.transforms.length, // index of first item
});
- for (let transform of group.transforms) {
+ for (const transform of group.transforms) {
tran.transforms.push({
from: build_strs_index(sect_strs, transform.from),
to: build_strs_index(sect_strs, transform.to),
@@ -77,7 +80,7 @@ export function build_tran(source_tran: Tran | Bksp, sect_strs: BUILDER_STRS, se
count: group.reorders.length,
index: tran.reorders.length, // index of first item
});
- for (let reorder of group.reorders) {
+ for (const reorder of group.reorders) {
tran.reorders.push({
elements: build_elem_index(sect_elem, reorder.elements),
before: build_elem_index(sect_elem, reorder.before),
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-uset.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-uset.ts
similarity index 89%
rename from common/web/types/src/kmx/kmx-plus-builder/build-uset.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-uset.ts
index 8c73afb796..1edd4d339f 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-uset.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-uset.ts
@@ -1,8 +1,12 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { KMXPlusData, StrsItem, UsetItem } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_SECTION, BUILDER_U32CHAR } from "./builder-section.js";
+import KMXPlusData = KMXPlus.KMXPlusData;
+import StrsItem = KMXPlus.StrsItem;
+import UsetItem = KMXPlus.UsetItem;
+
/** reference from build_uset_index */
export type BUILDER_USET_REF = number;
@@ -80,7 +84,7 @@ export function build_uset_index(sect_uset: BUILDER_USET, value: UsetItem) {
}
}
- let result = sect_uset.usets.findIndex(v => v._pattern.value === value.str.value);
+ const result = sect_uset.usets.findIndex(v => v._pattern.value === value.str.value);
if(result < 0) {
throw new Error('unexpectedly missing UsetItem ' + value.uset.toString());
}
diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-vars.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-vars.ts
similarity index 95%
rename from common/web/types/src/kmx/kmx-plus-builder/build-vars.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-vars.ts
index 8c09a169f6..92acbba9be 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/build-vars.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/build-vars.ts
@@ -1,10 +1,11 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { KMXPlusData } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { build_strs_index, BUILDER_STR_REF, BUILDER_STRS } from "./build-strs.js";
import { BUILDER_SECTION } from "./builder-section.js";
import { build_list_index, BUILDER_LIST, BUILDER_LIST_REF } from "./build-list.js";
import { build_elem_index, BUILDER_ELEM, BUILDER_ELEM_REF } from "./build-elem.js";
+import KMXPlusData = KMXPlus.KMXPlusData;
interface BUILDER_VARS_ITEM {
type: number;
diff --git a/common/web/types/src/kmx/kmx-plus-builder/builder-section.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts
similarity index 100%
rename from common/web/types/src/kmx/kmx-plus-builder/builder-section.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/builder-section.ts
diff --git a/common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts
similarity index 93%
rename from common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts
rename to developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts
index 381e1d6d19..acc732c932 100644
--- a/common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts
+++ b/developer/src/common/web/utils/src/types/kmx/kmx-plus-builder/kmx-plus-builder.ts
@@ -1,5 +1,5 @@
import * as r from 'restructure';
-import { KMXPlusFile } from "../kmx-plus.js";
+import { KMXPlus } from "@keymanapp/common-types";
import { constants, SectionIdent } from '@keymanapp/ldml-keyboard-constants';
import { BUILDER_SECTION } from './builder-section.js';
import { BUILDER_SECT, build_sect } from './build-sect.js';
@@ -15,6 +15,8 @@ import { BUILDER_TRAN, build_tran } from './build-tran.js';
import { BUILDER_USET, build_uset } from './build-uset.js';
import { BUILDER_VARS, build_vars } from './build-vars.js';
+import KMXPlusFile = KMXPlus.KMXPlusFile;
+
type BUILDER_BKSP = BUILDER_TRAN;
// type BUILDER_FINL = BUILDER_TRAN;
@@ -50,7 +52,7 @@ export default class KMXPlusBuilder {
public compile(): Uint8Array {
const fileSize = this.build();
- let file: Uint8Array = new Uint8Array(fileSize);
+ const file: Uint8Array = new Uint8Array(fileSize);
this.emitSection(file, this.file.COMP_PLUS_SECT, this.sect.sect);
// Keep the rest of these in order.
@@ -155,13 +157,13 @@ export default class KMXPlusBuilder {
}
private emitStrings(file: Uint8Array) {
- for(let item of this.sect.strs.items) {
+ for(const item of this.sect.strs.items) {
if(item._value === '') {
// We have a special case for the zero-length string
- let sbuf = r.uint16le;
+ const sbuf = r.uint16le;
file.set(sbuf.toBuffer(0), item.offset + this.sect.strs._offset);
} else {
- let sbuf = new r.String(null, 'utf16le');
+ const sbuf = new r.String(null, 'utf16le');
file.set(sbuf.toBuffer(item._value), item.offset + this.sect.strs._offset);
}
}
@@ -169,9 +171,9 @@ export default class KMXPlusBuilder {
private emitElements(file: Uint8Array) {
if(this.sect.elem) {
- for(let str of this.sect.elem.strings) {
+ for(const str of this.sect.elem.strings) {
if(str.items.length > 0) {
- let COMP_PLUS_ELEM_ELEMENTS = new r.Array(this.file.COMP_PLUS_ELEM_ELEMENT, str.items.length);
+ const COMP_PLUS_ELEM_ELEMENTS = new r.Array(this.file.COMP_PLUS_ELEM_ELEMENT, str.items.length);
file.set(COMP_PLUS_ELEM_ELEMENTS.toBuffer(str.items), str.offset + this.sect.elem._offset);
}
}
diff --git a/common/web/types/src/kpj/keyman-developer-project.ts b/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts
similarity index 92%
rename from common/web/types/src/kpj/keyman-developer-project.ts
rename to developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts
index 76f3ab709a..f678106a4c 100644
--- a/common/web/types/src/kpj/keyman-developer-project.ts
+++ b/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts
@@ -2,8 +2,8 @@
// Version 1.0 and 2.0 of Keyman Developer Project .kpj file
//
-import { KeymanFileTypes } from '../main.js';
-import { CompilerCallbacks } from '../util/compiler-interfaces.js';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerCallbacks } from '../../compiler-interfaces.js';
export class KeymanDeveloperProject {
options: KeymanDeveloperProjectOptions;
@@ -29,13 +29,13 @@ export class KeymanDeveloperProject {
if(this.options.version != '2.0') {
throw new Error('populateFiles can only be called on a v2.0 project');
}
- let sourcePath = this.resolveProjectPath(this.options.sourcePath);
+ const sourcePath = this.resolveProjectPath(this.options.sourcePath);
if(!this.callbacks.fs.existsSync(sourcePath)) {
return false;
}
- let files = this.callbacks.fs.readdirSync(sourcePath);
- for(let filename of files) {
- let fullPath = this.callbacks.path.join(sourcePath, filename);
+ const files = this.callbacks.fs.readdirSync(sourcePath);
+ for(const filename of files) {
+ const fullPath = this.callbacks.path.join(sourcePath, filename);
if(KeymanFileTypes.filenameIs(filename, KeymanFileTypes.Source.LdmlKeyboard)) {
try {
const data = this.callbacks.loadFile(fullPath);
@@ -51,7 +51,7 @@ export class KeymanDeveloperProject {
}
}
if(KeymanFileTypes.sourceTypeFromFilename(filename) !== null) {
- let file = new KeymanDeveloperProjectFile20(fullPath, this.callbacks);
+ const file = new KeymanDeveloperProjectFile20(fullPath, this.callbacks);
this.files.push(file);
}
}
@@ -109,7 +109,7 @@ export class KeymanDeveloperProject {
p = this.resolveProjectPath(p);
- let f = file.filename.replace(new RegExp(`\\${sourceExt}$`, 'i'), targetExt);
+ const f = file.filename.replace(new RegExp(`\\${sourceExt}$`, 'i'), targetExt);
return this.callbacks.path.normalize(this.callbacks.path.join(p, f));
}
diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts
similarity index 81%
rename from common/web/types/src/kpj/kpj-file-reader.ts
rename to developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts
index eb2a168a48..d776898753 100644
--- a/common/web/types/src/kpj/kpj-file-reader.ts
+++ b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts
@@ -1,9 +1,9 @@
-import * as xml2js from '../deps/xml2js/xml2js.js';
+import { xml2js } from '../../index.js';
import { KPJFile, KPJFileProject } from './kpj-file.js';
-import { boxXmlArray } from '../util/util.js';
+import { util } from '@keymanapp/common-types';
import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js';
-import { CompilerCallbacks } from '../util/compiler-interfaces.js';
-import SchemaValidators from '../schema-validators.js';
+import { SchemaValidators } from '@keymanapp/common-types';
+import { CompilerCallbacks } from '../../compiler-interfaces.js';
export class KPJFileReader {
constructor(private callbacks: CompilerCallbacks) {
@@ -29,7 +29,7 @@ export class KPJFileReader {
});
data = this.boxArrays(data);
if(data.KeymanDeveloperProject?.Files?.File?.length) {
- for(let file of data.KeymanDeveloperProject?.Files?.File) {
+ for(const file of data.KeymanDeveloperProject?.Files?.File) {
// xml2js imports
as '' so we will just delete the empty string
if(typeof file.Details == 'string') {
delete file.Details;
@@ -40,11 +40,11 @@ export class KPJFileReader {
}
public validate(source: KPJFile): void {
- if(!SchemaValidators.kpj(source)) {
- if(!SchemaValidators.kpj90(source)) {
+ if(!SchemaValidators.default.kpj(source)) {
+ if(!SchemaValidators.default.kpj90(source)) {
// If the legacy schema also does not validate, then we will only report
// the errors against the modern schema
- throw new Error(JSON.stringify((SchemaValidators.kpj).errors));
+ throw new Error(JSON.stringify((SchemaValidators.default.kpj).errors));
}
}
}
@@ -60,8 +60,8 @@ export class KPJFileReader {
// NOTE: at this point, the xml should have been validated
// and matched the schema result so we can assume the source
// is a valid shape
- let project = source.KeymanDeveloperProject;
- let result: KeymanDeveloperProject = new KeymanDeveloperProject(projectFilename, project.Options?.Version || "1.0", this.callbacks);
+ const project = source.KeymanDeveloperProject;
+ const result: KeymanDeveloperProject = new KeymanDeveloperProject(projectFilename, project.Options?.Version || "1.0", this.callbacks);
if(result.options.version == '2.0') {
result.options.buildPath = (project.Options?.BuildPath || result.options.buildPath).replace(/\\/g, '/');
result.options.sourcePath = (project.Options?.SourcePath || result.options.sourcePath).replace(/\\/g, '/');
@@ -88,9 +88,9 @@ export class KPJFileReader {
}
private transformFilesVersion10(project: KPJFileProject, result: KeymanDeveloperProject) {
- let ids: { [id: string]: KeymanDeveloperProjectFile10; } = {};
- for (let sourceFile of project.Files?.File) {
- let file: KeymanDeveloperProjectFile10 = new KeymanDeveloperProjectFile10(
+ const ids: { [id: string]: KeymanDeveloperProjectFile10; } = {};
+ for (const sourceFile of project.Files?.File) {
+ const file: KeymanDeveloperProjectFile10 = new KeymanDeveloperProjectFile10(
sourceFile.ID || '',
(sourceFile.Filepath || '').replace(/\\/g, '/'),
sourceFile.FileVersion || '',
@@ -123,7 +123,7 @@ export class KPJFileReader {
if(!source.KeymanDeveloperProject.Files || typeof source.KeymanDeveloperProject.Files == 'string') {
source.KeymanDeveloperProject.Files = {File:[]};
}
- boxXmlArray(source.KeymanDeveloperProject.Files, 'File');
+ util.boxXmlArray(source.KeymanDeveloperProject.Files, 'File');
return source;
}
}
\ No newline at end of file
diff --git a/common/web/types/src/kpj/kpj-file.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file.ts
similarity index 100%
rename from common/web/types/src/kpj/kpj-file.ts
rename to developer/src/common/web/utils/src/types/kpj/kpj-file.ts
diff --git a/common/web/types/src/package/kps-file.ts b/developer/src/common/web/utils/src/types/kps/kps-file.ts
similarity index 100%
rename from common/web/types/src/package/kps-file.ts
rename to developer/src/common/web/utils/src/types/kps/kps-file.ts
diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/developer/src/common/web/utils/src/types/kvks/kvks-file-reader.ts
similarity index 78%
rename from common/web/types/src/kvk/kvks-file-reader.ts
rename to developer/src/common/web/utils/src/types/kvks/kvks-file-reader.ts
index ff3a094e6f..6b2ad8e0a9 100644
--- a/common/web/types/src/kvk/kvks-file-reader.ts
+++ b/developer/src/common/web/utils/src/types/kvks/kvks-file-reader.ts
@@ -1,10 +1,19 @@
-import * as xml2js from '../deps/xml2js/xml2js.js';
+import { SchemaValidators as SV, KvkFile, util, Constants } from '@keymanapp/common-types';
+import { xml2js } from '../../index.js'
import KVKSourceFile from './kvks-file.js';
-import { boxXmlArray } from '../util/util.js';
-import { DEFAULT_KVK_FONT, VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js';
-import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js';
-import { BUILDER_KVK_HEADER_VERSION, KVK_HEADER_IDENTIFIER_BYTES } from './kvk-file.js';
-import SchemaValidators from '../schema-validators.js';
+const SchemaValidators = SV.default;
+import boxXmlArray = util.boxXmlArray;
+import USVirtualKeyCodes = Constants.USVirtualKeyCodes;
+import { VisualKeyboard as VK } from '@keymanapp/common-types';
+import DEFAULT_KVK_FONT = VK.DEFAULT_KVK_FONT;
+import VisualKeyboard = VK.VisualKeyboard;
+import VisualKeyboardHeaderFlags = VK.VisualKeyboardHeaderFlags;
+import VisualKeyboardKey = VK.VisualKeyboardKey;
+import VisualKeyboardKeyFlags = VK.VisualKeyboardKeyFlags;
+import VisualKeyboardLegalShiftStates = VK.VisualKeyboardLegalShiftStates;
+import VisualKeyboardShiftState = VK.VisualKeyboardShiftState;
+import BUILDER_KVK_HEADER_VERSION = KvkFile.BUILDER_KVK_HEADER_VERSION;
+import KVK_HEADER_IDENTIFIER_BYTES = KvkFile.KVK_HEADER_IDENTIFIER_BYTES;
export default class KVKSFileReader {
@@ -69,9 +78,9 @@ export default class KVKSFileReader {
}
}
- for(let key of Object.keys(source)) {
+ for(const key of Object.keys(source)) {
if(Array.isArray(source[key])) {
- for(let item of source[key]) {
+ for(const item of source[key]) {
if(typeof(item) === 'object') {
this.cleanupUnderscore(key, item);
}
@@ -92,7 +101,7 @@ export default class KVKSFileReader {
// NOTE: at this point, the xml should have been validated
// and matched the schema result so we can assume properties exist
- let result: VisualKeyboard = {
+ const result: VisualKeyboard = {
header: {
version: BUILDER_KVK_HEADER_VERSION,
flags: 0,
@@ -117,22 +126,22 @@ export default class KVKSFileReader {
result.header.flags |= VisualKeyboardHeaderFlags.kvkhUseUnderlying;
}
- for(let encoding of source.visualkeyboard.encoding) {
- let isUnicode = (encoding.$?.name == 'unicode'),
+ for(const encoding of source.visualkeyboard.encoding) {
+ const isUnicode = (encoding.$?.name == 'unicode'),
font = isUnicode ? result.header.unicodeFont : result.header.ansiFont;
font.name = encoding.$?.fontname ?? DEFAULT_KVK_FONT.name;
font.size = parseInt(encoding.$?.fontsize ?? DEFAULT_KVK_FONT.size.toString(), 10);
- for(let layer of encoding.layer) {
- let shift = this.kvksShiftToKvkShift(layer.$?.shift);
- for(let sourceKey of layer.key) {
- let vkey = (USVirtualKeyCodes as any)[sourceKey.$?.vkey];
+ for(const layer of encoding.layer) {
+ const shift = this.kvksShiftToKvkShift(layer.$?.shift);
+ for(const sourceKey of layer.key) {
+ const vkey = (USVirtualKeyCodes as any)[sourceKey.$?.vkey];
if(!vkey) {
if(typeof invalidVkeys !== 'undefined') {
invalidVkeys.push(sourceKey.$?.vkey);
}
continue;
}
- let key: VisualKeyboardKey = {
+ const key: VisualKeyboardKey = {
flags:
(isUnicode ? VisualKeyboardKeyFlags.kvkkUnicode : 0) |
(sourceKey.bitmap ? VisualKeyboardKeyFlags.kvkkBitmap : 0),
@@ -167,9 +176,9 @@ export default class KVKSFileReader {
*/
private boxArrays(source: KVKSourceFile) {
boxXmlArray(source.visualkeyboard, 'encoding');
- for(let encoding of source.visualkeyboard.encoding) {
+ for(const encoding of source.visualkeyboard.encoding) {
boxXmlArray(encoding, 'layer');
- for(let layer of encoding.layer) {
+ for(const layer of encoding.layer) {
boxXmlArray(layer, 'key');
}
}
@@ -181,7 +190,7 @@ export default class KVKSFileReader {
shift = shift.toUpperCase();
// TODO-LDML(lowpri): make a map of this?
- for(let state of VisualKeyboardLegalShiftStates) {
+ for(const state of VisualKeyboardLegalShiftStates) {
if(state.name == shift) {
return state.shift;
}
diff --git a/common/web/types/src/kvk/kvks-file-writer.ts b/developer/src/common/web/utils/src/types/kvks/kvks-file-writer.ts
similarity index 73%
rename from common/web/types/src/kvk/kvks-file-writer.ts
rename to developer/src/common/web/utils/src/types/kvks/kvks-file-writer.ts
index 19327e779d..dd4962a8fa 100644
--- a/common/web/types/src/kvk/kvks-file-writer.ts
+++ b/developer/src/common/web/utils/src/types/kvks/kvks-file-writer.ts
@@ -1,7 +1,13 @@
-import * as xml2js from '../deps/xml2js/xml2js.js';
+import { VisualKeyboard as VK, Constants } from '@keymanapp/common-types';
import KVKSourceFile, { KVKSEncoding, KVKSFlags, KVKSKey, KVKSLayer } from './kvks-file.js';
-import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js';
-import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js';
+import { xml2js } from '../../index.js';
+
+import USVirtualKeyCodes = Constants.USVirtualKeyCodes;
+import VisualKeyboard = VK.VisualKeyboard;
+import VisualKeyboardHeaderFlags = VK.VisualKeyboardHeaderFlags;
+import VisualKeyboardKeyFlags = VK.VisualKeyboardKeyFlags;
+import VisualKeyboardLegalShiftStates = VK.VisualKeyboardLegalShiftStates;
+import VisualKeyboardShiftState = VK.VisualKeyboardShiftState;
export default class KVKSFileWriter {
public write(vk: VisualKeyboard): string {
@@ -17,7 +23,7 @@ export default class KVKSFileWriter {
}
})
- let flags: KVKSFlags = {};
+ const flags: KVKSFlags = {};
if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) {
flags.displayunderlying = '';
}
@@ -33,7 +39,7 @@ export default class KVKSFileWriter {
- let kvks: KVKSourceFile = {
+ const kvks: KVKSourceFile = {
visualkeyboard: {
header: {
version: '10.0',
@@ -46,9 +52,9 @@ export default class KVKSFileWriter {
if(vk.header.underlyingLayout) kvks.visualkeyboard.header.layout = vk.header.underlyingLayout;
- let encodings: {ansi: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}, unicode: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}} = {ansi:null,unicode:null};
+ const encodings: {ansi: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}, unicode: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}} = {ansi:null,unicode:null};
- for(let key of vk.keys) {
+ for(const key of vk.keys) {
const encoding = key.flags & VisualKeyboardKeyFlags.kvkkUnicode ? 'unicode' : 'ansi';
const shift = this.kvkShiftToKvksShift(key.shift);
@@ -65,7 +71,7 @@ export default class KVKSFileWriter {
};
kvks.visualkeyboard.encoding.push(encodings[encoding].o);
}
- let e = encodings[encoding];
+ const e = encodings[encoding];
if(!e.l[shift]) {
e.l[shift] = {
key: [],
@@ -73,11 +79,11 @@ export default class KVKSFileWriter {
};
e.o.layer.push(e.l[shift]);
}
- let l = e.l[shift];
+ const l = e.l[shift];
// TODO-LDML: map
let vkeyName = '';
- for(let vkey of Object.keys(USVirtualKeyCodes)) {
+ for(const vkey of Object.keys(USVirtualKeyCodes)) {
if((USVirtualKeyCodes as any)[vkey] == key.vkey) {
vkeyName = vkey;
break;
@@ -88,7 +94,7 @@ export default class KVKSFileWriter {
//TODO-LDML: warn
continue;
}
- let k: KVKSKey = {
+ const k: KVKSKey = {
$: {vkey: vkeyName},
_: key.text,
}
@@ -99,7 +105,7 @@ export default class KVKSFileWriter {
l.key.push(k);
}
- let result = builder.buildObject(kvks);
+ const result = builder.buildObject(kvks);
return result; //Uint8Array.from(result);
}
@@ -111,7 +117,7 @@ export default class KVKSFileWriter {
public kvkShiftToKvksShift(shift: VisualKeyboardShiftState): string {
// TODO-LDML(lowpri): make a map of this?
- for(let state of VisualKeyboardLegalShiftStates) {
+ for(const state of VisualKeyboardLegalShiftStates) {
if(state.shift == shift) {
return state.name;
}
diff --git a/common/web/types/src/kvk/kvks-file.ts b/developer/src/common/web/utils/src/types/kvks/kvks-file.ts
similarity index 100%
rename from common/web/types/src/kvk/kvks-file.ts
rename to developer/src/common/web/utils/src/types/kvks/kvks-file.ts
diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-testdata-xml.ts
similarity index 100%
rename from common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts
rename to developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-testdata-xml.ts
diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts
similarity index 93%
rename from common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts
rename to developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts
index 9055e8ed1d..162cac4aac 100644
--- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts
+++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts
@@ -1,11 +1,12 @@
-import * as xml2js from '../deps/xml2js/xml2js.js';
+import { SchemaValidators, util } from '@keymanapp/common-types';
+import { xml2js } from '../../index.js';
+import { CommonTypesMessages } from '../../common-events.js';
+import { CompilerCallbacks } from '../../compiler-interfaces.js';
import { LDMLKeyboardXMLSourceFile, LKImport, ImportStatus } from './ldml-keyboard-xml.js';
-import { boxXmlArray } from '../util/util.js';
-import { CompilerCallbacks } from '../util/compiler-interfaces.js';
import { constants } from '@keymanapp/ldml-keyboard-constants';
-import { CommonTypesMessages } from '../util/common-events.js';
import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js';
-import SchemaValidators from '../schema-validators.js';
+
+import boxXmlArray = util.boxXmlArray;
interface NameAndProps {
'$'?: any; // content
@@ -22,7 +23,7 @@ export class LDMLKeyboardXMLSourceFileReader {
}
static get defaultImportsURL(): [string,string] {
- return ['../import/', import.meta.url];
+ return ['../../import/', import.meta.url];
}
readImportFile(version: string, subpath: string): Uint8Array {
@@ -63,10 +64,10 @@ export class LDMLKeyboardXMLSourceFileReader {
boxXmlArray(source?.keyboard3?.locales, 'locale');
boxXmlArray(source?.keyboard3, 'transforms');
if(source?.keyboard3?.layers) {
- for(let layers of source?.keyboard3?.layers) {
+ for(const layers of source?.keyboard3?.layers) {
boxXmlArray(layers, 'layer');
if(layers?.layer) {
- for(let layer of layers?.layer) {
+ for(const layer of layers?.layer) {
boxXmlArray(layer, 'row');
}
}
@@ -74,13 +75,13 @@ export class LDMLKeyboardXMLSourceFileReader {
}
if(source?.keyboard3?.forms?.form) {
boxXmlArray(source?.keyboard3?.forms, 'form');
- for(let form of source?.keyboard3?.forms?.form) {
+ for(const form of source?.keyboard3?.forms?.form) {
boxXmlArray(form, 'scanCodes');
}
}
if(source?.keyboard3?.flicks) {
boxXmlArray(source?.keyboard3?.flicks, 'flick');
- for(let flick of source?.keyboard3?.flicks?.flick) {
+ for(const flick of source?.keyboard3?.flicks?.flick) {
boxXmlArray(flick, 'flickSegment');
}
}
@@ -90,9 +91,9 @@ export class LDMLKeyboardXMLSourceFileReader {
boxXmlArray(source?.keyboard3?.variables, 'uset');
}
if(source?.keyboard3?.transforms) {
- for(let transforms of source.keyboard3.transforms) {
+ for(const transforms of source.keyboard3.transforms) {
boxXmlArray(transforms, 'transformGroup');
- for (let transformGroup of transforms.transformGroup) {
+ for (const transformGroup of transforms.transformGroup) {
boxXmlArray(transformGroup, 'transform');
boxXmlArray(transformGroup, 'reorder');
}
@@ -235,8 +236,8 @@ export class LDMLKeyboardXMLSourceFileReader {
* @returns true if valid, false if invalid
*/
public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile): boolean {
- if(!SchemaValidators.ldmlKeyboard3(source)) {
- for (let err of (SchemaValidators.ldmlKeyboard3).errors) {
+ if(!SchemaValidators.default.ldmlKeyboard3(source)) {
+ for (const err of (SchemaValidators.default.ldmlKeyboard3).errors) {
this.callbacks.reportMessage(CommonTypesMessages.Error_SchemaValidationError({
instancePath: err.instancePath,
keyword: err.keyword,
@@ -250,9 +251,9 @@ export class LDMLKeyboardXMLSourceFileReader {
}
loadUnboxed(file: Uint8Array): LDMLKeyboardXMLSourceFile {
- let source = (() => {
+ const source = (() => {
let a: LDMLKeyboardXMLSourceFile;
- let parser = new xml2js.Parser({
+ const parser = new xml2js.Parser({
explicitArray: false,
mergeAttrs: true,
includeWhiteChars: false,
@@ -289,9 +290,9 @@ export class LDMLKeyboardXMLSourceFileReader {
}
loadTestDataUnboxed(file: Uint8Array): any {
- let source = (() => {
+ const source = (() => {
let a: any;
- let parser = new xml2js.Parser({
+ const parser = new xml2js.Parser({
// explicitArray: false,
preserveChildrenOrder:true, // needed for test data
explicitChildren: true, // needed for test data
diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts
similarity index 100%
rename from common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts
rename to developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts
diff --git a/developer/src/common/web/utils/src/KeymanSentry.ts b/developer/src/common/web/utils/src/utils/KeymanSentry.ts
similarity index 100%
rename from developer/src/common/web/utils/src/KeymanSentry.ts
rename to developer/src/common/web/utils/src/utils/KeymanSentry.ts
diff --git a/developer/src/common/web/utils/src/keyman-urls.ts b/developer/src/common/web/utils/src/utils/keyman-urls.ts
similarity index 100%
rename from developer/src/common/web/utils/src/keyman-urls.ts
rename to developer/src/common/web/utils/src/utils/keyman-urls.ts
diff --git a/developer/src/common/web/utils/src/markdown.ts b/developer/src/common/web/utils/src/utils/markdown.ts
similarity index 100%
rename from developer/src/common/web/utils/src/markdown.ts
rename to developer/src/common/web/utils/src/utils/markdown.ts
diff --git a/developer/src/common/web/utils/src/options.ts b/developer/src/common/web/utils/src/utils/options.ts
similarity index 100%
rename from developer/src/common/web/utils/src/options.ts
rename to developer/src/common/web/utils/src/utils/options.ts
diff --git a/developer/src/common/web/utils/src/validate-mit-license.ts b/developer/src/common/web/utils/src/utils/validate-mit-license.ts
similarity index 100%
rename from developer/src/common/web/utils/src/validate-mit-license.ts
rename to developer/src/common/web/utils/src/utils/validate-mit-license.ts
diff --git a/common/web/types/test/helpers/TestCompilerCallbacks.ts b/developer/src/common/web/utils/test/TestCompilerCallbacks.ts
similarity index 87%
rename from common/web/types/test/helpers/TestCompilerCallbacks.ts
rename to developer/src/common/web/utils/test/TestCompilerCallbacks.ts
index d231290fef..d1413e477a 100644
--- a/common/web/types/test/helpers/TestCompilerCallbacks.ts
+++ b/developer/src/common/web/utils/test/TestCompilerCallbacks.ts
@@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
-import { loadFile, resolveFilename } from '../helpers/index.js';
-import { CompilerCallbacks, CompilerError, CompilerEvent, CompilerFileSystemCallbacks, CompilerPathCallbacks } from '../../src/util/compiler-interfaces.js';
+import { loadFile, resolveFilename } from './helpers/index.js';
+import { CompilerCallbacks, CompilerError, CompilerEvent, CompilerFileSystemCallbacks, CompilerPathCallbacks } from '../src/compiler-interfaces.js';
// This is related to developer/src/common/web/test-helpers/index.ts but has a slightly different API surface
// as this runs at a lower level than the compiler.
@@ -40,7 +40,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks {
return loadFile(filename);
} catch (e) {
if (e.code === 'ENOENT') {
- return null;
+ return null;
} else {
throw e;
}
diff --git a/common/web/types/test/fixtures/keyman-touch-layout/khmer_angkor.keyman-touch-layout b/developer/src/common/web/utils/test/fixtures/keyman-touch-layout/khmer_angkor.keyman-touch-layout
similarity index 100%
rename from common/web/types/test/fixtures/keyman-touch-layout/khmer_angkor.keyman-touch-layout
rename to developer/src/common/web/utils/test/fixtures/keyman-touch-layout/khmer_angkor.keyman-touch-layout
diff --git a/common/web/types/test/fixtures/keyman-touch-layout/legacy.keyman-touch-layout b/developer/src/common/web/utils/test/fixtures/keyman-touch-layout/legacy.keyman-touch-layout
similarity index 100%
rename from common/web/types/test/fixtures/keyman-touch-layout/legacy.keyman-touch-layout
rename to developer/src/common/web/utils/test/fixtures/keyman-touch-layout/legacy.keyman-touch-layout
diff --git a/common/web/types/test/fixtures/kpj/khmer_angkor.kpj b/developer/src/common/web/utils/test/fixtures/kpj/khmer_angkor.kpj
similarity index 100%
rename from common/web/types/test/fixtures/kpj/khmer_angkor.kpj
rename to developer/src/common/web/utils/test/fixtures/kpj/khmer_angkor.kpj
diff --git a/common/web/types/test/fixtures/kpj/project-missing-file/project_missing_file.kpj b/developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_file.kpj
similarity index 100%
rename from common/web/types/test/fixtures/kpj/project-missing-file/project_missing_file.kpj
rename to developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_file.kpj
diff --git a/common/web/types/test/fixtures/kpj/project-missing-file/project_missing_files.kpj b/developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_files.kpj
similarity index 100%
rename from common/web/types/test/fixtures/kpj/project-missing-file/project_missing_files.kpj
rename to developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_files.kpj
diff --git a/developer/src/common/web/utils/test/fixtures/kvks/balochi_inpage.kvk b/developer/src/common/web/utils/test/fixtures/kvks/balochi_inpage.kvk
new file mode 100644
index 0000000000..56fee9dfbd
Binary files /dev/null and b/developer/src/common/web/utils/test/fixtures/kvks/balochi_inpage.kvk differ
diff --git a/common/web/types/test/fixtures/kvk/balochi_inpage.kvks b/developer/src/common/web/utils/test/fixtures/kvks/balochi_inpage.kvks
similarity index 100%
rename from common/web/types/test/fixtures/kvk/balochi_inpage.kvks
rename to developer/src/common/web/utils/test/fixtures/kvks/balochi_inpage.kvks
diff --git a/developer/src/common/web/utils/test/fixtures/kvks/khmer_angkor.kvk b/developer/src/common/web/utils/test/fixtures/kvks/khmer_angkor.kvk
new file mode 100644
index 0000000000..c64b3a5a54
Binary files /dev/null and b/developer/src/common/web/utils/test/fixtures/kvks/khmer_angkor.kvk differ
diff --git a/common/web/types/test/fixtures/kvk/khmer_angkor.kvks b/developer/src/common/web/utils/test/fixtures/kvks/khmer_angkor.kvks
similarity index 100%
rename from common/web/types/test/fixtures/kvk/khmer_angkor.kvks
rename to developer/src/common/web/utils/test/fixtures/kvks/khmer_angkor.kvks
diff --git a/common/web/types/test/fixtures/import-minimal.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-minimal.xml
similarity index 100%
rename from common/web/types/test/fixtures/import-minimal.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-minimal.xml
diff --git a/common/web/types/test/fixtures/import-minimal1.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-minimal1.xml
similarity index 100%
rename from common/web/types/test/fixtures/import-minimal1.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-minimal1.xml
diff --git a/common/web/types/test/fixtures/import-minimal2.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-minimal2.xml
similarity index 100%
rename from common/web/types/test/fixtures/import-minimal2.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-minimal2.xml
diff --git a/common/web/types/test/fixtures/import-symbols.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-symbols.xml
similarity index 100%
rename from common/web/types/test/fixtures/import-symbols.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-symbols.xml
diff --git a/common/web/types/test/fixtures/invalid-conforms-to.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-conforms-to.xml
similarity index 100%
rename from common/web/types/test/fixtures/invalid-conforms-to.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-conforms-to.xml
diff --git a/common/web/types/test/fixtures/invalid-import-base.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-base.xml
similarity index 100%
rename from common/web/types/test/fixtures/invalid-import-base.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-base.xml
diff --git a/common/web/types/test/fixtures/invalid-import-path.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-path.xml
similarity index 100%
rename from common/web/types/test/fixtures/invalid-import-path.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-path.xml
diff --git a/common/web/types/test/fixtures/invalid-import-readfail.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-readfail.xml
similarity index 100%
rename from common/web/types/test/fixtures/invalid-import-readfail.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-readfail.xml
diff --git a/common/web/types/test/fixtures/invalid-import-wrongroot.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-wrongroot.xml
similarity index 100%
rename from common/web/types/test/fixtures/invalid-import-wrongroot.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-wrongroot.xml
diff --git a/common/web/types/test/fixtures/invalid-structure-per-dtd.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-structure-per-dtd.xml
similarity index 100%
rename from common/web/types/test/fixtures/invalid-structure-per-dtd.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-structure-per-dtd.xml
diff --git a/common/web/types/test/fixtures/test-fr.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/test-fr.xml
similarity index 100%
rename from common/web/types/test/fixtures/test-fr.xml
rename to developer/src/common/web/utils/test/fixtures/ldml-keyboard/test-fr.xml
diff --git a/developer/src/common/web/utils/test/helpers/index.ts b/developer/src/common/web/utils/test/helpers/index.ts
index 9968fbfea6..e36d07429b 100644
--- a/developer/src/common/web/utils/test/helpers/index.ts
+++ b/developer/src/common/web/utils/test/helpers/index.ts
@@ -2,6 +2,7 @@
* Helpers and utilities for the Mocha tests.
*/
import * as path from 'path';
+import * as fs from "fs";
import { fileURLToPath } from 'url';
/**
@@ -16,3 +17,25 @@ import { fileURLToPath } from 'url';
return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url));
}
+export function loadFile(filename: string | URL): Buffer {
+ return fs.readFileSync(filename);
+}
+
+export function resolveFilename(baseFilename: string, filename: 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;
+}
diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts
similarity index 88%
rename from common/web/types/test/helpers/reader-callback-test.ts
rename to developer/src/common/web/utils/test/helpers/reader-callback-test.ts
index 907793cdf7..c67d49dfdb 100644
--- a/common/web/types/test/helpers/reader-callback-test.ts
+++ b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts
@@ -1,11 +1,11 @@
import 'mocha';
import {assert} from 'chai';
-import { loadFile, makePathToFixture } from '../helpers/index.js';
-import { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js';
-import { CompilerEvent } from '../../src/util/compiler-interfaces.js';
-import { LDMLKeyboardXMLSourceFile } from '../../src/ldml-keyboard/ldml-keyboard-xml.js';
-import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboard-testdata-xml.js';
-import { TestCompilerCallbacks } from './TestCompilerCallbacks.js';
+import { loadFile, makePathToFixture } from './index.js';
+import { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from '../../src/types/ldml-keyboard/ldml-keyboard-xml-reader.js';
+import { CompilerEvent } from '../../src/compiler-interfaces.js';
+import { LDMLKeyboardXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-keyboard-xml.js';
+import { LDMLKeyboardTestDataXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-keyboard-testdata-xml.js';
+import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
import { fileURLToPath } from 'url';
const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = {
@@ -77,14 +77,14 @@ export function testReaderCases(cases : CompilationCase[]) {
// we need our own callbacks rather than using the global so messages don't get mixed
const callbacks = new TestCompilerCallbacks();
const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks);
- for (let testcase of cases) {
+ for (const testcase of cases) {
const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail
const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`:
`should load: ${testcase.subpath}`;
it(testHeading, function () {
callbacks.clear();
- const data = loadFile(makePathToFixture(testcase.subpath));
+ const data = loadFile(makePathToFixture('ldml-keyboard', testcase.subpath));
assert.ok(data, `reading ${testcase.subpath}`);
const source = reader.load(data);
if (!testcase.loadfail) {
@@ -128,14 +128,14 @@ export function testTestdataReaderCases(cases : TestDataCase[]) {
// we need our own callbacks rather than using the global so messages don't get mixed
const callbacks = new TestCompilerCallbacks();
const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks);
- for (let testcase of cases) {
+ for (const testcase of cases) {
const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail
const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`:
`should load: ${testcase.subpath}`;
it(testHeading, function () {
callbacks.clear();
- const data = loadFile(makePathToFixture(testcase.subpath));
+ const data = loadFile(makePathToFixture('ldml-keyboard', testcase.subpath));
assert.ok(data, `reading ${testcase.subpath}`);
const source = reader.loadTestData(data);
if (!testcase.loadfail) {
diff --git a/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts b/developer/src/common/web/utils/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts
similarity index 96%
rename from common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts
rename to developer/src/common/web/utils/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts
index 479f2c4ccb..bfebec85e8 100644
--- a/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts
+++ b/developer/src/common/web/utils/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts
@@ -2,7 +2,7 @@ import * as fs from 'fs';
import 'mocha';
import { assert } from 'chai';
import { makePathToFixture } from '../helpers/index.js';
-import { TouchLayoutFileReader } from "../../src/keyman-touch-layout/keyman-touch-layout-file-reader.js";
+import { TouchLayoutFileReader } from "../../src/types/keyman-touch-layout/keyman-touch-layout-file-reader.js";
describe('TouchLayoutFileReader', function () {
it('should read a valid file', function() {
diff --git a/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts b/developer/src/common/web/utils/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts
similarity index 81%
rename from common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts
rename to developer/src/common/web/utils/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts
index f6a09d08d3..25870e6e09 100644
--- a/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts
+++ b/developer/src/common/web/utils/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts
@@ -2,8 +2,8 @@ import * as fs from 'fs';
import 'mocha';
import { assert } from 'chai';
import { makePathToFixture } from '../helpers/index.js';
-import { TouchLayoutFileReader } from "../../src/keyman-touch-layout/keyman-touch-layout-file-reader.js";
-import { TouchLayoutFileWriter } from "../../src/keyman-touch-layout/keyman-touch-layout-file-writer.js";
+import { TouchLayoutFileReader } from "../../src/types/keyman-touch-layout/keyman-touch-layout-file-reader.js";
+import { TouchLayoutFileWriter } from "../../src/types/keyman-touch-layout/keyman-touch-layout-file-writer.js";
describe('TouchLayoutFile', function () {
it('should round-trip from TouchLayoutFileReader to TouchLayoutFileWriter', function() {
@@ -26,7 +26,7 @@ describe('TouchLayoutFile', function () {
assert.deepEqual(layout, newLayout);
// And do the same without any options
- let output2 = writer.write(layout);
+ const output2 = writer.write(layout);
assert.deepEqual(output, output2);
newLayout = reader.read(output2);
diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts b/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-testdata-reader.ts
similarity index 96%
rename from common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts
rename to developer/src/common/web/utils/test/kmx/test-ldml-keyboard-testdata-reader.ts
index 794adbbe66..d2ce6dc72a 100644
--- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts
+++ b/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-testdata-reader.ts
@@ -2,7 +2,7 @@ import { constants } from '@keymanapp/ldml-keyboard-constants';
import { assert } from 'chai';
import 'mocha';
import { testTestdataReaderCases } from '../helpers/reader-callback-test.js';
-import { LKTAnyAction } from './ldml-keyboard-testdata-xml.js';
+import { LKTAnyAction } from '../../src/types/ldml-keyboard/ldml-keyboard-testdata-xml.js';
describe('ldml keyboard xml reader tests', function () {
this.slow(500); // 0.5 sec -- json schema validation takes a while
diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-xml-reader.ts
similarity index 95%
rename from common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts
rename to developer/src/common/web/utils/test/kmx/test-ldml-keyboard-xml-reader.ts
index fa99be5267..a79960c7ca 100644
--- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts
+++ b/developer/src/common/web/utils/test/kmx/test-ldml-keyboard-xml-reader.ts
@@ -1,9 +1,13 @@
-import { LKKey, ImportStatus } from './../../src/ldml-keyboard/ldml-keyboard-xml.js';
+import { LKKey, ImportStatus } from '../../src/types/ldml-keyboard/ldml-keyboard-xml.js';
import 'mocha';
import {assert} from 'chai';
-import { CommonTypesMessages } from '../../src/util/common-events.js';
+import { CommonTypesMessages } from '../../src/common-events.js';
import { testReaderCases } from '../helpers/reader-callback-test.js';
-import { CLDRScanToVkey, CLDRScanToKeyMap, USVirtualKeyCodes } from '../../src/consts/virtual-key-constants.js';
+import { Constants } from '@keymanapp/common-types';
+
+import CLDRScanToVkey = Constants.CLDRScanToVkey;
+import CLDRScanToKeyMap = Constants.CLDRScanToKeyMap;
+import USVirtualKeyCodes = Constants.USVirtualKeyCodes;
function pluckKeysFromKeybag(keys: LKKey[], ids: string[]) {
return keys.filter(({id}) => ids.indexOf(id) !== -1);
diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts
similarity index 97%
rename from common/web/types/test/kpj/test-kpj-file-reader.ts
rename to developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts
index cbebdd7920..4b154af060 100644
--- a/common/web/types/test/kpj/test-kpj-file-reader.ts
+++ b/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts
@@ -2,9 +2,9 @@ import * as fs from 'fs';
import 'mocha';
import {assert} from 'chai';
import { makePathToFixture } from '../helpers/index.js';
-import { KPJFileReader } from "../../src/kpj/kpj-file-reader.js";
-import { KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from '../../src/kpj/keyman-developer-project.js';
-import { TestCompilerCallbacks } from '../helpers/TestCompilerCallbacks.js';
+import { KPJFileReader } from "../../src/types/kpj/kpj-file-reader.js";
+import { KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from '../../src/types/kpj/keyman-developer-project.js';
+import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
const callbacks = new TestCompilerCallbacks();
diff --git a/common/web/types/test/kvk/test-kvk-round-trip.ts b/developer/src/common/web/utils/test/kvks/test-kvk-round-trip.ts
similarity index 85%
rename from common/web/types/test/kvk/test-kvk-round-trip.ts
rename to developer/src/common/web/utils/test/kvks/test-kvk-round-trip.ts
index 7211ac3a35..b056bf4eed 100644
--- a/common/web/types/test/kvk/test-kvk-round-trip.ts
+++ b/developer/src/common/web/utils/test/kvks/test-kvk-round-trip.ts
@@ -5,10 +5,9 @@ import Hexy from 'hexy';
import gitDiff from 'git-diff';
const { hexy } = Hexy;
import { makePathToFixture } from '../helpers/index.js';
-import KvksFileReader from "../../src/kvk/kvks-file-reader.js";
-import KvkFileReader from "../../src/kvk/kvk-file-reader.js";
-import KvkFileWriter from "../../src/kvk/kvk-file-writer.js";
-import KvksFileWriter from "../../src/kvk/kvks-file-writer.js";
+import KvksFileReader from "../../src/types/kvks/kvks-file-reader.js";
+import { KvkFileReader, KvkFileWriter } from "@keymanapp/common-types";
+import KvksFileWriter from "../../src/types/kvks/kvks-file-writer.js";
/**
*
@@ -28,7 +27,7 @@ function assertBufferMatch(actual: Buffer, expected: Buffer) {
describe('kvk-file-reader', function () {
it('kvk-file-reader should round-trip with kvk-file-writer', function() {
- const path = makePathToFixture('kvk', 'khmer_angkor.kvk');
+ const path = makePathToFixture('kvks', 'khmer_angkor.kvk');
const input = fs.readFileSync(path);
const reader = new KvkFileReader();
const vk = reader.read(input);
@@ -44,8 +43,8 @@ describe('kvk-file-reader', function () {
describe('kvks-file-reader', function () {
it('kvks-file-reader should compile with kvk-file-writer', function() {
- const inputPath = makePathToFixture('kvk', 'khmer_angkor.kvks');
- const compiledPath = makePathToFixture('kvk', 'khmer_angkor.kvk');
+ const inputPath = makePathToFixture('kvks', 'khmer_angkor.kvks');
+ const compiledPath = makePathToFixture('kvks', 'khmer_angkor.kvk');
const input = fs.readFileSync(inputPath);
const compiled = fs.readFileSync(compiledPath);
const reader = new KvksFileReader();
@@ -63,7 +62,7 @@ describe('kvks-file-reader', function () {
describe('kvks-file-writer', function() {
it('kvks-file-writer should match what kvk-file-reader reads', function() {
- const kvkIn = makePathToFixture('kvk', 'khmer_angkor.kvk');
+ const kvkIn = makePathToFixture('kvks', 'khmer_angkor.kvk');
const kvkBuf = fs.readFileSync(kvkIn);
const kvkReader = new KvkFileReader();
const kvksReader = new KvksFileReader();
@@ -85,7 +84,7 @@ describe('kvks-file-reader', function () {
});
it('should have identical input and output for kvk and kvks', function() {
- const path = makePathToFixture('kvk', 'balochi_inpage.kvks');
+ const path = makePathToFixture('kvks', 'balochi_inpage.kvks');
const input = fs.readFileSync(path);
const reader = new KvksFileReader();
@@ -104,7 +103,7 @@ describe('kvks-file-reader', function () {
assert.deepEqual(vk, vkExpected);
// Then compare against the .kvk
- const kvkPath = makePathToFixture('kvk', 'balochi_inpage.kvk');
+ const kvkPath = makePathToFixture('kvks', 'balochi_inpage.kvk');
const kvkInput = fs.readFileSync(kvkPath);
const kvkReader = new KvkFileReader();
diff --git a/developer/src/common/web/utils/test/kvks/test-kvk-utils.ts b/developer/src/common/web/utils/test/kvks/test-kvk-utils.ts
new file mode 100644
index 0000000000..fe4c626d72
--- /dev/null
+++ b/developer/src/common/web/utils/test/kvks/test-kvk-utils.ts
@@ -0,0 +1,48 @@
+// NOTE: this is a copy of common/web/types/test/kvk/test-kvk-utils.ts
+import 'mocha';
+import {assert} from 'chai';
+import { VisualKeyboard as VK } from "@keymanapp/common-types";
+import VisualKeyboard = VK.VisualKeyboard;
+import VisualKeyboardHeaderFlags = VK.VisualKeyboardHeaderFlags;
+import VisualKeyboardKeyFlags = VK.VisualKeyboardKeyFlags;
+import VisualKeyboardShiftState = VK.VisualKeyboardShiftState;
+import { Constants } from '@keymanapp/common-types';
+import USVirtualKeyCodes = Constants.USVirtualKeyCodes;
+
+export function verify_khmer_angkor(vk: VisualKeyboard) {
+ assert.equal(vk.header.flags, VisualKeyboardHeaderFlags.kvkhAltGr);
+ assert.equal(vk.header.associatedKeyboard, 'khmer_angkor');
+ assert.equal(vk.header.ansiFont.name, 'Arial');
+ assert.equal(vk.header.ansiFont.size, -12);
+ assert.equal(vk.header.unicodeFont.name, 'Khmer Busra Kbd');
+ assert.equal(vk.header.unicodeFont.size, 16);
+ assert.equal(vk.keys.length, 186);
+ assert.equal(vk.keys[0].flags, VisualKeyboardKeyFlags.kvkkUnicode);
+ assert.equal(vk.keys[0].vkey, USVirtualKeyCodes.K_B);
+ assert.equal(vk.keys[0].shift, VisualKeyboardShiftState.KVKS_RALT);
+ assert.equal(vk.keys[0].text, 'ឞ');
+ assert.equal(vk.keys[185].flags, VisualKeyboardKeyFlags.kvkkUnicode);
+ assert.equal(vk.keys[185].vkey, USVirtualKeyCodes.K_COMMA);
+ assert.equal(vk.keys[185].shift, VisualKeyboardShiftState.KVKS_SHIFT);
+ assert.equal(vk.keys[185].text, '');
+}
+
+export function verify_balochi_inpage(vk: VisualKeyboard) {
+ assert.equal(vk.header.flags,
+ VisualKeyboardHeaderFlags.kvkhAltGr | VisualKeyboardHeaderFlags.kvkhDisplayUnderlying);
+ assert.equal(vk.header.associatedKeyboard, 'balochi_inpage');
+ assert.equal(vk.header.unicodeFont.name, 'Lateef');
+ assert.equal(vk.header.unicodeFont.size, 14);
+ assert.equal(vk.keys.length, 147);
+ assert.equal(vk.keys[0].flags, VisualKeyboardKeyFlags.kvkkUnicode);
+ assert.equal(vk.keys[0].vkey, USVirtualKeyCodes.K_BKQUOTE);
+ assert.equal(vk.keys[0].shift, VisualKeyboardShiftState.KVKS_RALT);
+ assert.equal(vk.keys[0].text, '');
+ assert.equal(vk.keys[30].flags,
+ VisualKeyboardKeyFlags.kvkkUnicode | VisualKeyboardKeyFlags.kvkkBitmap);
+ assert.equal(vk.keys[30].vkey, USVirtualKeyCodes.K_COMMA);
+ assert.equal(vk.keys[30].shift,
+ VisualKeyboardShiftState.KVKS_LCTRL);
+ assert.equal(vk.keys[30].text, '');
+ assert.equal(vk.keys[30].bitmap.byteLength, 35766);
+}
\ No newline at end of file
diff --git a/common/web/types/test/kvk/test-kvks-file.ts b/developer/src/common/web/utils/test/kvks/test-kvks-file.ts
similarity index 82%
rename from common/web/types/test/kvk/test-kvks-file.ts
rename to developer/src/common/web/utils/test/kvks/test-kvks-file.ts
index 2cd1a965c6..80851647bd 100644
--- a/common/web/types/test/kvk/test-kvks-file.ts
+++ b/developer/src/common/web/utils/test/kvks/test-kvks-file.ts
@@ -1,14 +1,14 @@
import * as fs from 'fs';
import 'mocha';
import { makePathToFixture } from '../helpers/index.js';
-import KvksFileReader from "../../src/kvk/kvks-file-reader.js";
-import KvksFileWriter from "../../src/kvk/kvks-file-writer.js";
+import KvksFileReader from "../../src/types/kvks/kvks-file-reader.js";
+import KvksFileWriter from "../../src/types/kvks/kvks-file-writer.js";
import { verify_khmer_angkor, verify_balochi_inpage } from './test-kvk-utils.js';
import { assert } from 'chai';
describe('kvks-file-reader', function() {
it('should read a valid file', function() {
- const path = makePathToFixture('kvk', 'khmer_angkor.kvks');
+ const path = makePathToFixture('kvks', 'khmer_angkor.kvks');
const input = fs.readFileSync(path);
const reader = new KvksFileReader();
@@ -23,7 +23,7 @@ describe('kvks-file-reader', function() {
});
it('should read a valid file with bitmaps', function() {
- const path = makePathToFixture('kvk', 'balochi_inpage.kvks');
+ const path = makePathToFixture('kvks', 'balochi_inpage.kvks');
const input = fs.readFileSync(path);
const reader = new KvksFileReader();
const kvks = reader.read(input);
@@ -34,7 +34,7 @@ describe('kvks-file-reader', function() {
});
it('should give a sensible error on a .kvk file', function() {
- const path = makePathToFixture('kvk', 'khmer_angkor.kvk');
+ const path = makePathToFixture('kvks', 'khmer_angkor.kvk');
const input = fs.readFileSync(path);
const reader = new KvksFileReader();
@@ -44,7 +44,7 @@ describe('kvks-file-reader', function() {
describe('kvks-file-writer', function() {
it('should write a valid file', function() {
- const path = makePathToFixture('kvk', 'khmer_angkor.kvks');
+ const path = makePathToFixture('kvks', 'khmer_angkor.kvks');
const input = fs.readFileSync(path);
const reader = new KvksFileReader();
diff --git a/developer/src/common/web/utils/test/test-license.ts b/developer/src/common/web/utils/test/test-license.ts
index 653f89ba69..501f4f4711 100644
--- a/developer/src/common/web/utils/test/test-license.ts
+++ b/developer/src/common/web/utils/test/test-license.ts
@@ -2,7 +2,7 @@ import * as fs from 'fs';
import { assert } from 'chai';
import 'mocha';
import { makePathToFixture } from './helpers/index.js';
-import { validateMITLicense } from '../src/validate-mit-license.js';
+import { validateMITLicense } from '../src/utils/validate-mit-license.js';
function verifyLicenseFile(filename: string) {
return validateMITLicense(fs.readFileSync(makePathToFixture('license', filename), 'utf-8'));
diff --git a/developer/src/common/web/utils/tsconfig.json b/developer/src/common/web/utils/tsconfig.json
index c5980c82f9..3be972dbcb 100644
--- a/developer/src/common/web/utils/tsconfig.json
+++ b/developer/src/common/web/utils/tsconfig.json
@@ -7,7 +7,7 @@
"baseUrl": ".",
},
"include": [
- "index.ts",
"src/**/*.ts",
+ "src/deps/xml2js/*.js",
],
}
\ No newline at end of file
diff --git a/developer/src/kmc-analyze/src/analyzer-messages.ts b/developer/src/kmc-analyze/src/analyzer-messages.ts
index 4cf6b83273..1e4204595e 100644
--- a/developer/src/kmc-analyze/src/analyzer-messages.ts
+++ b/developer/src/kmc-analyze/src/analyzer-messages.ts
@@ -1,8 +1,7 @@
///
// above is required for now, seemingly https://github.com/microsoft/TypeScript/issues/42873
// probably addressed in ts 5.5.2
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/common-types";
-import { KeymanUrls } from "@keymanapp/developer-utils";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.Analyzer;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
diff --git a/developer/src/kmc-analyze/src/osk-character-use/index.ts b/developer/src/kmc-analyze/src/osk-character-use/index.ts
index 86585ba961..36fbf0e78c 100644
--- a/developer/src/kmc-analyze/src/osk-character-use/index.ts
+++ b/developer/src/kmc-analyze/src/osk-character-use/index.ts
@@ -1,6 +1,6 @@
-import { CompilerCallbacks, KeymanFileTypes, KvksFile, KvksFileReader, TouchLayout, TouchLayoutFileReader } from "@keymanapp/common-types";
+import { KeymanFileTypes, TouchLayout } from "@keymanapp/common-types";
import { KmnCompilerMessages, Osk } from '@keymanapp/kmc-kmn';
-import { escapeMarkdownChar } from '@keymanapp/developer-utils';
+import { CompilerCallbacks, escapeMarkdownChar, KvksFile, KvksFileReader, TouchLayoutFileReader } from '@keymanapp/developer-utils';
import { getOskFromKmnFile } from "../util/get-osk-from-kmn-file.js";
import { AnalyzerMessages } from "../analyzer-messages.js";
diff --git a/developer/src/kmc-analyze/src/osk-rewrite-pua/index.ts b/developer/src/kmc-analyze/src/osk-rewrite-pua/index.ts
index fdc5143a30..dc56ebfb3a 100644
--- a/developer/src/kmc-analyze/src/osk-rewrite-pua/index.ts
+++ b/developer/src/kmc-analyze/src/osk-rewrite-pua/index.ts
@@ -1,4 +1,5 @@
-import { CompilerCallbacks, KeymanFileTypes, KvksFile, KvksFileReader, KvksFileWriter, TouchLayoutFileReader, TouchLayoutFileWriter } from "@keymanapp/common-types";
+import { KeymanFileTypes } from "@keymanapp/common-types";
+import { CompilerCallbacks, KvksFile, KvksFileReader, KvksFileWriter, TouchLayoutFileReader, TouchLayoutFileWriter } from '@keymanapp/developer-utils';
import { KmnCompilerMessages, Osk } from '@keymanapp/kmc-kmn';
import { getOskFromKmnFile } from "../util/get-osk-from-kmn-file.js";
import { AnalyzerMessages } from "../analyzer-messages.js";
diff --git a/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts b/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts
index 0e2ec28995..b9fd9cb602 100644
--- a/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts
+++ b/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts
@@ -1,4 +1,5 @@
-import { CompilerCallbacks, KMX, KmxFileReader } from "@keymanapp/common-types";
+import { KMX, KmxFileReader } from "@keymanapp/common-types";
+import { CompilerCallbacks } from "@keymanapp/developer-utils";
import { KmnCompiler } from "@keymanapp/kmc-kmn";
export async function getOskFromKmnFile(callbacks: CompilerCallbacks, filename: string): Promise<{
diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts
index d1a4c632c0..bf5d989718 100644
--- a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts
+++ b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler-messages.ts
@@ -1,4 +1,4 @@
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/common-types";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.KeyboardInfoCompiler;
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
@@ -52,8 +52,14 @@ export class KeyboardInfoCompilerMessages {
static Error_FontFileCannotBeRead = (o:{filename: string}) => m(this.ERROR_FontFileCannotBeRead,
`Font ${def(o.filename)} could not be parsed to extract a font family.`);
-static ERROR_FontFileMetaDataIsInvalid = SevError | 0x000F;
-static Error_FontFileMetaDataIsInvalid = (o:{filename: string,message:string}) => m(this.ERROR_FontFileMetaDataIsInvalid,
+ static ERROR_FontFileMetaDataIsInvalid = SevError | 0x000F;
+ static Error_FontFileMetaDataIsInvalid = (o:{filename: string,message:string}) => m(
+ this.ERROR_FontFileMetaDataIsInvalid,
`Font ${def(o.filename)} meta data invalid: ${def(o.message)}.`);
+
+ static ERROR_DescriptionIsMissing = SevError | 0x0010;
+ static Error_DescriptionIsMissing = (o:{filename:string}) => m(
+ this.ERROR_DescriptionIsMissing,
+ `The Info.Description field in the package ${def(o.filename)} is required, but is missing or empty.`);
}
diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts
index f829734ccc..8e89bb8164 100644
--- a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts
+++ b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts
@@ -5,10 +5,10 @@
import { minKeymanVersion } from "./min-keyman-version.js";
import { KeyboardInfoFile, KeyboardInfoFileIncludes, KeyboardInfoFileLanguageFont, KeyboardInfoFilePlatform } from "./keyboard-info-file.js";
-import { KeymanFileTypes, CompilerCallbacks, KmpJsonFile, KmxFileReader, KMX, KeymanTargets, KeymanCompiler, CompilerOptions, KeymanCompilerResult, KeymanCompilerArtifacts, KeymanCompilerArtifact } from "@keymanapp/common-types";
+import { KeymanFileTypes, KmpJsonFile, KmxFileReader, KMX, KeymanTargets } from "@keymanapp/common-types";
import { KeyboardInfoCompilerMessages } from "./keyboard-info-compiler-messages.js";
import langtags from "./imports/langtags.js";
-import { KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils";
+import { CompilerCallbacks, KeymanCompiler, CompilerOptions, KeymanCompilerResult, KeymanCompilerArtifacts, KeymanCompilerArtifact, KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils";
import { KmpCompiler } from "@keymanapp/kmc-package";
import { SchemaValidators } from "@keymanapp/common-types";
@@ -251,6 +251,9 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
if(kmpJsonData.info.description?.description) {
keyboard_info.description = kmpJsonData.info.description.description.trim();
+ } else {
+ this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_DescriptionIsMissing({filename:sources.kpsFilename}));
+ return null;
}
// extract the language identifiers from the language metadata arrays for
diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts
index 2d205f8f47..2bf73b7dd3 100644
--- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts
+++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts
@@ -2,7 +2,8 @@ import { assert } from 'chai';
import 'mocha';
import { KeyboardInfoCompilerMessages } from '../src/keyboard-info-compiler-messages.js';
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
-import { CompilerErrorNamespace, KmpJsonFile, KeymanFileTypes } from '@keymanapp/common-types';
+import { KmpJsonFile, KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
import { makePathToFixture } from './helpers/index.js';
import { KeyboardInfoCompiler, KeyboardInfoCompilerResult } from '../src/keyboard-info-compiler.js';
import { KeyboardInfoFile } from '../src/keyboard-info-file.js';
diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts
index f857c5d896..99c244be2d 100644
--- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts
+++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts
@@ -6,7 +6,8 @@ import { makePathToFixture } from './helpers/index.js';
import { KeyboardInfoCompiler, KeyboardInfoCompilerResult, unitTestEndpoints } from '../src/keyboard-info-compiler.js';
import langtags from "../src/imports/langtags.js";
import { KmpCompiler, KmpCompilerOptions } from '@keymanapp/kmc-package';
-import { CompilerCallbacks, KMX, KeymanFileTypes, KeymanTargets, KmpJsonFile } from '@keymanapp/common-types';
+import { KMX, KeymanFileTypes, KeymanTargets, KmpJsonFile } from '@keymanapp/common-types';
+import { CompilerCallbacks } from '@keymanapp/developer-utils';
import { KeyboardInfoFile, KeyboardInfoFileLanguage, KeyboardInfoFilePlatform } from './keyboard-info-file.js';
const callbacks = new TestCompilerCallbacks();
@@ -834,17 +835,4 @@ describe('keyboard-info-compiler', function () {
const result = await compiler['fontSourceToKeyboardInfoFont'](KHMER_ANGKOR_KPS, kmpJsonData, fonts);
assert.deepEqual(result, KHMER_ANGKOR_DISPLAY_FONT_INFO);
});
-
- it('handles missing info.version in a package file', async function() {
- const sources = {
- ...KHMER_ANGKOR_SOURCES,
- kpsFilename: makePathToFixture('missing-info-version-in-kps-11856', 'khmer_angkor.kps')
- };
- const compiler = new KeyboardInfoCompiler();
- assert.isTrue(await compiler.init(callbacks, {sources}));
- const kpjFilename = KHMER_ANGKOR_KPJ;
- const result = await compiler.run(kpjFilename);
- const actual = JSON.parse(new TextDecoder().decode(result.artifacts.keyboard_info.data));
- assert.equal(actual.version, '1.0');
- });
});
diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts
index f8938be60e..afb980e2fc 100644
--- a/developer/src/kmc-kmn/src/compiler/compiler.ts
+++ b/developer/src/kmc-kmn/src/compiler/compiler.ts
@@ -6,8 +6,11 @@ TODO: implement additional interfaces:
*/
// TODO: rename wasm-host?
-import { UnicodeSetParser, UnicodeSet, VisualKeyboard, KvkFileReader, KeymanCompiler, KeymanCompilerArtifacts, KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact } from '@keymanapp/common-types';
-import { CompilerCallbacks, CompilerEvent, CompilerOptions, KeymanFileTypes, KvkFileWriter, KvksFileReader } from '@keymanapp/common-types';
+import { VisualKeyboard, KvkFileReader, UnicodeSetParser, UnicodeSet, KeymanFileTypes, KvkFileWriter } from '@keymanapp/common-types';
+import {
+ CompilerCallbacks, CompilerEvent, CompilerOptions, KeymanCompiler, KeymanCompilerArtifacts,
+ KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact, KvksFileReader
+} from '@keymanapp/developer-utils';
import * as Osk from './osk.js';
import loadWasmHost from '../import/kmcmplib/wasm-host.js';
import { KmnCompilerMessages } from './kmn-compiler-messages.js';
diff --git a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts
index d40da3f492..8d2d4c88ea 100644
--- a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts
+++ b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts
@@ -1,6 +1,5 @@
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/common-types";
import { kmnfile } from "../kmw-compiler/compiler-globals.js";
-import { KeymanUrls } from "@keymanapp/developer-utils";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.KmnCompiler;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
diff --git a/developer/src/kmc-kmn/src/kmw-compiler/compiler-globals.ts b/developer/src/kmc-kmn/src/kmw-compiler/compiler-globals.ts
index 520c468b3a..a8c1e1b72c 100644
--- a/developer/src/kmc-kmn/src/kmw-compiler/compiler-globals.ts
+++ b/developer/src/kmc-kmn/src/kmw-compiler/compiler-globals.ts
@@ -1,4 +1,5 @@
-import { KMX, CompilerCallbacks, CompilerOptions } from "@keymanapp/common-types";
+import { KMX } from "@keymanapp/common-types";
+import { CompilerCallbacks, CompilerOptions } from "@keymanapp/developer-utils";
import { KmnCompilerResult } from "../compiler/compiler.js";
export let FTabStop: string;
diff --git a/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler-messages.ts b/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler-messages.ts
index 448b6bd6ac..5170a8d678 100644
--- a/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler-messages.ts
+++ b/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler-messages.ts
@@ -1,5 +1,5 @@
import { KmnCompilerMessages } from "../compiler/kmn-compiler-messages.js";
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageDef as def, CompilerMessageSpec } from "@keymanapp/common-types";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageDef as def, CompilerMessageSpec } from "@keymanapp/developer-utils";
import { kmnfile } from "./compiler-globals.js";
const Namespace = CompilerErrorNamespace.KmwCompiler;
diff --git a/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts b/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts
index 65f2111ecf..5dcde400ea 100644
--- a/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts
+++ b/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts
@@ -1,4 +1,5 @@
-import { KMX, CompilerOptions, CompilerCallbacks, KvkFileReader, VisualKeyboard, KmxFileReader, KvkFile } from "@keymanapp/common-types";
+import { KMX, KvkFileReader, VisualKeyboard, KmxFileReader, KvkFile } from "@keymanapp/common-types";
+import { CompilerOptions, CompilerCallbacks } from "@keymanapp/developer-utils";
import { ExpandSentinel, incxstr, xstrlen } from "./util.js";
import { options, nl, FTabStop, setupGlobals, callbacks, FFix183_LadderLength, FCallFunctions, fk, minimumKeymanVersionToString, isKeyboardVersion10OrLater, isKeyboardVersion17OrLater } from "./compiler-globals.js";
import { JavaScript_ContextMatch, JavaScript_KeyAsString, JavaScript_Name, JavaScript_OutputString, JavaScript_Rules, JavaScript_Shift, JavaScript_ShiftAsString, JavaScript_Store, zeroPadHex } from './javascript-strings.js';
diff --git a/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts b/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts
index 7432204cb1..61d0985f87 100644
--- a/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts
+++ b/developer/src/kmc-kmn/src/kmw-compiler/validate-layout-file.ts
@@ -1,4 +1,5 @@
-import { KMX, TouchLayout, TouchLayoutFileReader, TouchLayoutFileWriter } from "@keymanapp/common-types";
+import { KMX, TouchLayout } from "@keymanapp/common-types";
+import { TouchLayoutFileReader, TouchLayoutFileWriter } from "@keymanapp/developer-utils";
import { callbacks, minimumKeymanVersion, verifyAndSetMinimumRequiredKeymanVersion15,
isKeyboardVersion14OrLater, isKeyboardVersion17OrLater,
verifyAndSetMinimumRequiredKeymanVersion14,
diff --git a/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts b/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts
index 3e9cbaba87..37a58384b2 100644
--- a/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts
+++ b/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts
@@ -229,7 +229,7 @@ async function run_test_keyboard(kmnCompiler: KmnCompiler, id: string):
fs.writeFileSync(filenames.binary, value.actualCode);
}
- assert.deepEqual(value.actual.js, value.expected.js);
+ assert.deepEqual(value.actual.js.replaceAll(/\r\n/g, '\n'), value.expected.js.replaceAll(/\r\n/g, '\n'));
assert.deepEqual(JSON.parse(value.actual.touchLayout), JSON.parse(value.expected.touchLayout));
return value;
diff --git a/developer/src/kmc-kmn/test/kmw/test-kmw-messages.ts b/developer/src/kmc-kmn/test/kmw/test-kmw-messages.ts
index 2021f6787b..20a1a5c83b 100644
--- a/developer/src/kmc-kmn/test/kmw/test-kmw-messages.ts
+++ b/developer/src/kmc-kmn/test/kmw/test-kmw-messages.ts
@@ -4,7 +4,7 @@ import { KmwCompilerMessages } from '../../src/kmw-compiler/kmw-compiler-message
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
import { makePathToFixture } from '../helpers/index.js';
import { KmnCompiler } from '../../src/main.js';
-import { CompilerErrorNamespace } from '@keymanapp/common-types';
+import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
describe('KmwCompilerMessages', function () {
const callbacks = new TestCompilerCallbacks();
diff --git a/developer/src/kmc-kmn/test/test-messages.ts b/developer/src/kmc-kmn/test/test-messages.ts
index ea697a0174..0c3f17a58c 100644
--- a/developer/src/kmc-kmn/test/test-messages.ts
+++ b/developer/src/kmc-kmn/test/test-messages.ts
@@ -4,7 +4,7 @@ import { KmnCompilerMessageRanges, KmnCompilerMessages } from '../src/compiler/k
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
import { makePathToFixture } from './helpers/index.js';
import { KmnCompiler } from '../src/main.js';
-import { CompilerErrorMask, CompilerErrorNamespace } from '@keymanapp/common-types';
+import { CompilerErrorMask, CompilerErrorNamespace } from '@keymanapp/developer-utils';
describe('KmnCompilerMessages', function () {
const callbacks = new TestCompilerCallbacks();
diff --git a/developer/src/kmc-kmn/test/test-wasm-uset.ts b/developer/src/kmc-kmn/test/test-wasm-uset.ts
index 7c25513651..ad67b35246 100644
--- a/developer/src/kmc-kmn/test/test-wasm-uset.ts
+++ b/developer/src/kmc-kmn/test/test-wasm-uset.ts
@@ -3,7 +3,7 @@ import { assert } from 'chai';
import { KmnCompiler } from '../src/main.js';
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
import { KmnCompilerMessages } from '../src/compiler/kmn-compiler-messages.js';
-import { compilerErrorFormatCode } from '@keymanapp/common-types';
+import { compilerErrorFormatCode } from '@keymanapp/developer-utils';
describe('Compiler UnicodeSet function', function() {
it('should fixup "short" \\u{} escapes', function () {
diff --git a/developer/src/kmc-ldml/build.sh b/developer/src/kmc-ldml/build.sh
index 41b5e49478..73a40c79d7 100755
--- a/developer/src/kmc-ldml/build.sh
+++ b/developer/src/kmc-ldml/build.sh
@@ -14,6 +14,7 @@ builder_describe "Keyman kmc Keyboard Compiler module" \
"@/common/web/keyman-version" \
"@/common/web/types" \
"@/developer/src/kmc-kmn" \
+ "@/developer/src/common/web/utils" \
"@/developer/src/common/web/test-helpers" \
"configure" \
"build" \
diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json
index ef1a17bd68..6f65eaf0e0 100644
--- a/developer/src/kmc-ldml/package.json
+++ b/developer/src/kmc-ldml/package.json
@@ -26,9 +26,10 @@
},
"dependencies": {
"@keymanapp/keyman-version": "*",
+ "@keymanapp/developer-utils": "*",
"@keymanapp/kmc-kmn": "*",
"@keymanapp/ldml-keyboard-constants": "*",
- "semver": "^7.5.2"
+ "semver": "^7.5.4"
},
"devDependencies": {
"@keymanapp/developer-test-helpers": "*",
diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts
index 59ed5f9a07..950534e69c 100644
--- a/developer/src/kmc-ldml/src/compiler/compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/compiler.ts
@@ -1,4 +1,10 @@
-import { LDMLKeyboardXMLSourceFileReader, LDMLKeyboard, KMXPlus, CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile, UnicodeSetParser, KeymanCompiler, KeymanCompilerResult, KeymanCompilerArtifacts, defaultCompilerOptions, KMXBuilder, KvkFileWriter, KeymanCompilerArtifactOptional } from '@keymanapp/common-types';
+import { KMXPlus, UnicodeSetParser, KvkFileWriter } from '@keymanapp/common-types';
+import {
+ CompilerCallbacks, KeymanCompiler, KeymanCompilerResult, KeymanCompilerArtifacts,
+ defaultCompilerOptions, LDMLKeyboardXMLSourceFileReader, LDMLKeyboard,
+ LDMLKeyboardTestDataXMLSourceFile, KMXBuilder,
+ KeymanCompilerArtifactOptional
+} from "@keymanapp/developer-utils";
import { LdmlCompilerOptions } from './ldml-compiler-options.js';
import { LdmlCompilerMessages } from './ldml-compiler-messages.js';
import { BkspCompiler, TranCompiler } from './tran.js';
@@ -99,7 +105,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
* @returns false if initialization fails
*/
async init(callbacks: CompilerCallbacks, options: LdmlCompilerOptions): Promise {
- this.options = {...options};
+ this.options = { ...options };
this.callbacks = callbacks;
return true;
}
@@ -178,15 +184,15 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
* @returns true on success
*/
async write(artifacts: LdmlKeyboardCompilerArtifacts): Promise {
- if(artifacts.kmx) {
+ if (artifacts.kmx) {
this.callbacks.fs.writeFileSync(artifacts.kmx.filename, artifacts.kmx.data);
}
- if(artifacts.kvk) {
+ if (artifacts.kvk) {
this.callbacks.fs.writeFileSync(artifacts.kvk.filename, artifacts.kvk.data);
}
- if(artifacts.js) {
+ if (artifacts.js) {
this.callbacks.fs.writeFileSync(artifacts.js.filename, artifacts.js.data);
}
@@ -198,7 +204,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
* Construct or return a UnicodeSetParser, aka KmnCompiler
* @returns the held UnicodeSetParser
*/
- async getUsetParser() : Promise {
+ async getUsetParser(): Promise {
if (this.usetparser === undefined) {
// initialize
const compiler = new KmnCompiler();
@@ -228,14 +234,14 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks);
// load the file from disk into a string
const data = this.callbacks.loadFile(filename);
- if(!data) {
- this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'}));
+ if (!data) {
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({ errorText: 'Unable to read XML file' }));
return null;
}
// parse (load) the string into an object tree
const source = reader.load(data);
- if(!source) {
- this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({errorText: 'Unable to load XML file'}));
+ if (!source) {
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({ errorText: 'Unable to load XML file' }));
return null;
}
try {
@@ -243,8 +249,8 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
if (!reader.validate(source)) {
return null;
}
- } catch(e) {
- this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({errorText: e.toString()}));
+ } catch (e) {
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({ errorText: e.toString() }));
return null;
}
@@ -261,14 +267,14 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
public loadTestData(filename: string): LDMLKeyboardTestDataXMLSourceFile | null {
const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks);
const data = this.callbacks.loadFile(filename);
- if(!data) {
- this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'}));
+ if (!data) {
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({ errorText: 'Unable to read XML file' }));
return null;
}
const source = reader.loadTestData(data);
/* c8 ignore next 4 */
- if(!source) {
- this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({errorText: 'Unable to load XML file'}));
+ if (!source) {
+ this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({ errorText: 'Unable to load XML file' }));
return null;
}
// TODO-LDML: The unboxed data doesn't match the schema anymore. Skipping validation, for now.
@@ -291,10 +297,14 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
}
/**
- * Runs any linter steps
+ * Runs any linter steps, adding hints to the callbacks as needed
* @internal
+ * @returns true unless there was a linter failure.
*/
- private async lint(source: LDMLKeyboardXMLSourceFile, kmx: KMXPlus.KMXPlusFile) : Promise {
+ private async lint(source: LDMLKeyboardXMLSourceFile, kmx: KMXPlus.KMXPlusFile): Promise {
+ if (!kmx || !source) {
+ return false;
+ }
// run each of the linters
for (const linter of this.buildLinters(source, kmx)) {
if (!await linter.lint()) {
@@ -319,12 +329,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
}
// Run the linters
- if (!await this.lint(source, kmx)) {
- return false;
- }
-
- // We are valid if we have a keyboard file at this point.
- return !!kmx;
+ return (await this.lint(source, kmx));
}
/**
@@ -340,8 +345,8 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
const kmx = new KMXPlusFile();
- for(let section of sections) {
- if(!section.validate()) {
+ for (let section of sections) {
+ if (!section.validate()) {
// TODO-LDML: coverage
passed = false;
// We'll keep validating other sections anyway, so we get a full set of
@@ -349,13 +354,13 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
continue;
}
// clone
- const globalSections : DependencySections = Object.assign({}, kmx.kmxplus);
+ const globalSections: DependencySections = Object.assign({}, kmx.kmxplus);
// pre-initialize the usetparser
globalSections.usetparser = await this.getUsetParser();
const dependencies = section.dependencies;
let dependencyProblem = false;
- Object.keys(constants.section).forEach((sectstr : string) => {
- const sectid : SectionIdent = constants.section[sectstr];
+ Object.keys(constants.section).forEach((sectstr: string) => {
+ const sectid: SectionIdent = constants.section[sectstr];
if (dependencies.has(sectid)) {
/* c8 ignore next 4 */
if (!kmx.kmxplus[sectid]) {
@@ -379,12 +384,12 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
}
const sect = section.compile(globalSections);
- if(!sect) {
+ if (!sect) {
passed = false;
continue;
}
/* c8 ignore next 4 */
- if(kmx.kmxplus[section.id]) {
+ if (kmx.kmxplus[section.id]) {
// Internal error useful during section bring-up
throw new Error(`Internal error: section ${section.id} would be assigned twice`);
}
@@ -393,8 +398,8 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
// give all sections a chance to postValidate
if (postValidate) {
- for(let section of sections) {
- if(!section.postValidate(kmx.kmxplus[section.id])) {
+ for (let section of sections) {
+ if (!section.postValidate(kmx.kmxplus[section.id])) {
passed = false;
}
}
diff --git a/developer/src/kmc-ldml/src/compiler/disp.ts b/developer/src/kmc-ldml/src/compiler/disp.ts
index 769a1a1788..03c411ce8d 100644
--- a/developer/src/kmc-ldml/src/compiler/disp.ts
+++ b/developer/src/kmc-ldml/src/compiler/disp.ts
@@ -1,5 +1,6 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { KMXPlus, LDMLKeyboard } from '@keymanapp/common-types';
+import { LDMLKeyboard } from '@keymanapp/developer-utils';
+import { KMXPlus } from "@keymanapp/common-types";
import { LdmlCompilerMessages } from "./ldml-compiler-messages.js";
import { SectionCompiler } from "./section-compiler.js";
diff --git a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
index 78dc771ff6..92c364e4dc 100644
--- a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts
@@ -1,6 +1,7 @@
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
import { SectionCompiler } from "./section-compiler.js";
-import { util, LDMLKeyboard, KMXPlus, CompilerCallbacks, MarkerParser } from "@keymanapp/common-types";
+import { util, KMXPlus, MarkerParser } from "@keymanapp/common-types";
+import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils";
import { VarsCompiler } from './vars.js';
import { LdmlCompilerMessages } from './ldml-compiler-messages.js';
diff --git a/developer/src/kmc-ldml/src/compiler/keymanweb-compiler.ts b/developer/src/kmc-ldml/src/compiler/keymanweb-compiler.ts
index a35739247b..86402b6d45 100644
--- a/developer/src/kmc-ldml/src/compiler/keymanweb-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/keymanweb-compiler.ts
@@ -1,4 +1,5 @@
-import { CompilerCallbacks, VisualKeyboard, LDMLKeyboard, TouchLayoutFileWriter, KeymanFileTypes } from "@keymanapp/common-types";
+import { VisualKeyboard, KeymanFileTypes } from "@keymanapp/common-types";
+import { CompilerCallbacks, LDMLKeyboard, TouchLayoutFileWriter } from "@keymanapp/developer-utils";
import { LdmlCompilerOptions } from "./ldml-compiler-options.js";
import { TouchLayoutCompiler } from "./touch-layout-compiler.js";
import { LdmlKeyboardVisualKeyboardCompiler } from "./visual-keyboard-compiler.js";
diff --git a/developer/src/kmc-ldml/src/compiler/keys.ts b/developer/src/kmc-ldml/src/compiler/keys.ts
index 9e5de13246..e341f3ac87 100644
--- a/developer/src/kmc-ldml/src/compiler/keys.ts
+++ b/developer/src/kmc-ldml/src/compiler/keys.ts
@@ -1,5 +1,6 @@
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
-import { LDMLKeyboard, KMXPlus, Constants } from '@keymanapp/common-types';
+import { KMXPlus, Constants } from '@keymanapp/common-types';
+import { LDMLKeyboard } from '@keymanapp/developer-utils';
import { LdmlCompilerMessages } from './ldml-compiler-messages.js';
import { SectionCompiler } from "./section-compiler.js";
diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
index 2d6aa07415..1b6752bdee 100644
--- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
+++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts
@@ -1,4 +1,5 @@
-import { util, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def } from "@keymanapp/common-types";
+import { util } from "@keymanapp/common-types";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def } from '@keymanapp/developer-utils';
// const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler;
const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler;
const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler;
diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts
index 9ea0f043bb..1b4f72113c 100644
--- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts
+++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-options.ts
@@ -1,4 +1,4 @@
-import { CompilerOptions, LDMLKeyboardXMLSourceFileReaderOptions } from "@keymanapp/common-types";
+import { CompilerOptions, LDMLKeyboardXMLSourceFileReaderOptions } from "@keymanapp/developer-utils";
/**
* @public
diff --git a/developer/src/kmc-ldml/src/compiler/linter.ts b/developer/src/kmc-ldml/src/compiler/linter.ts
index 2a74dfd0b5..f01d4bd841 100644
--- a/developer/src/kmc-ldml/src/compiler/linter.ts
+++ b/developer/src/kmc-ldml/src/compiler/linter.ts
@@ -1,4 +1,5 @@
-import { LDMLKeyboard, KMXPlus, CompilerCallbacks } from "@keymanapp/common-types";
+import { KMXPlus } from "@keymanapp/common-types";
+import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils";
/** newable interface to Linter c'tor */
export type LinterNew = new (source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, kmx: KMXPlus.KMXPlusFile, callbacks: CompilerCallbacks) => Linter;
diff --git a/developer/src/kmc-ldml/src/compiler/loca.ts b/developer/src/kmc-ldml/src/compiler/loca.ts
index 3a46475581..fea8cc4338 100644
--- a/developer/src/kmc-ldml/src/compiler/loca.ts
+++ b/developer/src/kmc-ldml/src/compiler/loca.ts
@@ -1,5 +1,6 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { LDMLKeyboard, KMXPlus } from '@keymanapp/common-types';
+import { KMXPlus } from '@keymanapp/common-types';
+import { LDMLKeyboard } from '@keymanapp/developer-utils';
import { SectionCompiler } from "./section-compiler.js";
import { LdmlCompilerMessages } from "./ldml-compiler-messages.js";
diff --git a/developer/src/kmc-ldml/src/compiler/section-compiler.ts b/developer/src/kmc-ldml/src/compiler/section-compiler.ts
index f2b14e56a3..e33eda1086 100644
--- a/developer/src/kmc-ldml/src/compiler/section-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/section-compiler.ts
@@ -1,4 +1,5 @@
-import { LDMLKeyboard, KMXPlus, CompilerCallbacks } from "@keymanapp/common-types";
+import { KMXPlus } from "@keymanapp/common-types";
+import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils";
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
/** newable interface to SectionCompiler c'tor */
diff --git a/developer/src/kmc-ldml/src/compiler/touch-layout-compiler.ts b/developer/src/kmc-ldml/src/compiler/touch-layout-compiler.ts
index 5c5c7eb194..a36ab9c097 100644
--- a/developer/src/kmc-ldml/src/compiler/touch-layout-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/touch-layout-compiler.ts
@@ -1,4 +1,5 @@
-import { TouchLayout, LDMLKeyboard } from "@keymanapp/common-types";
+import { TouchLayout } from "@keymanapp/common-types";
+import { LDMLKeyboard } from "@keymanapp/developer-utils";
export class TouchLayoutCompiler {
public compileToJavascript(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): TouchLayout.TouchLayoutFile {
diff --git a/developer/src/kmc-ldml/src/compiler/tran.ts b/developer/src/kmc-ldml/src/compiler/tran.ts
index 84a0bc264f..a25cc2cc08 100644
--- a/developer/src/kmc-ldml/src/compiler/tran.ts
+++ b/developer/src/kmc-ldml/src/compiler/tran.ts
@@ -1,5 +1,6 @@
import { constants, SectionIdent } from "@keymanapp/ldml-keyboard-constants";
-import { util, KMXPlus, LDMLKeyboard, CompilerCallbacks, VariableParser, MarkerParser } from '@keymanapp/common-types';
+import { KMXPlus, VariableParser, MarkerParser, util } from '@keymanapp/common-types';
+import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils";
import { SectionCompiler } from "./section-compiler.js";
import Bksp = KMXPlus.Bksp;
diff --git a/developer/src/kmc-ldml/src/compiler/vars.ts b/developer/src/kmc-ldml/src/compiler/vars.ts
index c54081292f..fb9b2f7596 100644
--- a/developer/src/kmc-ldml/src/compiler/vars.ts
+++ b/developer/src/kmc-ldml/src/compiler/vars.ts
@@ -1,6 +1,6 @@
import { SectionIdent, constants } from "@keymanapp/ldml-keyboard-constants";
-import { KMXPlus, LDMLKeyboard, CompilerCallbacks, MarkerParser } from '@keymanapp/common-types';
-import { VariableParser } from '@keymanapp/common-types';
+import { KMXPlus, VariableParser, MarkerParser } from '@keymanapp/common-types';
+import { LDMLKeyboard, CompilerCallbacks } from '@keymanapp/developer-utils';
import { SectionCompiler } from "./section-compiler.js";
import Vars = KMXPlus.Vars;
import StringVarItem = KMXPlus.StringVarItem;
diff --git a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts
index e662b923e4..6182e6e2c8 100644
--- a/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts
+++ b/developer/src/kmc-ldml/src/compiler/visual-keyboard-compiler.ts
@@ -1,4 +1,5 @@
-import { VisualKeyboard, LDMLKeyboard, CompilerCallbacks } from "@keymanapp/common-types";
+import { VisualKeyboard } from "@keymanapp/common-types";
+import { LDMLKeyboard, CompilerCallbacks } from "@keymanapp/developer-utils";
import { KeysCompiler } from "./keys.js";
import { LdmlCompilerMessages } from "./ldml-compiler-messages.js";
diff --git a/developer/src/kmc-ldml/src/util/util.ts b/developer/src/kmc-ldml/src/util/util.ts
index 21409e7299..dbb5dc0531 100644
--- a/developer/src/kmc-ldml/src/util/util.ts
+++ b/developer/src/kmc-ldml/src/util/util.ts
@@ -1,4 +1,4 @@
-import { LDMLKeyboard } from "@keymanapp/common-types";
+import { LDMLKeyboard } from "@keymanapp/developer-utils";
import { constants } from "@keymanapp/ldml-keyboard-constants";
/**
* Verifies that value is an item in the enumeration.
diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts
index 2e9e16ff6a..0285088acc 100644
--- a/developer/src/kmc-ldml/test/helpers/index.ts
+++ b/developer/src/kmc-ldml/test/helpers/index.ts
@@ -5,7 +5,8 @@ import 'mocha';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { SectionCompiler, SectionCompilerNew } from '../../src/compiler/section-compiler.js';
-import { util, KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile, compilerEventFormat, LDMLKeyboard, UnicodeSetParser, CompilerCallbacks } from '@keymanapp/common-types';
+import { util, KMXPlus, UnicodeSetParser, VisualKeyboard } from '@keymanapp/common-types';
+import { CompilerEvent, compilerEventFormat, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboard, } from "@keymanapp/developer-utils";
import { LdmlKeyboardCompiler } from '../../src/main.js'; // make sure main.js compiles
import { assert } from 'chai';
import { KMXPlusMetadataCompiler } from '../../src/compiler/metadata-compiler.js';
diff --git a/developer/src/kmc-ldml/test/test-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-compiler-e2e.ts
index e6bc3f52e2..1f87e55a22 100644
--- a/developer/src/kmc-ldml/test/test-compiler-e2e.ts
+++ b/developer/src/kmc-ldml/test/test-compiler-e2e.ts
@@ -1,7 +1,7 @@
import 'mocha';
import {assert} from 'chai';
import hextobin from '@keymanapp/hextobin';
-import { KMXBuilder } from '@keymanapp/common-types';
+import { KMXBuilder } from '@keymanapp/developer-utils';
import {checkMessages, compileKeyboard, compilerTestCallbacks, compilerTestOptions, makePathToFixture} from './helpers/index.js';
import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js';
diff --git a/developer/src/kmc-ldml/test/test-helpers.ts b/developer/src/kmc-ldml/test/test-helpers.ts
index 23bf3a258a..8a7a6ba542 100644
--- a/developer/src/kmc-ldml/test/test-helpers.ts
+++ b/developer/src/kmc-ldml/test/test-helpers.ts
@@ -1,4 +1,4 @@
-import { CompilerEvent } from '@keymanapp/common-types';
+import { CompilerEvent } from '@keymanapp/developer-utils';
import 'mocha';
import {assert} from 'chai';
import { CompilerEventOrMatch, matchCompilerEvents } from './helpers/index.js';
diff --git a/developer/src/kmc-ldml/test/test-messages.ts b/developer/src/kmc-ldml/test/test-messages.ts
index b13d3bf104..041e1ed214 100644
--- a/developer/src/kmc-ldml/test/test-messages.ts
+++ b/developer/src/kmc-ldml/test/test-messages.ts
@@ -1,7 +1,7 @@
import 'mocha';
import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js';
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
-import { CompilerErrorNamespace } from '@keymanapp/common-types';
+import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
describe('LdmlCompilerMessages', function () {
it('should have a valid LdmlCompilerMessages object', function() {
diff --git a/developer/src/kmc-ldml/test/test-utils.ts b/developer/src/kmc-ldml/test/test-utils.ts
index fcf1f78683..3b89e3c2af 100644
--- a/developer/src/kmc-ldml/test/test-utils.ts
+++ b/developer/src/kmc-ldml/test/test-utils.ts
@@ -5,7 +5,7 @@ import {
isValidEnumValue, calculateUniqueKeys, allUsedKeyIdsInLayers,
translateLayerAttrToModifier, validModifier, verifyValidAndUnique } from '../src/util/util.js';
import { constants } from "@keymanapp/ldml-keyboard-constants";
-import { LDMLKeyboard } from '@keymanapp/common-types';
+import { LDMLKeyboard } from '@keymanapp/developer-utils';
describe('test of util/util.ts', () => {
describe('isValidEnumValue()', () => {
diff --git a/developer/src/kmc-model-info/src/model-info-compiler-messages.ts b/developer/src/kmc-model-info/src/model-info-compiler-messages.ts
index 9a1c5fdbdc..cbf6b57eb9 100644
--- a/developer/src/kmc-model-info/src/model-info-compiler-messages.ts
+++ b/developer/src/kmc-model-info/src/model-info-compiler-messages.ts
@@ -1,4 +1,4 @@
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/common-types";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.ModelInfoCompiler;
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
@@ -44,5 +44,10 @@ export class ModelInfoCompilerMessages {
static ERROR_NoLicenseFound = SevError | 0x0009;
static Error_NoLicenseFound = () => m(this.ERROR_NoLicenseFound,
`No license for the model was found. MIT license is required for publication to Keyman lexical-models repository.`);
+
+ static ERROR_DescriptionIsMissing = SevError | 0x000A;
+ static Error_DescriptionIsMissing = (o:{filename:string}) => m(
+ this.ERROR_DescriptionIsMissing,
+ `The Info.Description field in the package ${def(o.filename)} is required, but is missing or empty.`);
}
diff --git a/developer/src/kmc-model-info/src/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts
index 09e9fc45c8..a5fb62cbcd 100644
--- a/developer/src/kmc-model-info/src/model-info-compiler.ts
+++ b/developer/src/kmc-model-info/src/model-info-compiler.ts
@@ -5,9 +5,9 @@
import { minKeymanVersion } from "./min-keyman-version.js";
import { ModelInfoFile } from "./model-info-file.js";
-import { CompilerCallbacks, CompilerOptions, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KmpJsonFile } from "@keymanapp/common-types";
+import { KmpJsonFile } from "@keymanapp/common-types";
import { ModelInfoCompilerMessages } from "./model-info-compiler-messages.js";
-import { KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils";
+import { CompilerCallbacks, CompilerOptions, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult,KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils";
/* c8 ignore start */
/**
@@ -204,6 +204,9 @@ export class ModelInfoCompiler implements KeymanCompiler {
if(sources.kmpJsonData.info.description?.description) {
model_info.description = sources.kmpJsonData.info.description.description.trim();
+ } else {
+ this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_DescriptionIsMissing({filename:sources.kpsFilename}));
+ return null;
}
// isRTL -- this is a little bit of a heuristic from a compiled .js
diff --git a/developer/src/kmc-model-info/test/test-model-info-compiler-messages.ts b/developer/src/kmc-model-info/test/test-model-info-compiler-messages.ts
index 4dd1de538f..062fc9f3c0 100644
--- a/developer/src/kmc-model-info/test/test-model-info-compiler-messages.ts
+++ b/developer/src/kmc-model-info/test/test-model-info-compiler-messages.ts
@@ -1,7 +1,7 @@
import 'mocha';
import { ModelInfoCompilerMessages } from '../src/model-info-compiler-messages.js';
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
-import { CompilerErrorNamespace } from '@keymanapp/common-types';
+import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
describe('ModelInfoCompilerMessages', function () {
it('should have a valid ModelInfoCompilerMessages object', function() {
diff --git a/developer/src/kmc-model/src/compiler-callbacks.ts b/developer/src/kmc-model/src/compiler-callbacks.ts
index a077035003..401a68f568 100644
--- a/developer/src/kmc-model/src/compiler-callbacks.ts
+++ b/developer/src/kmc-model/src/compiler-callbacks.ts
@@ -1,4 +1,4 @@
-import { CompilerCallbacks } from "@keymanapp/common-types";
+import { CompilerCallbacks } from "@keymanapp/developer-utils";
export let callbacks: CompilerCallbacks;
diff --git a/developer/src/kmc-model/src/lexical-model-compiler.ts b/developer/src/kmc-model/src/lexical-model-compiler.ts
index 9add61ee23..e1071512a9 100644
--- a/developer/src/kmc-model/src/lexical-model-compiler.ts
+++ b/developer/src/kmc-model/src/lexical-model-compiler.ts
@@ -10,7 +10,7 @@ import {decorateWithScriptOverrides} from "./script-overrides-decorator.js";
import { LexicalModelSource, WordBreakerSpec, SimpleWordBreakerSpec } from "./lexical-model.js";
import { ModelCompilerError, ModelCompilerMessageContext, ModelCompilerMessages } from "./model-compiler-messages.js";
import { callbacks, setCompilerCallbacks } from "./compiler-callbacks.js";
-import { CompilerCallbacks, CompilerOptions, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult } from "@keymanapp/common-types";
+import { CompilerCallbacks, CompilerOptions, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult } from "@keymanapp/developer-utils";
/**
* An ECMAScript module as emitted by the TypeScript compiler.
diff --git a/developer/src/kmc-model/src/model-compiler-messages.ts b/developer/src/kmc-model/src/model-compiler-messages.ts
index 26126b5d13..51e4042ea8 100644
--- a/developer/src/kmc-model/src/model-compiler-messages.ts
+++ b/developer/src/kmc-model/src/model-compiler-messages.ts
@@ -1,4 +1,4 @@
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/common-types";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.ModelCompiler;
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
diff --git a/developer/src/kmc-model/test/test-messages.ts b/developer/src/kmc-model/test/test-messages.ts
index df584d80d1..2f87dfd718 100644
--- a/developer/src/kmc-model/test/test-messages.ts
+++ b/developer/src/kmc-model/test/test-messages.ts
@@ -1,7 +1,7 @@
import 'mocha';
import { ModelCompilerMessages } from '../src/model-compiler-messages.js';
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
-import { CompilerErrorNamespace } from '@keymanapp/common-types';
+import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
describe('ModelCompilerMessages', function () {
it('should have a valid ModelCompilerMessages object', function() {
diff --git a/developer/src/kmc-package/build.sh b/developer/src/kmc-package/build.sh
index 27c725a963..0b2d7bda9b 100755
--- a/developer/src/kmc-package/build.sh
+++ b/developer/src/kmc-package/build.sh
@@ -14,6 +14,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
builder_describe "Build Keyman kmc Package Compiler module" \
"@/common/web/keyman-version" \
"@/developer/src/common/web/test-helpers" \
+ "@/developer/src/common/web/utils" \
"configure" \
"build" \
"api analyze API and prepare API documentation" \
diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json
index 22bc7b024d..521ae02e44 100644
--- a/developer/src/kmc-package/package.json
+++ b/developer/src/kmc-package/package.json
@@ -30,6 +30,7 @@
},
"dependencies": {
"@keymanapp/common-types": "*",
+ "@keymanapp/developer-utils": "*",
"jszip": "^3.7.0",
"marked": "^7.0.0"
},
diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts
index b36f2da682..04688d0e5a 100644
--- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts
+++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts
@@ -1,8 +1,9 @@
-import { xml2js } from '@keymanapp/common-types';
+import { xml2js } from '@keymanapp/developer-utils';
import JSZip from 'jszip';
import KEYMAN_VERSION from "@keymanapp/keyman-version";
-import { KmpJsonFile, KpsFile, SchemaValidators, CompilerCallbacks, KeymanFileTypes, KvkFile, KeymanCompiler, CompilerOptions, KeymanCompilerResult, KeymanCompilerArtifacts, KeymanCompilerArtifact } from '@keymanapp/common-types';
+import { KmpJsonFile, SchemaValidators, KeymanFileTypes, KvkFile } from '@keymanapp/common-types';
+import { CompilerCallbacks, KpsFile, KeymanCompiler, CompilerOptions, KeymanCompilerResult, KeymanCompilerArtifacts, KeymanCompilerArtifact } from '@keymanapp/developer-utils';
import { PackageCompilerMessages } from './package-compiler-messages.js';
import { PackageMetadataCollector } from './package-metadata-collector.js';
import { KmpInfWriter } from './kmp-inf-writer.js';
diff --git a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts
index b974996fa6..00d8a380b5 100644
--- a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts
+++ b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts
@@ -1,4 +1,4 @@
-import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/common-types";
+import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.PackageCompiler;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
@@ -138,5 +138,11 @@ export class PackageCompilerMessages {
static ERROR_InvalidAuthorEmail = SevError | 0x0020;
static Error_InvalidAuthorEmail = (o:{email:string}) => m(this.ERROR_InvalidAuthorEmail,
`Invalid author email: ${def(o.email)}`);
+
+ static ERROR_PackageFileHasEmptyVersion = SevError | 0x0021;
+ static Error_PackageFileHasEmptyVersion = () => m(
+ this.ERROR_PackageFileHasEmptyVersion,
+ `Package version is not following keyboard version, but the package version field is blank.`
+ );
}
diff --git a/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts b/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts
index 91a362528d..06f36c7727 100644
--- a/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts
+++ b/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts
@@ -1,4 +1,5 @@
-import { KeymanTargets, CompilerCallbacks, KmpJsonFile } from "@keymanapp/common-types";
+import { KeymanTargets, KmpJsonFile } from "@keymanapp/common-types";
+import { CompilerCallbacks } from "@keymanapp/developer-utils";
import { PackageCompilerMessages } from "./package-compiler-messages.js";
import { KeyboardMetadataCollection } from "./package-metadata-collector.js";
diff --git a/developer/src/kmc-package/src/compiler/package-metadata-collector.ts b/developer/src/kmc-package/src/compiler/package-metadata-collector.ts
index 3c9c2fc7e3..89a8f35b09 100644
--- a/developer/src/kmc-package/src/compiler/package-metadata-collector.ts
+++ b/developer/src/kmc-package/src/compiler/package-metadata-collector.ts
@@ -1,4 +1,5 @@
-import { KmpJsonFile, CompilerCallbacks, KmxFileReader, KmxFileReaderError, KMX, KeymanFileTypes } from '@keymanapp/common-types';
+import { KmpJsonFile, KmxFileReader, KmxFileReaderError, KMX, KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerCallbacks } from "@keymanapp/developer-utils";
import { getCompiledKmxKeyboardMetadata } from './kmx-keyboard-metadata.js';
import { PackageCompilerMessages } from './package-compiler-messages.js';
import { getCompiledWebKeyboardMetadata, WebKeyboardMetadata } from './web-keyboard-metadata.js';
diff --git a/developer/src/kmc-package/src/compiler/package-validation.ts b/developer/src/kmc-package/src/compiler/package-validation.ts
index 1ba9911b31..a55610adad 100644
--- a/developer/src/kmc-package/src/compiler/package-validation.ts
+++ b/developer/src/kmc-package/src/compiler/package-validation.ts
@@ -1,7 +1,7 @@
-import { KmpJsonFile, CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types';
+import { KmpJsonFile, KeymanFileTypes } from '@keymanapp/common-types';
import { PackageCompilerMessages } from './package-compiler-messages.js';
import { keymanEngineForWindowsFiles, keymanForWindowsInstallerFiles, keymanForWindowsRedistFiles } from './redist-files.js';
-import { isValidEmail } from '@keymanapp/developer-utils';
+import { isValidEmail, CompilerCallbacks, CompilerOptions } from '@keymanapp/developer-utils';
// The keyboard ID SHOULD adhere to this pattern:
const KEYBOARD_ID_PATTERN_PACKAGE = /^[a-z_][a-z0-9_]*\.(kps|kmp)$/;
diff --git a/developer/src/kmc-package/src/compiler/package-version-validator.ts b/developer/src/kmc-package/src/compiler/package-version-validator.ts
index 3456253a4f..b20e086aec 100644
--- a/developer/src/kmc-package/src/compiler/package-version-validator.ts
+++ b/developer/src/kmc-package/src/compiler/package-version-validator.ts
@@ -1,6 +1,7 @@
-import { KmpJsonFile, CompilerCallbacks, KpsFile } from '@keymanapp/common-types';
+import { KmpJsonFile } from '@keymanapp/common-types';
import { PackageCompilerMessages } from './package-compiler-messages.js';
import { KeyboardMetadataCollection } from './package-metadata-collector.js';
+import { KpsFile, CompilerCallbacks } from '@keymanapp/developer-utils';
export const DEFAULT_KEYBOARD_VERSION = '1.0';
export const MIN_LM_FILEVERSION_KMP_JSON = '12.0';
@@ -41,6 +42,11 @@ export class PackageVersionValidator {
if(!this.checkFollowKeyboardVersion(kmp)) {
return false;
}
+ } else {
+ if(!kmp.info.version) {
+ this.callbacks.reportMessage(PackageCompilerMessages.Error_PackageFileHasEmptyVersion());
+ return false;
+ }
}
if(!kmp.keyboards) {
diff --git a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts
index a41c86091b..3eefada898 100644
--- a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts
+++ b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts
@@ -11,7 +11,8 @@
*/
import JSZip from 'jszip';
-import { CompilerCallbacks, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KeymanFileTypes, KmpJsonFile, KpsFile } from "@keymanapp/common-types";
+import { KeymanFileTypes, KmpJsonFile } from "@keymanapp/common-types";
+import { CompilerCallbacks, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KpsFile } from '@keymanapp/developer-utils';
import KEYMAN_VERSION from "@keymanapp/keyman-version";
import { KmpCompiler, KmpCompilerOptions } from "./kmp-compiler.js";
import { PackageCompilerMessages } from "./package-compiler-messages.js";
diff --git a/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps b/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps
index e7c4a8b851..b5a74c06ee 100644
--- a/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps
+++ b/developer/src/kmc-package/test/fixtures/absolute_path/source/absolute_path.kps
@@ -17,6 +17,7 @@
Absolute Path
+ 1.0
diff --git a/developer/src/kmc-package/test/fixtures/invalid/error_package_file_has_empty_version.kps b/developer/src/kmc-package/test/fixtures/invalid/error_package_file_has_empty_version.kps
new file mode 100644
index 0000000000..386b4e2e03
--- /dev/null
+++ b/developer/src/kmc-package/test/fixtures/invalid/error_package_file_has_empty_version.kps
@@ -0,0 +1,32 @@
+
+
+
+ 15.0.266.0
+ 7.0
+
+
+
+ Invalid Email Address
+ © 2019 National Research Council Canada
+ Eddie Antonio Santos
+
+
+
+
+ basic.kmx
+ Keyboard Basic
+ 0
+ .kmx
+
+
+
+
+ Basic
+ basic
+ 1.0
+
+ Central Khmer (Khmer, Cambodia)
+
+
+
+
diff --git a/developer/src/kmc-package/test/test-kmp-inf-writer.ts b/developer/src/kmc-package/test/test-kmp-inf-writer.ts
index b49f39311e..f852fc69b8 100644
--- a/developer/src/kmc-package/test/test-kmp-inf-writer.ts
+++ b/developer/src/kmc-package/test/test-kmp-inf-writer.ts
@@ -36,7 +36,7 @@ type InfFile = {[name:string]: Section};
* which never repeats
*/
function parseInf(inf: string): InfFile {
- let items = inf.split('\r\n');
+ let items = inf.replaceAll(/\r\n/g, '\n').split('\n');
let sections: InfFile = {}, newSection: Section = {};
sections[0] = newSection;
for(let item of items) {
diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts
index c80e48d4a6..2d8df44eb5 100644
--- a/developer/src/kmc-package/test/test-messages.ts
+++ b/developer/src/kmc-package/test/test-messages.ts
@@ -4,7 +4,7 @@ import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/
import { PackageCompilerMessages } from '../src/compiler/package-compiler-messages.js';
import { makePathToFixture } from './helpers/index.js';
import { KmpCompiler } from '../src/compiler/kmp-compiler.js';
-import { CompilerErrorNamespace, CompilerOptions } from '@keymanapp/common-types';
+import { CompilerErrorNamespace, CompilerOptions } from '@keymanapp/developer-utils';
const debug = false;
const callbacks = new TestCompilerCallbacks();
@@ -236,4 +236,8 @@ describe('PackageCompilerMessages', function () {
PackageCompilerMessages.ERROR_InvalidAuthorEmail);
});
+ it('should generate ERROR_PackageFileHasEmptyVersion if FollowKeyboardVersion is not present and Version is empty', async function() {
+ await testForMessage(this, ['invalid', 'error_package_file_has_empty_version.kps'],
+ PackageCompilerMessages.ERROR_PackageFileHasEmptyVersion);
+ });
});
diff --git a/developer/src/kmc-package/test/test-package-compiler.ts b/developer/src/kmc-package/test/test-package-compiler.ts
index 3f0e8d28fd..7952e2145a 100644
--- a/developer/src/kmc-package/test/test-package-compiler.ts
+++ b/developer/src/kmc-package/test/test-package-compiler.ts
@@ -209,6 +209,8 @@ describe('KmpCompiler', function () {
kmpJson = kmpCompiler.transformKpsToKmpObject(kpsPath);
});
+ assert.isNotNull(kmpJson);
+
await assert.isNull(kmpCompiler.buildKmpFile(kpsPath, kmpJson));
if(debug) callbacks.printMessages();
diff --git a/developer/src/kmc-package/tsconfig.json b/developer/src/kmc-package/tsconfig.json
index 0ab78a91ff..4fae2b81ec 100644
--- a/developer/src/kmc-package/tsconfig.json
+++ b/developer/src/kmc-package/tsconfig.json
@@ -10,6 +10,7 @@
"src/**/*.ts",
],
"references": [
+ { "path": "../common/web/utils" },
{ "path": "../../../common/web/keyman-version" },
{ "path": "../../../common/web/types" },
]
diff --git a/developer/src/kmc/src/commands/analyze.ts b/developer/src/kmc/src/commands/analyze.ts
index 3c53609179..5085262f07 100644
--- a/developer/src/kmc/src/commands/analyze.ts
+++ b/developer/src/kmc/src/commands/analyze.ts
@@ -3,7 +3,7 @@ import * as path from 'path';
import { Command, Option } from 'commander';
import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js';
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
-import { CompilerCallbacks, CompilerLogLevel } from '@keymanapp/common-types';
+import { CompilerCallbacks, CompilerLogLevel } from '@keymanapp/developer-utils';
import { AnalyzeOskCharacterUse, AnalyzeOskRewritePua } from '@keymanapp/kmc-analyze';
import { BaseOptions } from '../util/baseOptions.js';
import { runOnFiles } from '../util/projectRunner.js';
diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts
index 9669997055..b8d5496c5b 100644
--- a/developer/src/kmc/src/commands/build.ts
+++ b/developer/src/kmc/src/commands/build.ts
@@ -5,7 +5,8 @@ import { buildActivities } from './buildClasses/buildActivities.js';
import { BuildProject } from './buildClasses/BuildProject.js';
import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js';
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
-import { CompilerFileCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerOptions, CompilerFileCallbacks } from '@keymanapp/developer-utils';
import { BaseOptions } from '../util/baseOptions.js';
import { expandFileLists } from '../util/fileLists.js';
import { isProject } from '../util/projectLoader.js';
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildActivity.ts b/developer/src/kmc/src/commands/buildClasses/BuildActivity.ts
index 864611c26d..a8cde224bc 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildActivity.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildActivity.ts
@@ -1,6 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
-import { CompilerCallbacks, CompilerOptions, KeymanCompiler, KeymanFileTypes } from "@keymanapp/common-types";
+import { KeymanFileTypes } from "@keymanapp/common-types";
+import { CompilerCallbacks, CompilerOptions, KeymanCompiler } from "@keymanapp/developer-utils";
import { InfrastructureMessages } from '../../messages/infrastructureMessages.js';
export abstract class BuildActivity {
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts
index e25297c735..4248000c7a 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts
@@ -1,7 +1,8 @@
import * as fs from 'fs';
import * as path from 'path';
import { BuildActivity } from './BuildActivity.js';
-import { CompilerCallbacks, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerCallbacks } from '@keymanapp/developer-utils';
import { KeyboardInfoCompiler } from '@keymanapp/kmc-keyboard-info';
import { loadProject } from '../../util/projectLoader.js';
import { InfrastructureMessages } from '../../messages/infrastructureMessages.js';
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts
index e540aba451..a4d6f45b85 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts
@@ -1,7 +1,8 @@
import * as path from 'path';
import { platform } from 'os';
import { KmnCompiler } from '@keymanapp/kmc-kmn';
-import { CompilerOptions, CompilerCallbacks, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerOptions, CompilerCallbacks } from '@keymanapp/developer-utils';
import { BuildActivity } from './BuildActivity.js';
export class BuildKmnKeyboard extends BuildActivity {
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts
index 0e0cfd9766..6ac98c9b92 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts
@@ -1,5 +1,7 @@
import * as kmcLdml from '@keymanapp/kmc-ldml';
-import { CompilerCallbacks, LDMLKeyboardXMLSourceFileReader, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerOptions, CompilerCallbacks } from '@keymanapp/developer-utils';
+import { LDMLKeyboardXMLSourceFileReader } from '@keymanapp/developer-utils';
import { BuildActivity } from './BuildActivity.js';
import { fileURLToPath } from 'url';
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildModel.ts b/developer/src/kmc/src/commands/buildClasses/BuildModel.ts
index b2afc59294..c01d1adf20 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildModel.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildModel.ts
@@ -1,6 +1,7 @@
import { BuildActivity } from './BuildActivity.js';
import { LexicalModelCompiler } from '@keymanapp/kmc-model';
-import { CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerOptions, CompilerCallbacks } from '@keymanapp/developer-utils';
export class BuildModel extends BuildActivity {
public get name(): string { return 'Lexical model'; }
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts
index 0fca855540..3924309f93 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts
@@ -1,7 +1,8 @@
import * as fs from 'fs';
import * as path from 'path';
import { BuildActivity } from './BuildActivity.js';
-import { CompilerCallbacks, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerCallbacks } from '@keymanapp/developer-utils';
import { ModelInfoCompiler } from '@keymanapp/kmc-model-info';
import { KmpCompiler } from '@keymanapp/kmc-package';
import { loadProject } from '../../util/projectLoader.js';
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildPackage.ts b/developer/src/kmc/src/commands/buildClasses/BuildPackage.ts
index 1bb26b3b7f..8ecc08dace 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildPackage.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildPackage.ts
@@ -1,5 +1,6 @@
import { BuildActivity } from './BuildActivity.js';
-import { CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerOptions, CompilerCallbacks } from '@keymanapp/developer-utils';
import { KmpCompiler } from '@keymanapp/kmc-package';
export class BuildPackage extends BuildActivity {
diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts
index 4a5f9148bc..ee1204a7f0 100644
--- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts
+++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts
@@ -1,6 +1,8 @@
import * as path from 'path';
import * as fs from 'fs';
-import { CompilerCallbacks, CompilerFileCallbacks, KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerFileCallbacks, CompilerCallbacks } from '@keymanapp/developer-utils';
+import { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType } from '@keymanapp/developer-utils';
import { BuildActivity } from './BuildActivity.js';
import { buildActivities, buildKeyboardInfoActivity, buildModelInfoActivity } from './buildActivities.js';
import { InfrastructureMessages } from '../../messages/infrastructureMessages.js';
diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts
index 6e598954c1..c958673c2f 100644
--- a/developer/src/kmc/src/commands/buildTestData/index.ts
+++ b/developer/src/kmc/src/commands/buildTestData/index.ts
@@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as kmcLdml from '@keymanapp/kmc-ldml';
-import { CompilerCallbacks, defaultCompilerOptions, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLSourceFileReader } from '@keymanapp/common-types';
+import { CompilerCallbacks, defaultCompilerOptions, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLSourceFileReader } from '@keymanapp/developer-utils';
import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js';
import { fileURLToPath } from 'url';
import { CommandLineBaseOptions } from 'src/util/baseOptions.js';
diff --git a/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts
index 591dfe2e02..06076db929 100644
--- a/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts
+++ b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
-import { CompilerCallbacks, defaultCompilerOptions } from '@keymanapp/common-types';
+import { CompilerCallbacks, defaultCompilerOptions } from '@keymanapp/developer-utils';
import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js';
import { WindowsPackageInstallerCompiler, WindowsPackageInstallerSources } from '@keymanapp/kmc-package';
import { CommandLineBaseOptions } from 'src/util/baseOptions.js';
diff --git a/developer/src/kmc/src/commands/messageCommand.ts b/developer/src/kmc/src/commands/messageCommand.ts
index 94b95f300f..ee568dad9c 100644
--- a/developer/src/kmc/src/commands/messageCommand.ts
+++ b/developer/src/kmc/src/commands/messageCommand.ts
@@ -1,14 +1,13 @@
import * as fs from 'fs';
import * as path from 'path';
-import { CompilerBaseOptions, CompilerCallbacks, CompilerError, CompilerErrorNamespace, CompilerEvent } from '@keymanapp/common-types';
import { Command, Option } from 'commander';
+import { escapeMarkdownChar, KeymanUrls, CompilerBaseOptions, CompilerCallbacks, CompilerError, CompilerErrorNamespace, CompilerEvent } from '@keymanapp/developer-utils';
import { CompilerMessageSource, messageNamespaceKeys, messageSources } from '../messages/messageNamespaces.js';
import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js';
import { exitProcess } from '../util/sysexits.js';
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
import { CompilerMessageDetail, findMessageDetails, findMessagesById, getMessageIdentifiersSorted } from '../util/extendedCompilerOptions.js';
-import { escapeMarkdownChar, KeymanUrls } from '@keymanapp/developer-utils';
type MessageFormat = 'text'|'markdown'|'json';
diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts
index 7c96917cae..b4d8cebd2e 100644
--- a/developer/src/kmc/src/messages/infrastructureMessages.ts
+++ b/developer/src/kmc/src/messages/infrastructureMessages.ts
@@ -1,4 +1,4 @@
-import { CompilerError, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/common-types";
+import { CompilerError, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.Infrastructure;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
diff --git a/developer/src/kmc/src/messages/messageNamespaces.ts b/developer/src/kmc/src/messages/messageNamespaces.ts
index 2e0bef61b5..fc0e7e4d27 100644
--- a/developer/src/kmc/src/messages/messageNamespaces.ts
+++ b/developer/src/kmc/src/messages/messageNamespaces.ts
@@ -1,4 +1,4 @@
-import { CommonTypesMessages, CompilerErrorNamespace } from '@keymanapp/common-types';
+import { CommonTypesMessages, CompilerErrorNamespace } from '@keymanapp/developer-utils';
import { AnalyzerMessages } from '@keymanapp/kmc-analyze';
import { KeyboardInfoCompilerMessages } from '@keymanapp/kmc-keyboard-info';
import { KmnCompilerMessages, KmwCompilerMessages } from '@keymanapp/kmc-kmn';
diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts
index 446924913a..f94e06c18a 100644
--- a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts
+++ b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts
@@ -6,7 +6,7 @@ import { CompilerCallbacks, CompilerEvent,
compilerLogLevelToSeverity, CompilerErrorSeverity,
CompilerError,
CompilerCallbackOptions,
- CompilerFileCallbacks} from '@keymanapp/common-types';
+ CompilerFileCallbacks} from '@keymanapp/developer-utils';
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
import chalk from 'chalk';
import supportsColor from 'supports-color';
diff --git a/developer/src/kmc/src/util/baseOptions.ts b/developer/src/kmc/src/util/baseOptions.ts
index dd91f0bae6..ab07921dbd 100644
--- a/developer/src/kmc/src/util/baseOptions.ts
+++ b/developer/src/kmc/src/util/baseOptions.ts
@@ -1,4 +1,4 @@
-import { ALL_COMPILER_LOG_FORMATS, ALL_COMPILER_LOG_LEVELS, CompilerLogFormat, CompilerLogLevel } from "@keymanapp/common-types";
+import { ALL_COMPILER_LOG_FORMATS, ALL_COMPILER_LOG_LEVELS, CompilerLogFormat, CompilerLogLevel } from "@keymanapp/developer-utils";
import { Command, Option } from "commander";
/**
diff --git a/developer/src/kmc/src/util/extendedCompilerOptions.ts b/developer/src/kmc/src/util/extendedCompilerOptions.ts
index e9c90e47f0..0d72b5b550 100644
--- a/developer/src/kmc/src/util/extendedCompilerOptions.ts
+++ b/developer/src/kmc/src/util/extendedCompilerOptions.ts
@@ -1,4 +1,4 @@
-import { CompilerCallbacks, CompilerError, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageOverride, CompilerMessageOverrideMap, CompilerOptions } from '@keymanapp/common-types';
+import { CompilerCallbacks, CompilerError, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageOverride, CompilerMessageOverrideMap, CompilerOptions } from '@keymanapp/developer-utils';
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
import { CompilerMessageSource, messageNamespaceKeys, messageSources } from '../messages/messageNamespaces.js';
diff --git a/developer/src/kmc/src/util/fileLists.ts b/developer/src/kmc/src/util/fileLists.ts
index ecd786d0ae..29defb4b8d 100644
--- a/developer/src/kmc/src/util/fileLists.ts
+++ b/developer/src/kmc/src/util/fileLists.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
-import { CompilerCallbacks } from "@keymanapp/common-types";
+import { CompilerCallbacks } from "@keymanapp/developer-utils";
import { InfrastructureMessages } from "../messages/infrastructureMessages.js";
/**
diff --git a/developer/src/kmc/src/util/projectLoader.ts b/developer/src/kmc/src/util/projectLoader.ts
index b26399ee39..04a1af2796 100644
--- a/developer/src/kmc/src/util/projectLoader.ts
+++ b/developer/src/kmc/src/util/projectLoader.ts
@@ -1,7 +1,8 @@
import * as path from 'path';
import * as fs from 'fs';
-import { CompilerCallbacks, KeymanDeveloperProject, KeymanFileTypes, KPJFileReader } from "@keymanapp/common-types";
+import { KeymanFileTypes } from "@keymanapp/common-types";
+import { CompilerCallbacks, KeymanDeveloperProject, KPJFileReader } from "@keymanapp/developer-utils";
import { InfrastructureMessages } from "../messages/infrastructureMessages.js";
export const isProject = (filename: string): boolean =>
diff --git a/developer/src/kmc/src/util/projectRunner.ts b/developer/src/kmc/src/util/projectRunner.ts
index 1f3ffdfcd3..c4186635b7 100644
--- a/developer/src/kmc/src/util/projectRunner.ts
+++ b/developer/src/kmc/src/util/projectRunner.ts
@@ -1,4 +1,5 @@
-import { CompilerCallbacks, KeymanFileTypes } from "@keymanapp/common-types";
+import { KeymanFileTypes } from '@keymanapp/common-types';
+import { CompilerCallbacks } from '@keymanapp/developer-utils';
import { isProject, loadProject } from './projectLoader.js';
async function runProject(callbacks: CompilerCallbacks, filename: string, callback: (filename:string)=>Promise): Promise {
diff --git a/developer/src/kmc/test/test-extendedCompilerOptions.ts b/developer/src/kmc/test/test-extendedCompilerOptions.ts
index f0a304f2f4..adda29dfdb 100644
--- a/developer/src/kmc/test/test-extendedCompilerOptions.ts
+++ b/developer/src/kmc/test/test-extendedCompilerOptions.ts
@@ -3,7 +3,7 @@ import { assert } from 'chai';
import 'mocha';
import { unitTestEndpoints } from '../src/util/extendedCompilerOptions.js';
import { InfrastructureMessages } from '../src/messages/infrastructureMessages.js';
-import { CompilerError, CompilerMessageOverride, CompilerErrorSeverity } from '@keymanapp/common-types';
+import { CompilerError, CompilerMessageOverride, CompilerErrorSeverity } from '@keymanapp/developer-utils';
import { KmnCompilerMessages } from '@keymanapp/kmc-kmn';
interface MessageTest {input: string, result: CompilerMessageOverride};
diff --git a/developer/src/kmc/test/test-infrastructureMessages.ts b/developer/src/kmc/test/test-infrastructureMessages.ts
index bd39a0a65b..37316a8f3c 100644
--- a/developer/src/kmc/test/test-infrastructureMessages.ts
+++ b/developer/src/kmc/test/test-infrastructureMessages.ts
@@ -1,15 +1,16 @@
import 'mocha';
+import * as fs from 'fs';
import { assert } from 'chai';
import { InfrastructureMessages } from '../src/messages/infrastructureMessages.js';
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
import { makePathToFixture } from './helpers/index.js';
import { NodeCompilerCallbacks } from '../src/util/NodeCompilerCallbacks.js';
-import { CompilerErrorNamespace, CompilerEvent, KeymanFileTypes } from '@keymanapp/common-types';
+import { KeymanFileTypes } from '@keymanapp/common-types';
import { unitTestEndpoints } from '../src/commands/build.js';
import { KmnCompilerMessages } from '@keymanapp/kmc-kmn';
import { clearOptions } from '@keymanapp/developer-utils';
import { loadProject } from '../src/util/projectLoader.js';
-import { defaultCompilerOptions, CompilerOptions} from '@keymanapp/common-types';
+import { CompilerErrorNamespace, CompilerEvent, defaultCompilerOptions, CompilerOptions} from '@keymanapp/developer-utils';
import { analyzeUnitTestEndpoints } from '../src/commands/analyze.js';
import { BuildKeyboardInfo } from '../src/commands/buildClasses/BuildKeyboardInfo.js';
import { BuildModelInfo } from '../src/commands/buildClasses/BuildModelInfo.js';
@@ -171,10 +172,19 @@ describe('InfrastructureMessages', function () {
it('should generate HINT_FilenameHasDifferingCase if a referenced file has differing case', async function() {
// This message is generated by NodeCompilerCallbacks, because that's where the filesystem is visible,
// so we can't use our usual testForMessage pattern.
- const ncb = new NodeCompilerCallbacks({logLevel: 'silent'});
- const expectedMessages = [InfrastructureMessages.HINT_FilenameHasDifferingCase];
- ncb.loadFile(makePathToFixture('invalid-keyboards', 'Hint_Filename_Has_Differing_Case.kmn'));
- assertMessagesEqual(ncb.messages, expectedMessages);
+ const filename = makePathToFixture('invalid-keyboards', 'Hint_Filename_Has_Differing_Case.kmn');
+ if(fs.existsSync(filename)) {
+ // The file is actually named 'hint_filename_has_differing_case.kmn', so
+ // if we can see it, then we know we are not on a case-sensitive
+ // filesystem, and so we can test
+ const ncb = new NodeCompilerCallbacks({logLevel: 'silent'});
+ const expectedMessages = [InfrastructureMessages.HINT_FilenameHasDifferingCase];
+ assert.isNotNull(ncb.loadFile(filename), `Expected to load 'hint_filename_has_differing_case.kmn' with mixed case '${filename}'`);
+ assertMessagesEqual(ncb.messages, expectedMessages);
+ } else {
+ // We are on a case-sensitive filesystem, so this hint can never be generated
+ assert.isTrue(true);
+ }
});
// INFO_WarningsHaveFailedBuild
diff --git a/developer/src/kmcmplib/include/kmcompx.h b/developer/src/kmcmplib/include/kmcompx.h
index 7e7b270496..9d7ca75508 100644
--- a/developer/src/kmcmplib/include/kmcompx.h
+++ b/developer/src/kmcmplib/include/kmcompx.h
@@ -3,11 +3,6 @@
// TODO: merge with kmcmplib.h
-typedef wchar_t KMX_WCHART;
-typedef KMX_DWORD * PKMX_DWORD;
-typedef char * PKMX_STR;
-typedef KMX_WCHAR* PKMX_WCHAR ;
-
// TODO: these defines are copied out of unicode.h, because unicode.h still
// has windows-specific types. These should be remerged at a future date
diff --git a/developer/src/kmcmplib/src/CasedKeys.cpp b/developer/src/kmcmplib/src/CasedKeys.cpp
index 0a0508f11a..0883c401ce 100644
--- a/developer/src/kmcmplib/src/CasedKeys.cpp
+++ b/developer/src/kmcmplib/src/CasedKeys.cpp
@@ -1,13 +1,14 @@
#include "pch.h"
+#include "km_u16.h"
+#include "../../../../common/windows/cpp/include/vkeys.h"
+
#include "compfile.h"
#include "kmn_compiler_errors.h"
-#include "../../../../common/windows/cpp/include/vkeys.h"
#include "kmcmplib.h"
#include "CharToKeyConversion.h"
-#include "kmx_u16.h"
#include "xstring.h"
namespace kmcmp {
diff --git a/developer/src/kmcmplib/src/CheckForDuplicates.cpp b/developer/src/kmcmplib/src/CheckForDuplicates.cpp
index 9e5d63b9ef..e0fa0094bc 100644
--- a/developer/src/kmcmplib/src/CheckForDuplicates.cpp
+++ b/developer/src/kmcmplib/src/CheckForDuplicates.cpp
@@ -1,11 +1,11 @@
#include "pch.h"
+#include
#include "compfile.h"
#include
#include "kmcmplib.h"
#include
-#include "kmx_u16.h"
#include
#include "CheckForDuplicates.h"
diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp
index 11c8f4a26f..3893a72855 100644
--- a/developer/src/kmcmplib/src/Compiler.cpp
+++ b/developer/src/kmcmplib/src/Compiler.cpp
@@ -102,7 +102,7 @@
#include "UnreachableRules.h"
#include "CheckForDuplicates.h"
-#include "kmx_u16.h"
+#include "km_u16.h"
/* These macros are adapted from winnt.h and legacy use only */
#define MAKELANGID(p, s) ((((uint16_t)(s)) << 10) | (uint16_t)(p))
@@ -580,7 +580,7 @@ KMX_BOOL ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) {
//swprintf(tstr, "%d", fk->currentGroup);
/* Record a system store for the line number of the begin statement */
//wcscpy(tstr, DEBUGSTORE_MATCH);
- u16sprintf(tstr, _countof(tstr), L"%ls%d ", u16fmt(DEBUGSTORE_MATCH).c_str(), (int) fk->currentGroup);
+ u16sprintf(tstr, _countof(tstr), L"%ls%d ", DEBUGSTORE_MATCH_L, (int) fk->currentGroup);
u16ncat(tstr, gp->szName, _countof(tstr));
AddDebugStore(fk, tstr);
@@ -619,7 +619,7 @@ KMX_BOOL ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) {
{
KMX_WCHAR tstr[128];
/* Record a system store for the line number of the begin statement */
- u16sprintf(tstr, _countof(tstr), L"%ls%d ", u16fmt(DEBUGSTORE_NOMATCH).c_str(), (int) fk->currentGroup);
+ u16sprintf(tstr, _countof(tstr), L"%ls%d ", DEBUGSTORE_NOMATCH_L, (int) fk->currentGroup);
u16ncat(tstr, gp->szName, _countof(tstr));
AddDebugStore(fk, tstr);
}
@@ -683,7 +683,7 @@ KMX_BOOL ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p)
{
KMX_WCHAR tstr[128];
/* Record a system store for the line number of the begin statement */
- u16sprintf(tstr, _countof(tstr), L"%ls%d ", u16fmt(DEBUGSTORE_GROUP).c_str(), fk->cxGroupArray - 1);
+ u16sprintf(tstr, _countof(tstr), L"%ls%d ", DEBUGSTORE_GROUP_L, fk->cxGroupArray - 1);
u16ncat(tstr, gp->szName, _countof(tstr));
AddDebugStore(fk, tstr);
}
@@ -3595,7 +3595,7 @@ KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD File
return KmnCompilerMessages::ERROR_CannotReadBitmapFile;
}
- *FileSize = bufvec.size();
+ *FileSize = static_cast(bufvec.size());
*Buf = new KMX_BYTE[*FileSize];
std::copy(bufvec.begin(), bufvec.end(), *Buf);
@@ -3761,7 +3761,7 @@ void kmcmp::RecordDeadkeyNames(PFILE_KEYBOARD fk)
KMX_DWORD i;
for (i = 0; i < fk->cxDeadKeyArray; i++)
{
- u16sprintf(buf, _countof(buf), L"%ls%d ", u16fmt(DEBUGSTORE_DEADKEY).c_str(), (int)i);
+ u16sprintf(buf, _countof(buf), L"%ls%d ", DEBUGSTORE_DEADKEY_L, (int)i);
u16ncat(buf, fk->dpDeadKeyArray[i].szName, _countof(buf));
AddDebugStore(fk, buf);
@@ -3809,11 +3809,11 @@ bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16)
}
if(hasPreamble(result)) {
- *sz16 = result.size() * 2 - 2;
+ *sz16 = static_cast(result.size()) * 2 - 2;
*tempfile = new KMX_BYTE[*sz16];
memcpy(*tempfile, result.c_str() + 1, *sz16);
} else {
- *sz16 = result.size() * 2;
+ *sz16 = static_cast(result.size()) * 2;
*tempfile = new KMX_BYTE[*sz16];
memcpy(*tempfile, result.c_str(), *sz16);
}
diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp
index 11cbd95e59..ba36802832 100644
--- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp
+++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp
@@ -38,7 +38,7 @@ EXTERN bool kmcmp_CompileKeyboard(
int sz;
std::vector bufvec = kmcmp::loadfileproc(pszInfile, "");
- sz = bufvec.size();
+ sz = static_cast(bufvec.size());
if(!sz) {
ReportCompilerMessage(KmnCompilerMessages::ERROR_InfileNotExist);
return FALSE;
diff --git a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp
index a857b41dce..1ddd45dd3f 100644
--- a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp
+++ b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp
@@ -75,9 +75,16 @@ struct BindingType> {
using ValBinding = BindingType;
using WireType = ValBinding::WireType;
+#if __EMSCRIPTEN_major__ == 3 && __EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 60
+ // emscripten-core/emscripten#21692
+ static WireType toWireType(const std::vector &vec, rvp::default_tag) {
+ return ValBinding::toWireType(val::array(vec), rvp::default_tag{});
+ }
+#else
static WireType toWireType(const std::vector &vec) {
return ValBinding::toWireType(val::array(vec));
}
+#endif
static std::vector fromWireType(WireType value) {
return vecFromJSArray(ValBinding::fromWireType(value));
diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.cpp b/developer/src/kmcmplib/src/NamedCodeConstants.cpp
index 40e47d7a00..9cf322ab72 100644
--- a/developer/src/kmcmplib/src/NamedCodeConstants.cpp
+++ b/developer/src/kmcmplib/src/NamedCodeConstants.cpp
@@ -121,7 +121,7 @@ KMX_BOOL NamedCodeConstants::LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filena
int FileSize;
KMX_BYTE* Buf;
std::vector bufvec = loadfileproc(szNameUtf8, fk->extra->kmnFilename);
- FileSize = bufvec.size();
+ FileSize = static_cast(bufvec.size());
if(!FileSize) {
return FALSE;
}
diff --git a/developer/src/kmcmplib/src/UnreachableRules.cpp b/developer/src/kmcmplib/src/UnreachableRules.cpp
index c84bdfb1f5..40965db4d6 100644
--- a/developer/src/kmcmplib/src/UnreachableRules.cpp
+++ b/developer/src/kmcmplib/src/UnreachableRules.cpp
@@ -18,7 +18,7 @@ namespace kmcmp {
std::wstringstream key;
key << kp->Key << "," << kp->ShiftFlags << ",";
if (kp->dpContext) {
- std::wstring Context_ws = u16fmt((const PKMX_WCHAR) kp->dpContext);
+ std::wstring Context_ws = wstring_from_u16string((const PKMX_WCHAR) kp->dpContext);
key << Context_ws;
}
return key.str();
diff --git a/developer/src/kmcmplib/src/debugstore.h b/developer/src/kmcmplib/src/debugstore.h
index c54e3b45c3..d8bd4643df 100644
--- a/developer/src/kmcmplib/src/debugstore.h
+++ b/developer/src/kmcmplib/src/debugstore.h
@@ -5,16 +5,16 @@
#define DEBUGSTORE_BEGIN u"B"
#define DEBUGSTORE_BEGIN_C u'B'
-#define DEBUGSTORE_MATCH u"M"
-#define DEBUGSTORE_MATCH_C u'M'
+#define DEBUGSTORE_MATCH_U u"M"
+#define DEBUGSTORE_MATCH_L L"M"
-#define DEBUGSTORE_NOMATCH u"N"
-#define DEBUGSTORE_NOMATCH_C u'N'
+#define DEBUGSTORE_NOMATCH_U u"M"
+#define DEBUGSTORE_NOMATCH_L L"M"
-#define DEBUGSTORE_GROUP u"G"
-#define DEBUGSTORE_GROUP_C u'G'
+#define DEBUGSTORE_GROUP_U u"G"
+#define DEBUGSTORE_GROUP_L L"G"
-#define DEBUGSTORE_DEADKEY u"D"
-#define DEBUGSTORE_DEADKEY_C u'D'
+#define DEBUGSTORE_DEADKEY_U u"D"
+#define DEBUGSTORE_DEADKEY_L L"D"
#endif /* DEBUGSTORE_H */
diff --git a/developer/src/kmcmplib/src/kmx_u16.cpp b/developer/src/kmcmplib/src/kmx_u16.cpp
deleted file mode 100644
index 02e59f8c12..0000000000
--- a/developer/src/kmcmplib/src/kmx_u16.cpp
+++ /dev/null
@@ -1,293 +0,0 @@
-
-//#include "../../kmcompx/include/kmcompx.h"
-#include
-#include "kmx_u16.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-//String <- wstring
-std::string string_from_wstring(std::wstring const str) {
- std::wstring_convert, wchar_t> converter;
- return converter.to_bytes(str);
-}
-//wstring <- string
-std::wstring wstring_from_string(std::string const str) {
- std::wstring_convert, wchar_t> converter;
- return converter.from_bytes(str);
-}
-
-//u16String <- string
-std::u16string u16string_from_string(std::string const str) {
- std::wstring_convert, char16_t> converter;
- return converter.from_bytes(str);
-}
-
-//string <- u16string
-std::string string_from_u16string(std::u16string const str) {
- std::wstring_convert, char16_t> converter;
- return converter.to_bytes(str);
-}
-
-// often used with c_str() e.g. u16fmt( DEBUGSTORE_MATCH).c_str()
-// UTF16 (= const char16_t*) -> UTF8 (= std::string) -> UTF16 ( = std::wstring 16 bit)
-std::wstring u16fmt(const KMX_WCHAR * str) {
- std::wstring_convert, wchar_t> convert_wstring;
- std::wstring_convert, char16_t> convert;
-
- // UTF16 (= const char16_t*) -> UTF8 (= std::string) -> UTF16 ( = std::wstring 16 bit)
- std::string utf8str = convert.to_bytes(str); // UTF16 (= const char16_t*) -> UTF8 (= std::string)
- std::wstring wstr = convert_wstring.from_bytes(utf8str); // UTF8 (= std::string) -> UTF16 ( = std::wstring 16 bit)
- return wstr;
-}
-
-void u16sprintf(KMX_WCHAR * dst, const size_t sz, const wchar_t* fmt, ...) {
- // UTF16 (=const wchar_t*) -> -> std::string -> std::u16string -> UTF16 ( = char16_t*)
- wchar_t* wbuf = new wchar_t[sz];
- va_list args;
- va_start(args, fmt);
- vswprintf(wbuf, sz, fmt, args);
- va_end(args);
-
- std::wstring_convert, wchar_t> convert_wstring;
- std::wstring_convert, char16_t> convert;
-
- // UTF16 (=const wchar_t*) -> -> std::string -> std::u16string -> UTF16 ( = char16_t*)
- std::string utf8str = convert_wstring.to_bytes(wbuf); // UTF16 ( = const wchar_t*) -> std::string
- std::u16string u16str = convert.from_bytes(utf8str); // std::string -> std::u16string
- u16ncpy(dst, u16str.c_str(), sz); // std::u16string.c_str() -> char16_t*
- delete[] wbuf;
-}
-
- std::wstring convert_pchar16T_To_wstr(KMX_WCHAR *Name){
- // convert char16_t* -> std::u16string -> std::string -> std::wstring
- // char16_t* -> std::u16string
- std::u16string u16str(Name);
- // std::u16string -> std::string
- std::string stri = string_from_u16string(u16str);
- // std::string -> std::wstring
- std::wstring wstr = wstring_from_string(stri);
- return wstr;
- }
-
-long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base)
-{
- auto s = string_from_u16string(str);
- char* t;
- long int result = strtol(s.c_str(), &t, base);
- if (endptr != nullptr) *endptr = (KMX_WCHAR*)str + (t - s.c_str());
- return result;
-}
-
-std::string toHex(int num1) {
- if (num1 == 0)
- return "0";
- int num = num1;
- std::string s = "";
- while (num) {
- int temp = num % 16;
- if (temp <= 9)
- s += (48 + temp);
- else
- s += (87 + temp);
- num = num / 16;
- }
- std::reverse(s.begin(), s.end());
- return s;
-}
-
-const KMX_WCHAR * u16ncat(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max) {
- KMX_WCHAR* o = dst;
- dst = (KMX_WCHAR*) u16chr(dst, 0);
- //max -= (dst-o);
- while (*src && max > 0) {
- *dst++ = *src++;
- max--;
- }
- if(max > 0)
- *dst = 0;
- return o;
-}
-
-const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name)
-{
- const KMX_WCHAR* cp = NULL;
- cp = u16rchr(Name, '\\');
- if (cp == NULL)
- cp = u16rchr(Name, '/');
- return cp;
-}
-
-KMX_CHAR* strrchr_slash(KMX_CHAR* Name)
-{
- KMX_CHAR* cp = NULL;
- cp = strrchr(Name, '\\');
- if (cp == NULL)
- cp = strrchr(Name, '/');
- return cp;
-}
-
-// u16rchr returns last occurence of ch in p; It returns NULL if ch = '\0' and NULL if ch is not found
-const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch) {
- const KMX_WCHAR* p_end = p + u16len(p) - 1;
-
- if (ch == '\0') return p_end + 1;
- while (p_end >= p) {
- if (*p_end == ch) return p_end;
- p_end--;
- }
- return NULL;
-}
-
-const KMX_WCHAR * u16chr(const KMX_WCHAR *p, KMX_WCHAR ch) {
- while (*p) {
- if (*p == ch) return p;
- p++;
- }
- return ch == 0 ? p : NULL;
-}
-
-const KMX_WCHAR * u16cpy(KMX_WCHAR *dst, const KMX_WCHAR *src) {
- KMX_WCHAR *o = dst;
- while (*src) {
- *dst++ = *src++;
- }
- *dst = 0;
- return o;
-}
-
-const KMX_WCHAR * u16ncpy(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max) {
- KMX_WCHAR *o = dst;
- while (*src && max > 0) {
- *dst++ = *src++;
- max--;
- }
- if(max > 0) {
- *dst = 0;
- }
- return o;
-}
-
-size_t u16len(const KMX_WCHAR *p) {
- int i = 0;
- while (*p) {
- p++;
- i++;
- }
- return i;
-}
-
-int u16cmp(const KMX_WCHAR *p, const KMX_WCHAR *q) {
- while (*p && *q) {
- if (*p != *q) return *p - *q;
- p++;
- q++;
- }
- return *p - *q;
-}
-
-int u16nicmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) {
- while (*p && *q && count) {
- if (toupper(*p) != toupper(*q)) return *p - *q;
- p++;
- q++;
- count--;
- }
- if (count)
- return *p - *q;
- return 0;
-}
-
-int u16icmp(const KMX_WCHAR *p, const KMX_WCHAR *q) {
- while (*p && *q) {
- if (toupper(*p) != toupper(*q)) return *p - *q;
- p++;
- q++;
- }
- return *p - *q;
-}
-
-int u16ncmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) {
- while (*p && *q && count) {
- if (*p != *q) return *p - *q;
- p++;
- q++;
- count--;
- }
- if (count)
- return *p - *q;
- return 0;
-}
-
-KMX_WCHAR * u16tok(KMX_WCHAR *p, const KMX_WCHAR ch, KMX_WCHAR **ctx) {
- if (!p) {
- p = *ctx;
- if (!p) return NULL;
- }
-
- KMX_WCHAR *q = p;
- while (*q && *q != ch) {
- q++;
- }
- if (*q) {
- *q = 0;
- q++;
- while (*q == ch) q++;
- *ctx = q;
- }
- else {
- *ctx = NULL;
- }
- return *p ? p : NULL;
-}
-
-KMX_WCHAR * u16tok(KMX_WCHAR* p, const KMX_WCHAR* delim, KMX_WCHAR** ctx) {
- if (!p) {
- p = *ctx;
- if (!p) return NULL;
- }
-
- KMX_WCHAR * q = p;
- while (*q && !u16chr(delim, *q)) {
- q++;
- }
- if (*q) {
- *q = 0;
- q++;
- while (*q && u16chr(delim, *q)) q++;
- *ctx = q;
- }
- else {
- *ctx = NULL;
- }
- return *p ? p : NULL;
-}
-
-double u16tof( KMX_WCHAR* str)
-{
- double val = 0;
- int offsetdot=0;
- char digit;
-
- PKMX_WCHAR q = (PKMX_WCHAR)u16chr(str, '.');
- size_t pos_dot = (q-str < 0) ? u16len(str) : q-str;
-
- for (size_t i = 0; i < u16len(str); i++)
- {
- digit = static_cast(towupper(*str));
-
- if (i > pos_dot - 1)
- offsetdot = 1;
-
- if (digit != '.')
- val =val+ ((int(digit)) - 48) * pow(10, (pos_dot - 1- i + offsetdot));
-
- str++;
- }
- return val;
-}
diff --git a/developer/src/kmcmplib/src/kmx_u16.h b/developer/src/kmcmplib/src/kmx_u16.h
deleted file mode 100644
index 450c400e2f..0000000000
--- a/developer/src/kmcmplib/src/kmx_u16.h
+++ /dev/null
@@ -1,38 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include "kmcompx.h"
-
-std::string string_from_wstring(std::wstring const str);
-std::wstring wstring_from_string(std::string const str);
-std::u16string u16string_from_string(std::string const str);
-std::string string_from_u16string(std::u16string const str);
-
-std::wstring u16fmt(const KMX_WCHAR * str);
-void u16sprintf(KMX_WCHAR * dst, const size_t sz, const wchar_t* fmt, ...) ;
-
-std::wstring convert_pchar16T_To_wstr(KMX_WCHAR *Name);
-
-size_t u16len(const KMX_WCHAR *p);
-int u16cmp(const KMX_WCHAR *p, const KMX_WCHAR *q);
-int u16icmp(const KMX_WCHAR *p, const KMX_WCHAR *q);
-int u16ncmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count);
-int u16nicmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) ;
-const KMX_WCHAR * u16ncpy(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max);
-const KMX_WCHAR * u16cpy(KMX_WCHAR *dst, const KMX_WCHAR *src);
-const KMX_WCHAR * u16rchr(const KMX_WCHAR *p, KMX_WCHAR ch) ;
-const KMX_WCHAR * u16chr(const KMX_WCHAR *p, KMX_WCHAR ch) ;
-const KMX_WCHAR * u16ncat(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max);
-KMX_WCHAR * u16tok(KMX_WCHAR *p, const KMX_WCHAR ch, KMX_WCHAR **ctx) ;
-KMX_WCHAR * u16tok(KMX_WCHAR* p, const KMX_WCHAR* ch, KMX_WCHAR** ctx) ;
-long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base) ;
-double u16tof( KMX_WCHAR* str);
-
-KMX_CHAR* strrchr_slash(KMX_CHAR* Name);
-const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name);
-
-std::string toHex(int num1);
diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build
index 2b9a431e09..51acf70409 100644
--- a/developer/src/kmcmplib/src/meson.build
+++ b/developer/src/kmcmplib/src/meson.build
@@ -78,13 +78,14 @@ lib = library('kmcmplib',
'cp1252.cpp',
'DeprecationChecks.cpp',
'Edition.cpp',
- 'kmx_u16.cpp',
'NamedCodeConstants.cpp',
'UnreachableRules.cpp',
'uset-api.cpp',
'versioning.cpp',
'virtualcharkeys.cpp',
'xstring.cpp',
+ '../../../../common/cpp/km_u16.cpp',
+ '../../../../common/cpp/utfcodec.cpp',
'../../../../common/windows/cpp/src/ConvertUTF.c',
'../../../../common/windows/cpp/src/crc32.cpp',
'../../../../common/windows/cpp/src/vkeys.cpp',
diff --git a/developer/src/kmcmplib/src/pch.h b/developer/src/kmcmplib/src/pch.h
index 358e28998a..80bb639fb8 100644
--- a/developer/src/kmcmplib/src/pch.h
+++ b/developer/src/kmcmplib/src/pch.h
@@ -4,7 +4,7 @@
#define USE_CHAR16_T
#include
-#include "kmx_u16.h"
+#include
#include
#include "../../../../common/windows/cpp/include/crc32.h"
diff --git a/developer/src/kmcmplib/subprojects/gtest.wrap b/developer/src/kmcmplib/subprojects/gtest.wrap
index adb8a9a6d9..a77eb315bf 100644
--- a/developer/src/kmcmplib/subprojects/gtest.wrap
+++ b/developer/src/kmcmplib/subprojects/gtest.wrap
@@ -4,7 +4,7 @@ source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.
source_filename = gtest-1.14.0.tar.gz
source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7
patch_filename = gtest_1.14.0-2_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-2/get_patch
+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
diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp
index 22b38dccc6..1c20597645 100644
--- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp
+++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp
@@ -1,7 +1,7 @@
#include
+#include
#include "../include/kmcompx.h"
#include "../include/kmcmplibapi.h"
-#include "../src/kmx_u16.h"
#include "../src/compfile.h"
#include "../src/CompilerErrors.h"
#include "../../common/include/kmn_compiler_errors.h"
diff --git a/developer/src/kmcmplib/tests/gtest-kmx_u16-test.cpp b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp
similarity index 94%
rename from developer/src/kmcmplib/tests/gtest-kmx_u16-test.cpp
rename to developer/src/kmcmplib/tests/gtest-km_u16-test.cpp
index aa1dca1de0..b67957bd31 100644
--- a/developer/src/kmcmplib/tests/gtest-kmx_u16-test.cpp
+++ b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp
@@ -1,9 +1,9 @@
#include
-#include "../src/kmx_u16.h"
+#include
+#include
#include "../src/compfile.h"
-#include "../../../../common/include/km_types.h"
-TEST(kmx_u16_Test, u16chr) {
+TEST(km_u16_Test, u16chr) {
KMX_WCHAR str[LINESIZE];
u16cpy(str, u"abc");
@@ -14,7 +14,7 @@ TEST(kmx_u16_Test, u16chr) {
EXPECT_EQ(3, u16chr(str, '\0') - str); // locate null terminator
}
-TEST(kmx_u16_Test, u16chr_compare_to_strchr) {
+TEST(km_u16_Test, u16chr_compare_to_strchr) {
// Compare behaviour of strchr:
char str[LINESIZE];
@@ -26,7 +26,7 @@ TEST(kmx_u16_Test, u16chr_compare_to_strchr) {
EXPECT_EQ(3, strchr(str, '\0') - str); // locate null terminator
}
-TEST(kmx_u16_Test, u16tok_char_delim) {
+TEST(km_u16_Test, u16tok_char_delim) {
// For char delimiter: KMX_WCHAR * u16tok(KMX_WCHAR *p, const KMX_WCHAR ch, KMX_WCHAR **ctx) ;
KMX_WCHAR str[LINESIZE];
@@ -67,7 +67,7 @@ TEST(kmx_u16_Test, u16tok_char_delim) {
EXPECT_TRUE(!u16cmp(u"def", ctx));
}
-TEST(kmx_u16_Test, u16tok_str_delim) {
+TEST(km_u16_Test, u16tok_str_delim) {
// For string delimiter: KMX_WCHAR * u16tok(KMX_WCHAR* p, const KMX_WCHAR* ch, KMX_WCHAR** ctx) ;
KMX_WCHAR str[LINESIZE];
@@ -126,7 +126,7 @@ TEST(kmx_u16_Test, u16tok_str_delim) {
EXPECT_EQ(nullptr, ctx);
}
-TEST(kmx_u16_Test, u16tok_str_compare_to_strtok) {
+TEST(km_u16_Test, u16tok_str_compare_to_strtok) {
// Compare behaviour of strtok:
char str[LINESIZE];
diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build
index a5b6476c88..65915d1e9d 100644
--- a/developer/src/kmcmplib/tests/meson.build
+++ b/developer/src/kmcmplib/tests/meson.build
@@ -168,7 +168,7 @@ gtestcompilertest = executable('gtest-compiler-test', 'gtest-compiler-test.cpp',
test('gtest-compiler-test', gtestcompilertest)
-gtestkmx_u16test = executable('gtest-kmx_u16-test', 'gtest-kmx_u16-test.cpp',
+gtest_km_u16_test = executable('gtest-km_u16-test', 'gtest-km_u16-test.cpp',
cpp_args: defns + flags,
include_directories: inc,
name_suffix: name_suffix,
@@ -177,4 +177,4 @@ gtestkmx_u16test = executable('gtest-kmx_u16-test', 'gtest-kmx_u16-test.cpp',
dependencies: [ icuuc_dep, gtest_dep, gmock_dep ],
)
-test('gtest-kmx_u16-test', gtestkmx_u16test)
+test('gtest-km_u16-test', gtest_km_u16_test)
diff --git a/developer/src/kmcmplib/tests/util_filesystem.cpp b/developer/src/kmcmplib/tests/util_filesystem.cpp
index 7f9edd6b36..0873ff1328 100644
--- a/developer/src/kmcmplib/tests/util_filesystem.cpp
+++ b/developer/src/kmcmplib/tests/util_filesystem.cpp
@@ -117,23 +117,10 @@ FILE* Open_File(const KMX_CHAR* Filename, const KMX_CHAR* mode) {
#endif
};
-FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode) {
-#ifdef _MSC_VER
- std::wstring cpath = Filename; //, cmode = mode;
- std::replace(cpath.begin(), cpath.end(), '/', '\\');
- return _wfsopen(cpath.c_str(), mode, _SH_DENYWR);
-#else
- std::string cpath, cmode;
- cpath = string_from_wstring(Filename);
- cmode = string_from_wstring(mode);
- return fopen_wrapper(cpath.c_str(), cmode.c_str());
-#endif
-};
-
FILE* Open_File(const KMX_WCHAR* Filename, const KMX_WCHAR* mode) {
#ifdef _MSC_VER
- std::wstring cpath = convert_pchar16T_To_wstr((KMX_WCHAR*) Filename);
- std::wstring cmode = convert_pchar16T_To_wstr((KMX_WCHAR*) mode);
+ std::wstring cpath = wstring_from_u16string(Filename);
+ std::wstring cmode = wstring_from_u16string(mode);
std::replace(cpath.begin(), cpath.end(), '/', '\\');
return _wfsopen(cpath.c_str(), cmode.c_str(), _SH_DENYWR);
#else
diff --git a/developer/src/kmcmplib/tests/util_filesystem.h b/developer/src/kmcmplib/tests/util_filesystem.h
index 4c94dae6bf..c35967b05e 100644
--- a/developer/src/kmcmplib/tests/util_filesystem.h
+++ b/developer/src/kmcmplib/tests/util_filesystem.h
@@ -1,12 +1,11 @@
#pragma once
#include
-#include "../src/kmx_u16.h"
+#include
// Opens files on windows and non-windows platforms. Datatypes for Filename and mode must be the same.
// returns FILE* if file could be opened; FILE needs to be closed in calling function
FILE* Open_File(const KMX_CHAR* Filename, const KMX_CHAR* mode);
-FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode);
FILE* Open_File(const KMX_WCHAR* Filename, const KMX_WCHAR* mode);
KMX_BOOL kmcmp_FileExists(const KMX_CHAR *filename);
KMX_BOOL kmcmp_FileExists(const KMX_WCHAR *filename);
diff --git a/developer/src/kmcmplib/wasm.build.linux.in b/developer/src/kmcmplib/wasm.build.linux.in
index c5e61a9bda..27cb7ba68f 100644
--- a/developer/src/kmcmplib/wasm.build.linux.in
+++ b/developer/src/kmcmplib/wasm.build.linux.in
@@ -1,4 +1,4 @@
[binaries]
-c = ['$EMSCRIPTEN_BASE/emcc.py']
-cpp = ['$EMSCRIPTEN_BASE/em++.py']
-ar = ['$EMSCRIPTEN_BASE/emar.py']
+c = ['$EMSCRIPTEN_BASE/emcc']
+cpp = ['$EMSCRIPTEN_BASE/em++']
+ar = ['$EMSCRIPTEN_BASE/emar']
diff --git a/developer/src/kmcmplib/wasm.build.mac.in b/developer/src/kmcmplib/wasm.build.mac.in
index c5e61a9bda..27cb7ba68f 100644
--- a/developer/src/kmcmplib/wasm.build.mac.in
+++ b/developer/src/kmcmplib/wasm.build.mac.in
@@ -1,4 +1,4 @@
[binaries]
-c = ['$EMSCRIPTEN_BASE/emcc.py']
-cpp = ['$EMSCRIPTEN_BASE/em++.py']
-ar = ['$EMSCRIPTEN_BASE/emar.py']
+c = ['$EMSCRIPTEN_BASE/emcc']
+cpp = ['$EMSCRIPTEN_BASE/em++']
+ar = ['$EMSCRIPTEN_BASE/emar']
diff --git a/developer/src/server/build-addins.inc.sh b/developer/src/server/build-addins.inc.sh
index d48d44d1e7..052a24d77b 100644
--- a/developer/src/server/build-addins.inc.sh
+++ b/developer/src/server/build-addins.inc.sh
@@ -41,9 +41,10 @@ do_build_addins() {
# Build node-windows-trayicon
#
- pushd "$KEYMAN_ROOT/node_modules/node-windows-trayicon"
+ pushd "$KEYMAN_ROOT/developer/src/server/src/win32/trayicon/addon-src"
rm -rf build
- npx node-gyp clean configure build --arch=$ARCH --silent
+ npm ci
+ "$KEYMAN_ROOT/node_modules/.bin/node-gyp" clean configure build --arch=$ARCH --silent
cp build/Release/addon.node "$TRAYICON_SRC_TARGET"
cp build/Release/addon.node "$TRAYICON_BIN_TARGET"
popd
@@ -52,9 +53,9 @@ do_build_addins() {
# Build hetrodo-node-hide-console-window-napi
#
- pushd "$KEYMAN_ROOT/node_modules/hetrodo-node-hide-console-window-napi"
+ pushd "$KEYMAN_ROOT/node_modules/node-hide-console-window"
rm -rf build
- npx node-gyp clean configure build --arch=$ARCH --silent
+ "$KEYMAN_ROOT/node_modules/.bin/node-gyp" clean configure build --arch=$ARCH --silent
cp build/Release/node-hide-console-window.node "$HIDECONSOLE_SRC_TARGET"
cp build/Release/node-hide-console-window.node "$HIDECONSOLE_BIN_TARGET"
popd
diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh
index e053d72b96..c73ed853f7 100755
--- a/developer/src/server/build.sh
+++ b/developer/src/server/build.sh
@@ -87,9 +87,16 @@ function build_server() {
# Post build
mkdir -p "$THIS_SCRIPT_PATH/build/src/site/"
- mkdir -p "$THIS_SCRIPT_PATH/build/src/win32/"
cp -r "$THIS_SCRIPT_PATH/src/site/"** "$THIS_SCRIPT_PATH/build/src/site/"
- cp -r "$THIS_SCRIPT_PATH/src/win32/"** "$THIS_SCRIPT_PATH/build/src/win32/"
+
+ mkdir -p "$THIS_SCRIPT_PATH/build/src/win32/"
+ mkdir -p "$THIS_SCRIPT_PATH/build/src/win32/console"
+ mkdir -p "$THIS_SCRIPT_PATH/build/src/win32/trayicon"
+ cp "$THIS_SCRIPT_PATH/src/win32/README.md" "$THIS_SCRIPT_PATH/build/src/win32/"
+ cp "$THIS_SCRIPT_PATH/src/win32/console/node-hide-console-window.node" "$THIS_SCRIPT_PATH/build/src/win32/console/"
+ cp "$THIS_SCRIPT_PATH/src/win32/console/node-hide-console-window.x64.node" "$THIS_SCRIPT_PATH/build/src/win32/console/"
+ cp "$THIS_SCRIPT_PATH/src/win32/trayicon/addon.node" "$THIS_SCRIPT_PATH/build/src/win32/trayicon/"
+ cp "$THIS_SCRIPT_PATH/src/win32/trayicon/addon.x64.node" "$THIS_SCRIPT_PATH/build/src/win32/trayicon/"
replaceVersionStrings "$THIS_SCRIPT_PATH/build/src/site/lib/sentry/init.js.in" "$THIS_SCRIPT_PATH/build/src/site/lib/sentry/init.js"
rm "$THIS_SCRIPT_PATH/build/src/site/lib/sentry/init.js.in"
@@ -114,10 +121,13 @@ function installer_server() {
rm -f node_modules/ngrok/bin/ngrok.exe
popd
- # @keymanapp/keyman-version is required in build/ now but we need to copy it in manually
+ # Dependencies are required in build/ but we need to copy them in manually
mkdir -p "$PRODBUILDTEMP/node_modules/@keymanapp/"
cp -R "$KEYMAN_ROOT/node_modules/@keymanapp/keyman-version/" "$PRODBUILDTEMP/node_modules/@keymanapp/"
cp -R "$KEYMAN_ROOT/node_modules/@keymanapp/developer-utils/" "$PRODBUILDTEMP/node_modules/@keymanapp/"
+ cp -R "$KEYMAN_ROOT/node_modules/@keymanapp/common-types/" "$PRODBUILDTEMP/node_modules/@keymanapp/"
+ cp -R "$KEYMAN_ROOT/node_modules/@keymanapp/ldml-keyboard-constants/" "$PRODBUILDTEMP/node_modules/@keymanapp/"
+ cp -R "$KEYMAN_ROOT/node_modules/eventemitter3/" "$PRODBUILDTEMP/node_modules/"
# We'll build in the $KEYMAN_ROOT/developer/bin/server/ folder
rm -rf "$KEYMAN_ROOT/developer/bin/server/"
@@ -144,8 +154,8 @@ function publish_server() {
builder_run_action clean:server clean_server
builder_run_action configure:server configure_server
-builder_run_action build:server build_server
builder_run_action build:addins build_addins
+builder_run_action build:server build_server
builder_run_action test:server test_server
# builder_run_action test:addins # no op
builder_run_action installer:server installer_server # TODO: rename to install-prep
diff --git a/developer/src/server/package.json b/developer/src/server/package.json
index c4c195ae1b..f3be3425f2 100644
--- a/developer/src/server/package.json
+++ b/developer/src/server/package.json
@@ -16,11 +16,14 @@
"multer": "^1.4.5-lts.1",
"ngrok": "^5.0.0-beta.2",
"open": "^8.4.0",
- "ws": "^8.17.1"
- },
+ "restructure": "^3.0.1",
+ "sax": ">=0.6.0",
+ "semver": "^7.5.4",
+ "ws": "^8.17.1",
+ "xmlbuilder": "~11.0.0"
+},
"optionalDependencies": {
- "hetrodo-node-hide-console-window-napi": "keymanapp/hetrodo-node-hide-console-window-napi#keyman-15.0",
- "node-windows-trayicon": "keymanapp/node-windows-trayicon#keyman-16.0"
+ "node-hide-console-window": "^2.2.0"
},
"devDependencies": {
"@keymanapp/keyman-version": "*",
@@ -32,6 +35,7 @@
"@types/ws": "^8.2.2",
"copyfiles": "^2.4.1",
"mocha": "^10.0.0",
+ "node-gyp": "^10.2.0",
"tsc-watch": "^4.5.0",
"typescript": "^5.4.5"
}
diff --git a/developer/src/server/src/win32/trayicon/README.md b/developer/src/server/src/win32/trayicon/README.md
index 2a6dfcff37..9dbc4b19f5 100644
--- a/developer/src/server/src/win32/trayicon/README.md
+++ b/developer/src/server/src/win32/trayicon/README.md
@@ -8,3 +8,8 @@ bundled libraries target for specific versions of node.
* Tree: https://github.com/mceSystems/node-windows-trayicon/tree/cbd7543ac186ca15ee8d7141aac43f26ceae1655
* License: MIT
* Built from: /developer/src/server/win32/node-windows-trayicon
+
+---
+
+The source for trayicon is currently in addon-src, as the original package is
+unmaintained.
\ No newline at end of file
diff --git a/developer/src/server/src/win32/trayicon/addon-src/.gitignore b/developer/src/server/src/win32/trayicon/addon-src/.gitignore
new file mode 100644
index 0000000000..c6bf5be1c7
--- /dev/null
+++ b/developer/src/server/src/win32/trayicon/addon-src/.gitignore
@@ -0,0 +1,62 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+.vscode
+build/
+test/icon.ico
\ No newline at end of file
diff --git a/developer/src/server/src/win32/trayicon/addon-src/LICENSE b/developer/src/server/src/win32/trayicon/addon-src/LICENSE
new file mode 100644
index 0000000000..454c5bc10a
--- /dev/null
+++ b/developer/src/server/src/win32/trayicon/addon-src/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 mceSystems
+
+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/src/server/src/win32/trayicon/addon-src/README.md b/developer/src/server/src/win32/trayicon/addon-src/README.md
new file mode 100644
index 0000000000..6c429beb59
--- /dev/null
+++ b/developer/src/server/src/win32/trayicon/addon-src/README.md
@@ -0,0 +1,57 @@
+# windows-trayicon
+Native addon to add a windows tray icon with menu, built on windows-native libraries (no .NET dependency)
+
+# Installation
+```
+npm install --save windows-trayicon
+```
+
+# Usage
+```
+const WindowsTrayicon = require("windows-trayicon");
+const path = require("path");
+const fs = require("fs");
+
+const myTrayApp = new WindowsTrayicon({
+ title: "Trayicon Test",
+ icon: path.resolve(__dirname, "icon.ico"),
+ menu: [
+ {
+ id: "item-1-id",
+ caption: "First Item"
+ },
+ {
+ id: "item-2-id",
+ caption: "Second Item"
+ },
+ {
+ id: "item-3-id-exit",
+ caption: "Exit"
+ }
+ ]
+});
+
+myTrayApp.item((id) => {
+ console.log(`Menu id selected=${id}`);
+ switch (id) {
+ case "item-1-id": {
+ console.log("First item selected...");
+ break;
+ }
+ case "item-2-id": {
+ myTrayApp.balloon("Hello There!", "This is my message to you").then(() => {
+ console.log("Balloon clicked");
+ })
+ break;
+ }
+ case "item-3-id-exit": {
+ myTrayApp.exit();
+ process.exit(0)
+ break;
+ }
+ }
+});
+
+process.stdin.resume()
+
+```
\ No newline at end of file
diff --git a/developer/src/server/src/win32/trayicon/addon-src/TrayIcon.cpp b/developer/src/server/src/win32/trayicon/addon-src/TrayIcon.cpp
new file mode 100644
index 0000000000..549ff935e3
--- /dev/null
+++ b/developer/src/server/src/win32/trayicon/addon-src/TrayIcon.cpp
@@ -0,0 +1,512 @@
+/*
+* Copyright (c) Istvan Pasztor
+* This source has been published on www.codeproject.com under the CPOL license.
+*/
+#include "stdafx.h"
+#include "TrayIcon.h"
+#include
+
+#define TRAY_WINDOW_MESSAGE (WM_USER + 100)
+
+namespace
+{
+// A map that never holds allocated memory when it is empty. This map will be created with placement new as a static variable,
+// and its destructor will be never called, and it shouldn't leak memory if it contains no items at program exit.
+// This dirty trick is useful when you create your trayicon object as static. In this case we can not control the
+// order of destruction of this map object and the static trayicon object. However this dirty trick ensures that
+// the map is freed exactly when the destructor of the last static trayicon is unregistering itself.
+class CIdToTrayIconMap
+{
+ public:
+ typedef UINT KeyType;
+ typedef CTrayIcon *ValueType;
+
+ // typedef didn't work with VC++6
+ struct StdMap : public std::map
+ {
+ };
+ typedef StdMap::iterator iterator;
+
+ CIdToTrayIconMap() : m_Empty(true) {}
+ ValueType &operator[](KeyType k)
+ {
+ return GetOrCreateStdMap()[k];
+ }
+ ValueType *find(KeyType k)
+ {
+ if (m_Empty)
+ return false;
+ StdMap::iterator it = GetStdMap().find(k);
+ if (it == GetStdMap().end())
+ return NULL;
+ return &it->second;
+ }
+ int erase(KeyType k)
+ {
+ if (m_Empty)
+ return 0;
+ StdMap &m = GetStdMap();
+ int res = (int)m.erase(k);
+ if (m.empty())
+ {
+ m.~StdMap();
+ m_Empty = true;
+ }
+ return res;
+ }
+ bool empty() const
+ {
+ return m_Empty;
+ }
+ // Call this only when the container is not empty!!!
+ iterator begin()
+ {
+ assert(!m_Empty); // Call this only when the container is not empty!!!
+ return m_Empty ? iterator() : GetStdMap().begin();
+ }
+ // Call this only when the container is not empty!!!
+ iterator end()
+ {
+ assert(!m_Empty); // Call this only when the container is not empty!!!
+ return m_Empty ? iterator() : GetStdMap().end();
+ }
+
+ private:
+ StdMap &GetStdMap()
+ {
+ assert(!m_Empty);
+ return (StdMap &)m_MapBuffer;
+ }
+ StdMap &GetOrCreateStdMap()
+ {
+ if (m_Empty)
+ {
+ new ((void *)&m_MapBuffer) StdMap();
+ m_Empty = false;
+ }
+ return (StdMap &)m_MapBuffer;
+ }
+
+ private:
+ bool m_Empty;
+ char m_MapBuffer[sizeof(StdMap)];
+};
+
+static CIdToTrayIconMap &GetIdToTrayIconMap()
+{
+ // This hack prevents running the destructor of our map, so it isn't problem if someone tries to reach this from a static destructor.
+ // Because of using MyMap this will not cause a memory leak if the user removes all items from the container before exiting.
+ static char id_to_tray_icon_buffer[sizeof(CIdToTrayIconMap)];
+ static bool initialized = false;
+ if (!initialized)
+ {
+ initialized = true;
+ new ((void *)id_to_tray_icon_buffer) CIdToTrayIconMap();
+ }
+ return (CIdToTrayIconMap &)id_to_tray_icon_buffer;
+}
+
+static UINT GetNextTrayIconId()
+{
+ static UINT next_id = 1;
+ return next_id++;
+}
+}
+
+void CallJsWithOptionalString(Napi::Env env, Function callback, Context *context, std::string* data) {
+ if (env != nullptr) {
+ if (callback != nullptr) {
+ if (data) {
+ callback.Call(context->Value(), { String::New(env, *data) });
+ } else {
+ callback.Call(context->Value(), {});
+ }
+
+ }
+ }
+ if (data != nullptr) {
+ delete data;
+ }
+}
+
+static const UINT g_WndMsgTaskbarCreated = RegisterWindowMessage(TEXT("TaskbarCreated"));
+LRESULT CALLBACK CTrayIcon::MessageProcessorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == TRAY_WINDOW_MESSAGE)
+ {
+ if (CTrayIcon **ppIcon = GetIdToTrayIconMap().find((UINT)wParam))
+ (*ppIcon)->OnMessage((UINT)lParam);
+ return 0;
+ }
+ else if (uMsg == g_WndMsgTaskbarCreated)
+ {
+ CIdToTrayIconMap &id_to_tray = GetIdToTrayIconMap();
+ if (!id_to_tray.empty())
+ {
+ for (std::map::const_iterator it = id_to_tray.begin(), eit = id_to_tray.end(); it != eit; ++it)
+ {
+ CTrayIcon *pTrayIcon = it->second;
+ pTrayIcon->OnTaskbarCreated();
+ }
+ }
+ }
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+HWND CTrayIcon::GetMessageProcessorHWND()
+{
+ static HWND hWnd = NULL;
+ if (!hWnd)
+ {
+ static const TCHAR TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME[] = TEXT("TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASS");
+ HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
+
+ WNDCLASSEX wc;
+ wc.cbSize = sizeof(wc);
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
+ wc.hIconSm = NULL;
+ wc.hInstance = hInstance;
+ wc.lpfnWndProc = MessageProcessorWndProc;
+ wc.lpszClassName = TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME;
+ wc.lpszMenuName = NULL;
+ wc.style = 0;
+ if (!RegisterClassEx(&wc))
+ return NULL;
+
+ hWnd = CreateWindowEx(
+ 0,
+ TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME,
+ TEXT("TRAY_ICON_MESSAGE_PROCESSOR_WND"),
+ WS_POPUP,
+ 0, 0, 0, 0,
+ NULL,
+ NULL,
+ hInstance,
+ NULL);
+ }
+ return hWnd;
+}
+
+CTrayIcon::CTrayIcon(const char *name, bool visible, HICON hIcon, bool destroy_icon_in_destructor)
+ : m_Id(GetNextTrayIconId()), m_Name(name), m_hIcon(hIcon), m_Visible(false), m_DestroyIconInDestructor(destroy_icon_in_destructor), m_pOnMessageFunc(NULL), m_pListener(NULL)
+{
+ GetIdToTrayIconMap()[m_Id] = this;
+ SetVisible(visible);
+}
+
+CTrayIcon::~CTrayIcon()
+{
+ SetVisible(false);
+ SetIcon(NULL, m_DestroyIconInDestructor);
+ GetIdToTrayIconMap().erase(m_Id);
+}
+
+HICON CTrayIcon::InternalGetIcon() const
+{
+ return m_hIcon ? m_hIcon : ::LoadIcon(NULL, IDI_APPLICATION);
+}
+
+bool CTrayIcon::AddIcon()
+{
+ NOTIFYICONDATAA data;
+ FillNotifyIconData(data);
+ data.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP;
+ data.uCallbackMessage = TRAY_WINDOW_MESSAGE;
+ data.hIcon = InternalGetIcon();
+
+ size_t tip_len = max(sizeof(data.szTip) - 1, strlen(m_Name.c_str()));
+ memcpy(data.szTip, m_Name.c_str(), tip_len);
+ data.szTip[tip_len] = 0;
+
+ return FALSE != Shell_NotifyIconA(NIM_ADD, &data);
+}
+
+bool CTrayIcon::RemoveIcon()
+{
+ NOTIFYICONDATAA data;
+ FillNotifyIconData(data);
+ return FALSE != Shell_NotifyIconA(NIM_DELETE, &data);
+}
+
+void CTrayIcon::OnTaskbarCreated()
+{
+ if (m_Visible)
+ AddIcon();
+}
+
+void CTrayIcon::SetName(const char *name)
+{
+ m_Name = name;
+ if (m_Visible)
+ {
+ NOTIFYICONDATAA data;
+ FillNotifyIconData(data);
+ data.uFlags |= NIF_TIP;
+
+ size_t tip_len = max(sizeof(data.szTip) - 1, strlen(name));
+ memcpy(data.szTip, name, tip_len);
+ data.szTip[tip_len] = 0;
+
+ Shell_NotifyIconA(NIM_MODIFY, &data);
+ }
+}
+
+bool CTrayIcon::SetVisible(bool visible)
+{
+ if (m_Visible == visible)
+ return true;
+ m_Visible = visible;
+ if (m_Visible)
+ return AddIcon();
+ return RemoveIcon();
+}
+
+void CTrayIcon::SetIcon(HICON hNewIcon, bool destroy_current_icon)
+{
+ if (m_hIcon == hNewIcon)
+ return;
+ if (destroy_current_icon && m_hIcon)
+ DestroyIcon(m_hIcon);
+ m_hIcon = hNewIcon;
+
+ if (m_Visible)
+ {
+ NOTIFYICONDATAA data;
+ FillNotifyIconData(data);
+ data.uFlags |= NIF_ICON;
+ data.hIcon = InternalGetIcon();
+ Shell_NotifyIconA(NIM_MODIFY, &data);
+ }
+}
+
+bool CTrayIcon::ShowBalloonTooltip(const char *title, const char *msg, ETooltipIcon icon)
+{
+#ifndef NOTIFYICONDATA_V2_SIZE
+ return false;
+#else
+ if (!m_Visible)
+ return false;
+
+ NOTIFYICONDATAA data;
+ FillNotifyIconData(data);
+ data.cbSize = NOTIFYICONDATAA_V2_SIZE; // win2k and later
+ data.uFlags |= NIF_INFO;
+ data.dwInfoFlags = icon;
+ data.uTimeout = 10000; // deprecated as of Windows Vista, it has a min(10000) and max(30000) value on previous Windows versions.
+
+ strcpy_s(data.szInfoTitle, title);
+ strcpy_s(data.szInfo, msg);
+
+ return FALSE != Shell_NotifyIconA(NIM_MODIFY, &data);
+#endif
+}
+
+void CTrayIcon::OnMessage(UINT uMsg)
+{
+ if (m_pOnMessageFunc)
+ m_pOnMessageFunc(this, uMsg);
+ if (m_pListener)
+ m_pListener->OnTrayIconMessage(this, uMsg);
+}
+
+void CTrayIcon::FillNotifyIconData(NOTIFYICONDATAA &data)
+{
+ memset(&data, 0, sizeof(data));
+ // the basic functions need only V1
+#ifdef NOTIFYICONDATA_V1_SIZE
+ data.cbSize = NOTIFYICONDATA_V1_SIZE;
+#else
+ data.cbSize = sizeof(data);
+#endif
+ data.hWnd = GetMessageProcessorHWND();
+ assert(data.hWnd);
+ data.uID = m_Id;
+}
+
+void TrayOnMessage(CTrayIcon *pTrayIcon, UINT uMsg)
+{
+ switch (uMsg)
+ {
+ case WM_LBUTTONUP:
+ case WM_RBUTTONUP:
+ ((CTrayIconContainer *)pTrayIcon->GetUserData())->PopupMenu();
+ break;
+
+ case NIN_BALLOONUSERCLICK:
+ ((CTrayIconContainer *)pTrayIcon->GetUserData())->BalloonClick();
+ break;
+ }
+}
+
+HICON GetIconHandle(std::string iconPath)
+{
+ return (HICON)LoadImage(NULL, iconPath.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED);
+}
+
+
+CTrayIconContainer::CTrayIconContainer(const CallbackInfo& info) : ObjectWrap(info), m_OnBalloonClick(nullptr), m_OnMenuItem(nullptr) {}
+
+Object CTrayIconContainer::Init(Napi::Env env, Object exports){
+ Function func =
+ DefineClass(env,
+ "CTrayIconContainer",
+ {
+ InstanceMethod("Start", &CTrayIconContainer::Start),
+ InstanceMethod("SetIconPath", &CTrayIconContainer::SetIconPath),
+ InstanceMethod("SetTitle", &CTrayIconContainer::SetTitle),
+ InstanceMethod("Stop", &CTrayIconContainer::Stop),
+ InstanceMethod("AddMenuItem", &CTrayIconContainer::AddMenuItem),
+ InstanceMethod("OnMenuItem", &CTrayIconContainer::OnMenuItem),
+ InstanceMethod("ShowBalloon", &CTrayIconContainer::ShowBalloon),
+ }
+ );
+
+ FunctionReference* constructor = new FunctionReference();
+ *constructor = Napi::Persistent(func);
+ env.SetInstanceData(constructor);
+
+ exports.Set("CTrayIconContainer", func);
+ return exports;
+}
+
+void CTrayIconContainer::Stop(const CallbackInfo& info)
+{
+ if (m_OnMenuItem) {
+ m_OnMenuItem.Release();
+ m_OnMenuItem = nullptr;
+ }
+ if (m_OnBalloonClick) {
+ m_OnBalloonClick.Release();
+ m_OnBalloonClick = nullptr;
+ }
+ PostThreadMessage(GetThreadId(m_worker->native_handle()), WM_QUIT, NULL, NULL);
+ m_worker->join();
+ delete m_worker;
+ m_worker = nullptr;
+ m_tray.SetVisible(false);
+}
+
+LRESULT CALLBACK pWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+void CTrayIconContainer::SetIconPath(const CallbackInfo& info)
+{
+ std::string iconPath = info[0].As();
+ m_tray.SetIcon(GetIconHandle(iconPath));
+}
+void CTrayIconContainer::SetTitle(const CallbackInfo& info)
+{
+ std::string title = info[0].As();
+ m_tray.SetName(title.c_str());
+}
+void CTrayIconContainer::Start(const CallbackInfo& info)
+{
+ HANDLE ready = CreateEvent(nullptr, true, false, nullptr);
+
+ m_worker = new std::thread([this, ready] {
+ m_tray.SetUserData(this);
+ m_tray.SetVisible(true);
+ m_tray.SetListener(TrayOnMessage);
+
+ SetEvent(ready);
+
+ static const TCHAR *class_name = TEXT("MCE_HWND_MESSAGE");
+ WNDCLASSEX wx = {};
+ wx.cbSize = sizeof(WNDCLASSEX);
+ wx.lpfnWndProc = pWndProc;
+ wx.hInstance = 0;
+ wx.lpszClassName = class_name;
+ RegisterClassEx(&wx);
+ m_hwnd = CreateWindowEx(0, class_name, TEXT("MCE_HWND_MESSAGE"), 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
+
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ DestroyWindow(m_hwnd);
+
+ return 0;
+ });
+
+ WaitForSingleObject(ready, INFINITE);
+ CloseHandle(ready);
+}
+
+void CTrayIconContainer::BalloonClick()
+{
+ m_OnBalloonClick.BlockingCall(nullptr);
+}
+
+void CTrayIconContainer::PopupMenu()
+{
+ POINT pt;
+ if (GetCursorPos(&pt))
+ {
+ HMENU menu = CreatePopupMenu();
+ int i = 1;
+ for (auto const &item : m_menuItems)
+ {
+ if(item.m_caption == "-")
+ {
+ AppendMenuA(menu, MF_SEPARATOR, -1, NULL);
+ }
+ else
+ {
+ AppendMenuA(menu, MF_STRING, i, item.m_caption.c_str());
+ }
+ i++;
+ }
+
+ HWND activeHwnd = GetForegroundWindow();
+ SetForegroundWindow(m_hwnd);
+ UINT cmd = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hwnd, NULL);
+ PostMessage(m_hwnd, WM_NULL, 0, 0);
+ SetForegroundWindow(activeHwnd);
+ if(cmd > 0) {
+ m_OnMenuItem.BlockingCall(new std::string(m_menuItems[cmd-1].m_id));
+ }
+ }
+}
+
+void CTrayIconContainer::AddMenuItem(const CallbackInfo& info)
+{
+ std::string id = info[0].As();
+ std::string caption = info[1].As();
+ m_menuItems.push_back(CTrayIconMenuItem(id, caption));
+}
+
+void CTrayIconContainer::OnMenuItem(const CallbackInfo& info)
+{
+ Function cb = info[0].As();
+ Context *context = new Reference(Persistent(info.This()));
+ if (m_OnMenuItem) {
+ m_OnMenuItem.Release();
+ }
+ m_OnMenuItem = TSFNOptString::New(info.Env(), cb, "wintrayicon_OnMenuItem", 0, 1, context);
+}
+
+void CTrayIconContainer::ShowBalloon(const CallbackInfo& info)
+{
+ std::string title = info[0].As();
+ std::string text = info[1].As();
+ int timeout = info[2].As();
+ Function cb = info[3].As();
+
+ Context *context = new Reference(Persistent(info.This()));
+
+ if (m_OnBalloonClick) {
+ m_OnBalloonClick.Release();
+ }
+
+ m_OnBalloonClick = TSFNOptString::New(info.Env(), cb, "wintrayicon_ShowBalloon", 0, 1, context);
+ m_tray.ShowBalloonTooltip(title.c_str(), text.c_str());
+}
diff --git a/developer/src/server/src/win32/trayicon/addon-src/TrayIcon.h b/developer/src/server/src/win32/trayicon/addon-src/TrayIcon.h
new file mode 100644
index 0000000000..ee0c85561e
--- /dev/null
+++ b/developer/src/server/src/win32/trayicon/addon-src/TrayIcon.h
@@ -0,0 +1,193 @@
+/*
+* Copyright (c) Istvan Pasztor
+* This source has been published on www.codeproject.com under the CPOL license.
+*/
+#ifndef __TRAY_ICON_H__
+#define __TRAY_ICON_H__
+#pragma once
+
+// NOTE: include the following headers in your stdafx.h: