mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-28 00:37:53 +00:00
chore(common): Merge remote-tracking branch 'origin/master' into maint/common/7z-inc-sh
# Keyman Conventional Commit suggestions:
#
# - Link to a Sentry issue with git trailer:
# Fixes: _MODULE_-_ID_
# - Give credit to co-authors:
# Co-authored-by: _Name_ <_email_>
# - Use imperative, present tense ('attach' not 'attaches', 'attached' etc)
# - Don't include a period at the end of the title
# - Always include a blank line before trailers
# - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes
This commit is contained in:
commit
f78a417ba2
76 changed files with 758 additions and 1070 deletions
|
|
@ -1,5 +1,13 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.35 alpha 2025-05-12
|
||||
|
||||
* maint(common): add two more functions to detect if running on CI (#13869)
|
||||
* maint(common): use unique names for Keyman version variables (#13854)
|
||||
* maint(common): use new `builder_is_running_on_*` functions (#13875)
|
||||
* maint(common): remove obsolete code from docker-images `run.sh` (#13879)
|
||||
* chore(mac): remove obsolete key processing code (#13848)
|
||||
|
||||
## 19.0.34 alpha 2025-05-08
|
||||
|
||||
* docs(common): update `builder_echo` documentation for TC blocks (#13855)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.35
|
||||
19.0.36
|
||||
|
|
@ -8,10 +8,10 @@ buildscript {
|
|||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
// sentry-android-gradle-plugin 2.1.5+ requires AGP 7.0
|
||||
classpath 'io.sentry:sentry-android-gradle-plugin:4.6.0'
|
||||
classpath 'name.remal:gradle-plugins:1.5.0'
|
||||
classpath 'name.remal:gradle-plugins:1.9.2'
|
||||
|
||||
// From jcenter() which was deprecated August 2024
|
||||
// https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ builder_parse "$@"
|
|||
if builder_is_debug_build; then
|
||||
builder_heading "### Debug config ####"
|
||||
CONFIG="debug"
|
||||
BUILD_FLAGS="assembleDebug -x lint -x test"
|
||||
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
|
||||
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -20,6 +20,15 @@ android {
|
|||
noCompress "kmp"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.tavultesoft.kmapro"
|
||||
minSdkVersion 21
|
||||
|
|
@ -156,7 +165,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'com.stepstone.stepper:material-stepper:4.3.1'
|
||||
api(name: 'keyman-engine', ext: 'aar')
|
||||
|
|
|
|||
|
|
@ -440,41 +440,41 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_info:
|
||||
showInfo();
|
||||
return true;
|
||||
case R.id.action_share:
|
||||
showShareDialog();
|
||||
return true;
|
||||
// Android Gradle 8.0 no longer declares resources final, so can't use switch statement here
|
||||
if (item.getItemId() == R.id.action_info) {
|
||||
showInfo();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_share) {
|
||||
showShareDialog();
|
||||
return true;
|
||||
/* Disable Web Browser to investigate Google sign-in
|
||||
case R.id.action_web:
|
||||
} else if ((item.getItemId() == R.id.action_web) {
|
||||
showWebBrowser();
|
||||
return true;*/
|
||||
case R.id.action_text_size:
|
||||
showTextSizeDialog();
|
||||
return true;
|
||||
case R.id.action_clear_text:
|
||||
showClearTextDialog();
|
||||
return true;
|
||||
case R.id.action_get_started:
|
||||
showGetStarted();
|
||||
return true;
|
||||
case R.id.action_settings:
|
||||
showSettings();
|
||||
return true;
|
||||
case R.id.action_update_keyboards:
|
||||
KMManager.getUpdateTool().executeOpenUpdates();
|
||||
// Dismiss icon
|
||||
updateUpdateCountIndicator(0);
|
||||
final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards);
|
||||
if (_keyboardupdate != null && _keyboardupdate.isVisible()) {
|
||||
_keyboardupdate.setVisible(false);
|
||||
}
|
||||
} else if (item.getItemId() == R.id.action_text_size) {
|
||||
showTextSizeDialog();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_clear_text){
|
||||
showClearTextDialog();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_get_started) {
|
||||
showGetStarted();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_settings) {
|
||||
showSettings();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_update_keyboards) {
|
||||
KMManager.getUpdateTool().executeOpenUpdates();
|
||||
// Dismiss icon
|
||||
updateUpdateCountIndicator(0);
|
||||
final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards);
|
||||
if (_keyboardupdate != null && _keyboardupdate.isVisible()) {
|
||||
_keyboardupdate.setVisible(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -632,7 +632,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
Intent i = new Intent(this, InfoActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
startActivity(i);
|
||||
overridePendingTransition(android.R.anim.fade_in, R.anim.hold);
|
||||
overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold);
|
||||
}
|
||||
|
||||
private void showWebBrowser() {
|
||||
|
|
@ -643,7 +643,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
Intent i = new Intent(this, WebBrowserActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
startActivity(i);
|
||||
overridePendingTransition(android.R.anim.fade_in, R.anim.hold);
|
||||
overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold);
|
||||
}
|
||||
|
||||
private void showShareDialog() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
plugins {
|
||||
id 'base'
|
||||
id 'com.android.library'
|
||||
id 'name.remal.default-plugins'
|
||||
}
|
||||
|
|
@ -6,10 +7,25 @@ plugins {
|
|||
ext.rootPath = '../../'
|
||||
apply from: "$rootPath/version.gradle"
|
||||
|
||||
base {
|
||||
// Specify library filename keyman-engine-$CONFIG.aar
|
||||
archivesName = "keyman-engine"
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 34
|
||||
namespace "com.keyman.engine"
|
||||
|
||||
buildFeatures {
|
||||
// needed for custom BuildConfigField values in defaultConfig
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
|
|
@ -17,9 +33,6 @@ android {
|
|||
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle but Gradle removes them for libraries
|
||||
buildConfigField "String", "KEYMAN_ENGINE_VERSION_NAME", "\""+KEYMAN_VERSION_NAME+"\""
|
||||
buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\""
|
||||
|
||||
// Sepcify library filename keyman-engine-$CONFIG.aar
|
||||
setProperty("archivesBaseName", "keyman-engine")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -36,10 +49,6 @@ android {
|
|||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
// Allows use of a simulated Android API for tests. (Thanks, roboelectric!)
|
||||
includeAndroidResources = true
|
||||
}
|
||||
unitTests.all {
|
||||
testLogging {
|
||||
// May also add "passed", "skipped".
|
||||
|
|
@ -55,17 +64,17 @@ android {
|
|||
|
||||
dependencies {
|
||||
// 1.6.0-rc01 needed to resolve https://issuetracker.google.com/issues/238425626
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
// material:1.7.0 will need Gradle plugin 7.1.0+
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'commons-io:commons-io:2.16.1'
|
||||
implementation 'io.sentry:sentry-android:7.8.0'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
||||
// Robolectric
|
||||
testImplementation 'androidx.test:core:1.5.0'
|
||||
testImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
testImplementation 'org.robolectric:robolectric:4.10.3'
|
||||
testImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.14.1'
|
||||
testImplementation 'androidx.test:core:1.6.1'
|
||||
|
||||
// Generate QR Codes
|
||||
implementation ('com.github.kenglxn.QRGen:android:3.0.1') {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.keyman.engine.cloud;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
|
|
@ -60,6 +61,7 @@ public class CloudDownloadMgr{
|
|||
* Append downloadreceiver to the main context.
|
||||
* @param aContext the context
|
||||
*/
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
public synchronized void initialize(Context aContext)
|
||||
{
|
||||
if(isInitialized)
|
||||
|
|
@ -73,6 +75,7 @@ public class CloudDownloadMgr{
|
|||
aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||
Context.RECEIVER_EXPORTED);
|
||||
} else {
|
||||
// Needs @SuppressLint UnspecifiedRegisterReceiverFlag
|
||||
aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
// io.sentry:sentry-android-gradle-plugin not available for library project
|
||||
classpath 'io.sentry:sentry-android:7.8.0'
|
||||
classpath 'name.remal:gradle-plugins:1.5.0'
|
||||
classpath 'name.remal:gradle-plugins:1.9.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ builder_parse "$@"
|
|||
if builder_is_debug_build; then
|
||||
builder_heading "### Debug config ####"
|
||||
CONFIG="debug"
|
||||
BUILD_FLAGS="assembleDebug -x lint -x test"
|
||||
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
|
||||
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
|
||||
JUNIT_RESULTS="##teamcity[importData type='junit' path='keyman\android\KMEA\app\build\test-results\testDebugUnitTest\']"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#Wed Apr 30 11:17:37 ICT 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ android {
|
|||
aaptOptions {
|
||||
noCompress "kmp"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
```
|
||||
5. After the `android {}` object, include the following:
|
||||
````gradle
|
||||
|
|
@ -154,7 +158,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api(name: 'keyman-engine', ext: 'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
|||
|
|
@ -18,4 +18,8 @@
|
|||
# org.gradle.parallel=true
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
|||
|
|
@ -18,4 +18,8 @@
|
|||
# org.gradle.parallel=true
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ android {
|
|||
noCompress "kmp"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.keyman.android.tests.keyboardHarness"
|
||||
minSdkVersion 21
|
||||
|
|
@ -42,7 +46,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ ARTIFACT="app-release-unsigned.apk"
|
|||
if builder_is_debug_build; then
|
||||
builder_heading "### Debug config ####"
|
||||
CONFIG="debug"
|
||||
BUILD_FLAGS="assembleDebug -x lint -x test"
|
||||
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
|
||||
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
|
||||
ARTIFACT="app-$CONFIG.apk"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -19,3 +19,6 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"dependencies": {
|
||||
"@keymanapp/common-types": "*",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"fast-xml-parser": "^5.0.9",
|
||||
"fast-xml-parser": "^5.2.2",
|
||||
"path-browserify": "^1.0.1",
|
||||
"restructure": "^3.0.1",
|
||||
"sax": ">=0.6.0",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@
|
|||
|
||||
import { CompilerEvent, CompilerCallbackOptions, CompilerErrorSeverity, CompilerError, CompilerMessageOverrideMap, CompilerErrorMask } from "./compiler-interfaces.js";
|
||||
|
||||
/**
|
||||
* The EventResolver implementation is able to expand error messages,
|
||||
* particularly to change a byte offset into a line number.
|
||||
*/
|
||||
export interface EventResolver {
|
||||
resolve(event: CompilerEvent): void;
|
||||
}
|
||||
|
||||
/** A do-nothing EventResolver. Implementations can use this as their default EventResolver. */
|
||||
export class NullEventResolver implements EventResolver {
|
||||
resolve(event: CompilerEvent): void {
|
||||
// do nothing, this is the null event resolver.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract interface for callbacks, to abstract out file i/o
|
||||
*/
|
||||
|
|
@ -40,6 +55,10 @@ export interface CompilerCallbacks {
|
|||
*/
|
||||
resolveFilename(baseFilename: string, filename: string): string;
|
||||
|
||||
/**
|
||||
* Report a message from the compiler back to the caller.
|
||||
* @param event
|
||||
*/
|
||||
reportMessage(event: CompilerEvent): void;
|
||||
|
||||
debug(msg: string): void;
|
||||
|
|
@ -106,14 +125,64 @@ export interface CompilerFileSystemAsyncCallbacks {
|
|||
resolveFilename(baseFilename: string, filename: string): string;
|
||||
}
|
||||
|
||||
/** a CompilerCallbacks implementation that delegates to a parent */
|
||||
export class DelegatingCompilerCallbacks implements CompilerCallbacks {
|
||||
constructor(protected options: CompilerCallbackOptions, protected parent: CompilerCallbacks) {
|
||||
}
|
||||
|
||||
loadFile(filename: string): Uint8Array {
|
||||
return this.parent.loadFile(filename);
|
||||
}
|
||||
|
||||
fileSize(filename: string): number {
|
||||
return this.parent.fileSize(filename);
|
||||
}
|
||||
|
||||
isDirectory(filename: string): boolean {
|
||||
return this.parent.isDirectory(filename);
|
||||
}
|
||||
|
||||
get path(): CompilerPathCallbacks {
|
||||
return this.parent.path;
|
||||
}
|
||||
|
||||
get fs(): CompilerFileSystemCallbacks {
|
||||
return this.parent.fs;
|
||||
}
|
||||
|
||||
get net(): CompilerNetAsyncCallbacks {
|
||||
return this.parent.net;
|
||||
}
|
||||
|
||||
get fsAsync(): CompilerFileSystemAsyncCallbacks {
|
||||
return this.parent.fsAsync;
|
||||
}
|
||||
|
||||
resolveFilename(baseFilename: string, filename: string): string {
|
||||
return this.parent.resolveFilename(baseFilename, filename);
|
||||
}
|
||||
|
||||
debug(msg: string): void {
|
||||
return this.parent.debug(msg);
|
||||
}
|
||||
|
||||
fileURLToPath(url: string | URL): string {
|
||||
return this.parent.fileURLToPath(url);
|
||||
}
|
||||
|
||||
reportMessage(event: CompilerEvent) {
|
||||
this.parent.reportMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for CompilerCallbacks for a given input file
|
||||
*/
|
||||
export class CompilerFileCallbacks implements CompilerCallbacks {
|
||||
export class CompilerFileCallbacks extends DelegatingCompilerCallbacks {
|
||||
messages: CompilerEvent[] = [];
|
||||
|
||||
constructor(private filename: string, private options: CompilerCallbackOptions, private parent: CompilerCallbacks) {
|
||||
constructor(private filename: string, options: CompilerCallbackOptions, parent: CompilerCallbacks) {
|
||||
super(options, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -170,38 +239,6 @@ export class CompilerFileCallbacks implements CompilerCallbacks {
|
|||
this.messages = [];
|
||||
}
|
||||
|
||||
loadFile(filename: string): Uint8Array {
|
||||
return this.parent.loadFile(filename);
|
||||
}
|
||||
|
||||
fileSize(filename: string): number {
|
||||
return this.parent.fileSize(filename);
|
||||
}
|
||||
|
||||
isDirectory(filename: string): boolean {
|
||||
return this.parent.isDirectory(filename);
|
||||
}
|
||||
|
||||
get path(): CompilerPathCallbacks {
|
||||
return this.parent.path;
|
||||
}
|
||||
|
||||
get fs(): CompilerFileSystemCallbacks {
|
||||
return this.parent.fs;
|
||||
}
|
||||
|
||||
get net(): CompilerNetAsyncCallbacks {
|
||||
return this.parent.net;
|
||||
}
|
||||
|
||||
get fsAsync(): CompilerFileSystemAsyncCallbacks {
|
||||
return this.parent.fsAsync;
|
||||
}
|
||||
|
||||
resolveFilename(baseFilename: string, filename: string): string {
|
||||
return this.parent.resolveFilename(baseFilename, filename);
|
||||
}
|
||||
|
||||
reportMessage(event: CompilerEvent): void {
|
||||
const disable = CompilerFileCallbacks.applyMessageOverridesToEvent(event, this.options.messageOverrides);
|
||||
this.messages.push(event);
|
||||
|
|
@ -209,14 +246,6 @@ export class CompilerFileCallbacks implements CompilerCallbacks {
|
|||
this.parent.reportMessage({ filename: this.filename, ...event });
|
||||
}
|
||||
}
|
||||
|
||||
debug(msg: string): void {
|
||||
return this.parent.debug(msg);
|
||||
}
|
||||
|
||||
fileURLToPath(url: string | URL): string {
|
||||
return this.parent.fileURLToPath(url);
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultCompilerFileSystemAsyncCallbacks implements CompilerFileSystemAsyncCallbacks {
|
||||
|
|
@ -242,3 +271,15 @@ export class DefaultCompilerFileSystemAsyncCallbacks implements CompilerFileSyst
|
|||
return this.owner.resolveFilename(baseFilename, filename);
|
||||
}
|
||||
}
|
||||
|
||||
/** a CompilerCallbacks that applies the EventResolver to any reported message */
|
||||
export class ResolvingCompilerCallbacks extends DelegatingCompilerCallbacks {
|
||||
constructor(private eventResolver: EventResolver, options: CompilerCallbackOptions, parent: CompilerCallbacks) {
|
||||
super(options, parent);
|
||||
}
|
||||
|
||||
reportMessage(event: CompilerEvent) {
|
||||
this.eventResolver.resolve(event);
|
||||
this.parent.reportMessage(event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,17 @@ import { CompilerCallbacks } from "./compiler-callbacks.js";
|
|||
*/
|
||||
export interface CompilerEvent {
|
||||
filename?: string;
|
||||
/** line where a message applies */
|
||||
line?: number;
|
||||
/**
|
||||
* column where a message applies.
|
||||
*/
|
||||
column?: number;
|
||||
/**
|
||||
* offset where a message applies.
|
||||
* If set, encompasses line and column.
|
||||
*/
|
||||
offset?: number;
|
||||
code: number;
|
||||
message: string;
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ export {
|
|||
CompilerPathCallbacks,
|
||||
CompilerFileSystemCallbacksFolderEntry as FileSystemFolderEntry,
|
||||
DefaultCompilerFileSystemAsyncCallbacks,
|
||||
EventResolver,
|
||||
NullEventResolver,
|
||||
DelegatingCompilerCallbacks,
|
||||
ResolvingCompilerCallbacks,
|
||||
} from './compiler-callbacks.js';
|
||||
|
||||
export { defaultCompilerOptions, CompilerBaseOptions, CompilerOptions, CompilerEvent, CompilerErrorNamespace,
|
||||
|
|
@ -63,8 +67,9 @@ export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.j
|
|||
|
||||
export { DeveloperUtilsMessages } from './developer-utils-messages.js';
|
||||
export * as SourceFilenamePatterns from './source-filename-patterns.js';
|
||||
export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader } from './xml-utils.js';
|
||||
|
||||
export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL } from './xml-utils.js';
|
||||
export { SymbolUtils } from './symbol-utils.js';
|
||||
export * as LineUtils from './line-utils.js';
|
||||
export * as GitHubUrls from './github-urls.js';
|
||||
export * as CloudUrls from './cloud-urls.js';
|
||||
|
||||
|
|
@ -72,4 +77,4 @@ export { getFontFamily, getFontFamilySync } from './font-family.js';
|
|||
|
||||
export * as ValidIds from './valid-ids.js';
|
||||
|
||||
export * as ProjectLoader from './project-loader.js';
|
||||
export * as ProjectLoader from './project-loader.js';
|
||||
|
|
|
|||
124
developer/src/common/web/utils/src/line-utils.ts
Normal file
124
developer/src/common/web/utils/src/line-utils.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by srl on 2025-04-03
|
||||
*
|
||||
* Abstraction for line number processing
|
||||
*/
|
||||
|
||||
import { CompilerEvent, EventResolver } from "./index.js";
|
||||
|
||||
/** line number with an optional column number */
|
||||
export interface LineColumn {
|
||||
line: number;
|
||||
column?: number;
|
||||
}
|
||||
|
||||
export type LineList = number[];
|
||||
|
||||
/** a LineFinder is able to convert from an offset to a line/column */
|
||||
export class LineFinder {
|
||||
private list: LineList;
|
||||
constructor(public text: string) {
|
||||
}
|
||||
|
||||
public getLineList(): LineList {
|
||||
if (!this.list) {
|
||||
this.list = LineFinder.textToLines(this.text);
|
||||
}
|
||||
return this.list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an offset into line:column
|
||||
* @param offset input offset into the text
|
||||
* @returns line:column information
|
||||
*/
|
||||
public findOffset(offset: number): LineColumn {
|
||||
return LineFinder.offsetToLineColumn(offset, this.getLineList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess text to turn it into arrays of line lengths.
|
||||
* This is effectively a 1-based line length, since line 0 has length of
|
||||
* 0.
|
||||
* Note that the fast-xml-parser converts \r\n to \n before processing,
|
||||
* and this function does as well. So, any offsets passed in will need to
|
||||
* have that taken into consideration.
|
||||
*/
|
||||
public static textToLines(text: string): LineList {
|
||||
return [
|
||||
0, // "line 0" is empty
|
||||
...text.replaceAll("\r\n", "\n").split("\n")
|
||||
.map(l => l.length + 1) // line length (counting the trailing newline)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a line number array to a line/column.
|
||||
* Note that the fast-xml-parser converts \r\n to \n before processing,
|
||||
* and this function does as well. So, any offsets passed in will need to
|
||||
* have that taken into consideration.
|
||||
*/
|
||||
public static offsetToLineColumn(offset: number, list: LineList): LineColumn {
|
||||
for (let line = 1; line < list.length; line++) { // 1-based (assume the first row is 0)
|
||||
if (list[line] > offset) {
|
||||
return { line, column: offset };
|
||||
}
|
||||
offset = offset - (list[line]); // count newline at end
|
||||
}
|
||||
// default: line 0, error
|
||||
return { line: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a class which can receive file contents,
|
||||
* keyed by the filename
|
||||
*/
|
||||
export interface FileConsumer {
|
||||
/**
|
||||
* @param filename name of the file
|
||||
* @param contents string contents of the file
|
||||
*/
|
||||
addFile(filename: string, contents: string): void;
|
||||
}
|
||||
|
||||
/** Cache of LineFinder elements, organized by filename */
|
||||
export class LineFinderCache implements FileConsumer {
|
||||
contentsCache: Map<string, LineFinder> = new Map();
|
||||
|
||||
/** add or update a source file */
|
||||
addFile(filename: string, contents: string): void {
|
||||
this.contentsCache.set(filename, new LineFinder(contents));
|
||||
}
|
||||
|
||||
getByFilename(filename: string): LineFinder | undefined {
|
||||
const lf = this.contentsCache.get(filename);
|
||||
return lf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This EventResolver expands events that have a filename and offset set,
|
||||
* by looking up the event.offset in the LineFinder list.
|
||||
* The expanded event has the event.line and event.column fields set.
|
||||
*/
|
||||
export class LineFinderEventResolver implements EventResolver, FileConsumer {
|
||||
private lfcache = new LineFinderCache();
|
||||
|
||||
addFile(filename: string, contents: string): void {
|
||||
this.lfcache.addFile(filename, contents);
|
||||
}
|
||||
|
||||
/** resolve a CompilerEvent by expanding the line numbers */
|
||||
resolve(event: CompilerEvent) {
|
||||
if (event.offset && !event.line && event.filename) {
|
||||
const lf = this.lfcache.getByFilename(event.filename);
|
||||
const loc = lf.findOffset(event.offset);
|
||||
event.line = loc.line;
|
||||
event.column = loc.column;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
33
developer/src/common/web/utils/src/symbol-utils.ts
Normal file
33
developer/src/common/web/utils/src/symbol-utils.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by srl on 2025-03-27
|
||||
*
|
||||
* Utilities for manipulating Symbol properties
|
||||
*/
|
||||
|
||||
export class SymbolUtils {
|
||||
/**
|
||||
* Copy symbols shallowly from 'from' onto 'onto'
|
||||
* @param onto object to copy onto
|
||||
* @param from source for symbols
|
||||
* @returns the onto object
|
||||
*/
|
||||
public static copySymbols<T>(onto: T, from: any): T {
|
||||
const o = onto as any;
|
||||
for (const sym of Object.getOwnPropertySymbols(from)) {
|
||||
o[sym] = from[sym];
|
||||
}
|
||||
return onto;
|
||||
}
|
||||
|
||||
/** use Object.entries to remove all symbols, recursively. */
|
||||
public static removeSymbols<T>(from: T): T {
|
||||
if (Array.isArray(from)) {
|
||||
return from.map(o => SymbolUtils.removeSymbols(o)) as T;
|
||||
}
|
||||
if (typeof from !== "object") return from;
|
||||
return Object.fromEntries(Object.entries(from).map(([k, v]) => ([k, SymbolUtils.removeSymbols(v)]))) as T;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,12 +5,14 @@
|
|||
*/
|
||||
import { SchemaValidators, util } from '@keymanapp/common-types';
|
||||
import { DeveloperUtilsMessages } from '../../developer-utils-messages.js';
|
||||
import { CompilerCallbacks } from "../../compiler-callbacks.js";
|
||||
import { CompilerCallbacks, EventResolver } from "../../compiler-callbacks.js";
|
||||
import { CompilerEvent } from "../../compiler-interfaces.js";
|
||||
import { LDMLKeyboardXMLSourceFile, LKImport, ImportStatus } from './ldml-keyboard-xml.js';
|
||||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js';
|
||||
import { KeymanXMLReader } from '@keymanapp/developer-utils';
|
||||
import boxXmlArray = util.boxXmlArray;
|
||||
import { LineFinderEventResolver } from '../../line-utils.js';
|
||||
import { XML_FILENAME_SYMBOL, KeymanXMLReader } from '../../xml-utils.js';
|
||||
|
||||
interface NameAndProps {
|
||||
'$'?: any; // content
|
||||
|
|
@ -25,28 +27,44 @@ export class LDMLKeyboardXMLSourceFileReaderOptions {
|
|||
localImportsPaths: string[];
|
||||
};
|
||||
|
||||
export class LDMLKeyboardXMLSourceFileReader {
|
||||
export class LDMLKeyboardXMLSourceFileReader implements EventResolver {
|
||||
/** for resolving messages involving line numbers */
|
||||
private eventResolver: LineFinderEventResolver = new LineFinderEventResolver();
|
||||
|
||||
constructor(private options: LDMLKeyboardXMLSourceFileReaderOptions, private callbacks : CompilerCallbacks) {
|
||||
}
|
||||
resolve(event: CompilerEvent): void {
|
||||
this.eventResolver.resolve(event);
|
||||
}
|
||||
|
||||
static get defaultImportsURL(): [string,string] {
|
||||
return ['../import/', import.meta.url];
|
||||
}
|
||||
|
||||
readImportFile(version: string, subpath: string): Uint8Array {
|
||||
const importPath = this.callbacks.resolveFilename(this.options.cldrImportsPath, `${version}/${subpath}`);
|
||||
return this.callbacks.loadFile(importPath);
|
||||
/** bottleneck for reading keyboard XML files */
|
||||
readFile(path: string): Uint8Array {
|
||||
const data = this.callbacks.loadFile(path);
|
||||
if (data) {
|
||||
this.eventResolver.addFile(path, new TextDecoder().decode(data));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
readLocalImportFile(path: string): Uint8Array {
|
||||
/** @returns [data, filename] */
|
||||
readImportFile(version: string, subpath: string): [Uint8Array, string] {
|
||||
const importPath = this.callbacks.resolveFilename(this.options.cldrImportsPath, `${version}/${subpath}`);
|
||||
return [this.readFile(importPath), importPath];
|
||||
}
|
||||
|
||||
readLocalImportFile(path: string): [Uint8Array, string] {
|
||||
// try each of the local imports paths
|
||||
for (const localPath of this.options.localImportsPaths) {
|
||||
const importPath = this.callbacks.path.join(localPath, path);
|
||||
if(this.callbacks.fs.existsSync(importPath)) {
|
||||
return this.callbacks.loadFile(importPath);
|
||||
return [this.readFile(importPath), importPath];
|
||||
}
|
||||
}
|
||||
return null; // was not able to load from any of the paths
|
||||
return [null, null]; // was not able to load from any of the paths
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -223,6 +241,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
return false;
|
||||
}
|
||||
let importData: Uint8Array;
|
||||
let importPath: string;
|
||||
|
||||
if (base === constants.cldr_import_base) {
|
||||
// CLDR import
|
||||
|
|
@ -235,10 +254,10 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
/** There's no data or DTD change in 45, 46, 46.1, 47 so map them all to 46 at present. */
|
||||
paths[0] = constants.cldr_version_latest;
|
||||
}
|
||||
importData = this.readImportFile(paths[0], paths[1]);
|
||||
[importData, importPath] = this.readImportFile(paths[0], paths[1]);
|
||||
} else {
|
||||
// local import
|
||||
importData = this.readLocalImportFile(path);
|
||||
[importData, importPath] = this.readLocalImportFile(path);
|
||||
}
|
||||
if (!importData || !importData.length) {
|
||||
this.callbacks.reportMessage(DeveloperUtilsMessages.Error_ImportReadFail({base, path, subtag}));
|
||||
|
|
@ -246,6 +265,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
}
|
||||
const importXml: any = this.loadUnboxed(importData); // TODO-LDML: have to load as any because it is an arbitrary part
|
||||
const importRootNode = importXml[subtag]; // e.g. <keys/>
|
||||
this.eventResolver.addFile(importPath, new TextDecoder().decode(importData)); // TODO: double decode
|
||||
|
||||
// importXml will have one property: the root element.
|
||||
if (!importRootNode) {
|
||||
|
|
@ -263,7 +283,11 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
return false;
|
||||
}
|
||||
// Mark all children as an import
|
||||
subsubval.forEach(o => o[ImportStatus.import] = basePath);
|
||||
subsubval.forEach(o => {
|
||||
o[ImportStatus.import] = basePath;
|
||||
KeymanXMLReader.setMetaData(o, {[XML_FILENAME_SYMBOL as any]: importPath}); // mark overriding import path
|
||||
});
|
||||
|
||||
if (implied) {
|
||||
// mark all children as an implied import
|
||||
subsubval.forEach(o => o[ImportStatus.impliedImport] = basePath);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@
|
|||
* Abstraction for XML reading and writing
|
||||
*/
|
||||
|
||||
import { XMLParser, XMLBuilder, XmlBuilderOptions, X2jOptions } from 'fast-xml-parser';
|
||||
import { XMLParser, XMLBuilder, XMLMetaData, X2jOptions, XmlBuilderOptions } from 'fast-xml-parser';
|
||||
import { SymbolUtils } from "./symbol-utils.js";
|
||||
|
||||
/** Symbol giving the start offset, in chars, of the node */
|
||||
const XML_META_DATA_SYMBOL = XMLParser.getMetaDataSymbol();
|
||||
/** Symbol giving an override which file a node came from */
|
||||
export const XML_FILENAME_SYMBOL = Symbol("XML Filename");
|
||||
|
||||
export type KeymanXMLType =
|
||||
'keyboard3' // LDML <keyboard3>
|
||||
|
|
@ -48,6 +54,7 @@ const PARSER_OPTIONS: KeymanXMLParserOptionsBag = {
|
|||
// if we do need elements in the future, we'd check the preserve-space attribute here.
|
||||
return tagValue?.trim();
|
||||
},
|
||||
captureMetaData: true,
|
||||
trimValues: false, // preserve spaces, but see tagValueProcessor
|
||||
},
|
||||
'keyboardTest3': {
|
||||
|
|
@ -108,11 +115,54 @@ const GENERATOR_OPTIONS: KeymanXMLGeneratorOptionsBag = {
|
|||
},
|
||||
};
|
||||
|
||||
export interface KeymanXMLMetadata extends XMLMetaData {
|
||||
/** override of name of XML file */
|
||||
[XML_FILENAME_SYMBOL]?: string;
|
||||
}
|
||||
|
||||
/** wrapper for XML parsing support */
|
||||
export class KeymanXMLReader {
|
||||
public constructor(public type: KeymanXMLType) {
|
||||
}
|
||||
|
||||
/** Get metadata on a node if not already set */
|
||||
static getMetaData(o: any) : KeymanXMLMetadata {
|
||||
if(!o) return o;
|
||||
const metadata : KeymanXMLMetadata = o[XML_META_DATA_SYMBOL as any];
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/** Set metadata if not already set */
|
||||
public static setMetaData(o: any, metadata: KeymanXMLMetadata) : KeymanXMLMetadata {
|
||||
let m : KeymanXMLMetadata = KeymanXMLReader.getMetaData(o);
|
||||
if (!m) {
|
||||
m = {};
|
||||
}
|
||||
// copy non-symbols
|
||||
m = {...metadata, ...m};
|
||||
// copy symbols
|
||||
SymbolUtils.copySymbols(m, metadata);
|
||||
o[XML_META_DATA_SYMBOL as any] = m;
|
||||
return m;
|
||||
}
|
||||
|
||||
/** set metadata on this and children with the default filename - if not already set */
|
||||
public static setDefaultFilename(data: any, filename: string) {
|
||||
if (!data || !filename) return;
|
||||
if (typeof data === 'object') {
|
||||
const m = KeymanXMLReader.getMetaData(data) || {};
|
||||
if (!m[XML_FILENAME_SYMBOL]) {
|
||||
(m as any)[XML_FILENAME_SYMBOL] = filename;
|
||||
KeymanXMLReader.setMetaData(data, m);
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach(e => KeymanXMLReader.setDefaultFilename(e, filename));
|
||||
} else for(const k of Object.keys(data)) {
|
||||
KeymanXMLReader.setDefaultFilename(data[k], filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** move `{ $abc: 4 }` into `{ $: { abc: 4 } }` */
|
||||
private static fixupDollarAttributes(data: any) : any {
|
||||
if (typeof data === 'object') {
|
||||
|
|
@ -131,9 +181,9 @@ export class KeymanXMLReader {
|
|||
}
|
||||
});
|
||||
if (attrs.length) {
|
||||
e.push(['$', Object.fromEntries(attrs)]);
|
||||
e.push(['$', SymbolUtils.copySymbols(Object.fromEntries(attrs), data)]);
|
||||
}
|
||||
return Object.fromEntries(e);
|
||||
return SymbolUtils.copySymbols(Object.fromEntries(e), data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
|
|
@ -169,7 +219,7 @@ export class KeymanXMLReader {
|
|||
}
|
||||
}
|
||||
});
|
||||
return Object.fromEntries(e);
|
||||
return SymbolUtils.copySymbols(Object.fromEntries(e), data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
|
|
@ -249,12 +299,11 @@ export class KeymanXMLReader {
|
|||
}
|
||||
|
||||
public parser() {
|
||||
let options = PARSER_OPTIONS[this.type];
|
||||
const options = PARSER_OPTIONS[this.type];
|
||||
if (!options) {
|
||||
/* c8 ignore next 1 */
|
||||
throw Error(`Internal error: unhandled XML type ${this.type}`);
|
||||
}
|
||||
options = Object.assign({}, options); // TODO: xml2js likes to mutate the options here. Shallow clone the object.
|
||||
return new XMLParser(options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { KPJFileReader } from "../../src/types/kpj/kpj-file-reader.js";
|
|||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
import { KPJFileWriter } from '../../src/types/kpj/kpj-file-writer.js';
|
||||
import { KeymanDeveloperProjectOptions } from '../../src/types/kpj/keyman-developer-project.js';
|
||||
import { SymbolUtils } from '../../src/symbol-utils.js';
|
||||
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
|
||||
|
|
@ -21,12 +22,12 @@ describe('kpj-file-writer', function () {
|
|||
|
||||
const writer = new KPJFileWriter();
|
||||
const output = writer.write(project);
|
||||
const outputKpj = reader.read(new TextEncoder().encode(output));
|
||||
// Remove XML metadata symbols to reduce clutter for testing purposes
|
||||
const outputKpj = SymbolUtils.removeSymbols(reader.read(new TextEncoder().encode(output)));
|
||||
|
||||
// The outputKpj may not contain all the fields from the inputKpj, only the
|
||||
// essential fields. Many of the fields in .kpj are deprecated, when they
|
||||
// relate to file content (e.g. parented files, file details)
|
||||
|
||||
assert.deepEqual(outputKpj.KeymanDeveloperProject.Options, {
|
||||
"BuildPath": "$PROJECTPATH\\build",
|
||||
"CompilerWarningsAsErrors": "True",
|
||||
|
|
@ -61,7 +62,8 @@ describe('kpj-file-writer', function () {
|
|||
|
||||
const writer = new KPJFileWriter();
|
||||
const output = writer.write(project);
|
||||
const outputKpj = reader.read(new TextEncoder().encode(output));
|
||||
// Remove XML metadata symbols to reduce clutter for testing purposes
|
||||
const outputKpj = SymbolUtils.removeSymbols(reader.read(new TextEncoder().encode(output)));
|
||||
|
||||
// The outputKpj may not contain all the fields from the inputKpj, only the
|
||||
// essential fields. Many of the fields in .kpj are deprecated, when they
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { makePathToFixture } from '../helpers/index.js';
|
|||
|
||||
import { KpsFileReader } from "../../src/types/kps/kps-file-reader.js";
|
||||
import { KpsFileWriter } from '../../src/types/kps/kps-file-writer.js';
|
||||
import { SymbolUtils } from '../../src/symbol-utils.js';
|
||||
import { DeveloperUtilsMessages } from '../../src/developer-utils-messages.js';
|
||||
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
|
|
@ -59,13 +60,15 @@ describe('kps-file-reader', function () {
|
|||
it('kps-file-reader should round-trip with kps-file-writer', function() {
|
||||
const input = fs.readFileSync(makePathToFixture('kps', 'khmer_angkor.kps'));
|
||||
const reader = new KpsFileReader(callbacks);
|
||||
const kps = reader.read(input);
|
||||
// Remove XML metadata symbols to reduce clutter for testing purposes
|
||||
const kps = SymbolUtils.removeSymbols(reader.read(input));
|
||||
|
||||
const writer = new KpsFileWriter();
|
||||
const output = writer.write(kps);
|
||||
|
||||
// Round Trip
|
||||
const kps2 = reader.read(new TextEncoder().encode(output));
|
||||
// Remove XML metadata symbols to reduce clutter for testing purposes
|
||||
const kps2 = SymbolUtils.removeSymbols(reader.read(new TextEncoder().encode(output)));
|
||||
assert.deepEqual(kps2, kps);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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 './kvk-utils.tests.js';
|
||||
import { assert } from 'chai';
|
||||
import { SymbolUtils } from '../../src/symbol-utils.js';
|
||||
|
||||
describe('kvks-file-reader', function() {
|
||||
it('should read a valid file', function() {
|
||||
|
|
@ -63,7 +64,8 @@ describe('kvks-file-writer', function() {
|
|||
const input = fs.readFileSync(path);
|
||||
|
||||
const reader = new KvksFileReader();
|
||||
const kvksExpected = reader.read(input);
|
||||
// Remove XML metadata symbols to reduce clutter for testing purposes
|
||||
const kvksExpected = SymbolUtils.removeSymbols(reader.read(input));
|
||||
const invalidVkeys: string[] = [];
|
||||
const vk = reader.transform(kvksExpected, invalidVkeys);
|
||||
assert.isEmpty(invalidVkeys);
|
||||
|
|
@ -74,6 +76,7 @@ describe('kvks-file-writer', function() {
|
|||
// We compare the (re)loaded data, because there may be
|
||||
// minor, irrelevant formatting differences in the emitted xml
|
||||
const kvks = reader.read(Buffer.from(output, 'utf8'));
|
||||
assert.deepEqual(kvks, kvksExpected);
|
||||
// Remove XML metadata symbols to reduce clutter for testing purposes
|
||||
assert.deepEqual(SymbolUtils.removeSymbols(kvks), kvksExpected);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { testReaderCases } from '../helpers/reader-callback-test.js';
|
|||
import CLDRScanToVkey = Constants.CLDRScanToVkey;
|
||||
import CLDRScanToKeyMap = Constants.CLDRScanToKeyMap;
|
||||
import USVirtualKeyCodes = Constants.USVirtualKeyCodes;
|
||||
import { KeymanXMLReader, XML_FILENAME_SYMBOL } from '../../src/xml-utils.js';
|
||||
|
||||
function pluckKeysFromKeybag(keys: LKKey[], ids: string[]) {
|
||||
return keys.filter(({id}) => ids.indexOf(id) !== -1);
|
||||
|
|
@ -140,10 +141,18 @@ describe('ldml keyboard xml reader tests', function () {
|
|||
{ id: 'interrobang', output: '‽' },
|
||||
{ id: 'snail', output: '@' },
|
||||
]);
|
||||
const snailKey = source?.keyboard3?.keys.key.find(({ id }) => id === 'snail');
|
||||
// all of the keys are implied imports here
|
||||
assert.isFalse(ImportStatus.isImpliedImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail')));
|
||||
assert.isTrue(ImportStatus.isImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail')));
|
||||
assert.isTrue(ImportStatus.isLocalImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail')));
|
||||
assert.isFalse(ImportStatus.isImpliedImport(snailKey));
|
||||
assert.isTrue(ImportStatus.isImport(snailKey));
|
||||
assert.isTrue(ImportStatus.isLocalImport(snailKey));
|
||||
// get the actual filename of where the import was located
|
||||
const metadata = KeymanXMLReader.getMetaData(snailKey);
|
||||
assert.ok(metadata);
|
||||
const snailFilename = (metadata)[XML_FILENAME_SYMBOL];
|
||||
assert.ok(snailFilename);
|
||||
assert.ok(/keys-Zyyy-morepunctuation.xml$/.test(snailFilename)
|
||||
, `snail key filename is ${snailFilename}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
28
developer/src/common/web/utils/test/line-utils.tests.ts
Normal file
28
developer/src/common/web/utils/test/line-utils.tests.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by srl on 2025-04-03
|
||||
*
|
||||
* Test for LineFinder
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import 'mocha';
|
||||
import { LineFinder } from '../src/line-utils.js';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
|
||||
describe(`LineFinder test`, () => {
|
||||
const path = 'tran_fail-empty.xml';
|
||||
|
||||
it(`Should be able to process ${path}`, () => {
|
||||
const xmlPath = makePathToFixture('xml', `${path}`);
|
||||
const data = readFileSync(xmlPath, 'utf-8');
|
||||
assert.ok(data);
|
||||
const lf = new LineFinder(data);
|
||||
assert.ok(lf);
|
||||
assert.deepEqual(lf.findOffset(40), { line: 3, column: 0 });
|
||||
assert.deepEqual(lf.findOffset(136), { line: 4, column: 2 });
|
||||
assert.deepEqual(lf.findOffset(186), { line: 8, column: 2 });
|
||||
});
|
||||
});
|
||||
|
|
@ -13,7 +13,9 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
|||
|
||||
|
||||
import { KeymanXMLType, KeymanXMLReader, KeymanXMLWriter } from '../src/xml-utils.js';
|
||||
import { LineFinder } from '../src/line-utils.js';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
import { SymbolUtils } from '../src/symbol-utils.js';
|
||||
|
||||
// if true, attempt to WRITE the fixtures
|
||||
const { GEN_XML_FIXTURES } = env;
|
||||
|
|
@ -119,7 +121,7 @@ describe(`XML Reader Test ${GEN_XML_FIXTURES && '(update mode!)' || ''}`, () =>
|
|||
writeJson(jsonPath, actual);
|
||||
} else {
|
||||
assert.ok(expect, `Could not read ${jsonPath} - run with env GEN_XML_FIXTURES=1 to update.`);
|
||||
assert.deepEqual(actual, expect, `Mismatch of ${xmlPath} vs ${jsonPath}`);
|
||||
assert.deepEqual(SymbolUtils.removeSymbols(actual), expect, `Mismatch of ${xmlPath} vs ${jsonPath}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -160,3 +162,41 @@ describe(`XML Writer Test ${GEN_XML_FIXTURES && '(update mode!)' || ''}`, () =>
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe(`XML Reader line number test`, () => {
|
||||
const path = 'tran_fail-empty.xml';
|
||||
const xmlPath = makePathToFixture('xml', `${path}`);
|
||||
const type: KeymanXMLType = 'keyboard3';
|
||||
it(`Should report line numbers on parse of ${type} ${path}`, () => {
|
||||
let xml = readData(xmlPath);
|
||||
assert.ok(xml, `Could not read ${xmlPath}`);
|
||||
xml = xml.replace(/\r\n/g, '\n');
|
||||
const reader = new KeymanXMLReader(type);
|
||||
assert.ok(reader);
|
||||
|
||||
// now, parse. subsitute endings for Win
|
||||
const actual = reader.parse(xml);
|
||||
const lines = LineFinder.textToLines(xml);
|
||||
assert.ok(actual, `Parser failed on ${xmlPath}`);
|
||||
|
||||
// now, assert char offset
|
||||
const getMetaData = KeymanXMLReader.getMetaData;
|
||||
assert.ok(getMetaData(actual.keyboard3));
|
||||
assert.equal(
|
||||
getMetaData(actual.keyboard3)?.startIndex, 40); // index of <keyboard3> element
|
||||
assert.equal(
|
||||
getMetaData(actual.keyboard3.info)?.startIndex, 136); // index of <info> etc
|
||||
assert.equal(
|
||||
getMetaData(actual.keyboard3.transforms)?.startIndex, 186);
|
||||
assert.deepEqual(
|
||||
LineFinder.offsetToLineColumn(
|
||||
getMetaData(actual.keyboard3).startIndex, lines), { line: 3, column: 0 });
|
||||
assert.deepEqual(
|
||||
LineFinder.offsetToLineColumn(
|
||||
getMetaData(actual.keyboard3.info).startIndex, lines), { line: 4, column: 2 });
|
||||
assert.deepEqual(
|
||||
LineFinder.offsetToLineColumn(
|
||||
getMetaData(actual.keyboard3.transforms).startIndex, lines), { line: 8, column: 2 });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import {
|
|||
CompilerCallbacks, KeymanCompiler, KeymanCompilerResult, KeymanCompilerArtifacts,
|
||||
defaultCompilerOptions, LDMLKeyboardXMLSourceFileReader, LDMLKeyboard,
|
||||
LDMLKeyboardTestDataXMLSourceFile, KMXBuilder,
|
||||
KeymanCompilerArtifactOptional
|
||||
KeymanCompilerArtifactOptional,
|
||||
ResolvingCompilerCallbacks,
|
||||
KeymanXMLReader,
|
||||
} from "@keymanapp/developer-utils";
|
||||
import { LdmlCompilerOptions } from './ldml-compiler-options.js';
|
||||
import { LdmlCompilerMessages } from './ldml-compiler-messages.js';
|
||||
|
|
@ -94,6 +96,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
|
||||
// uset parser
|
||||
private usetparser?: LdmlKeyboardTypes.UnicodeSetParser = undefined;
|
||||
private reader?: LDMLKeyboardXMLSourceFileReader;
|
||||
|
||||
/**
|
||||
* Initialize the compiler, including loading the WASM host for uset parsing.
|
||||
|
|
@ -105,7 +108,9 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
*/
|
||||
async init(callbacks: CompilerCallbacks, options: LdmlCompilerOptions): Promise<boolean> {
|
||||
this.options = { ...options };
|
||||
this.callbacks = callbacks;
|
||||
this.reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, callbacks);
|
||||
// wrap the callbacks so that the eventresolver is called
|
||||
this.callbacks = new ResolvingCompilerCallbacks(this.reader, this.options, callbacks);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -238,9 +243,9 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
* @returns the source file, or null if invalid
|
||||
*/
|
||||
public load(filename: string): LDMLKeyboardXMLSourceFile | null {
|
||||
const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks);
|
||||
const reader = this.reader;
|
||||
// load the file from disk into a string
|
||||
const data = this.callbacks.loadFile(filename);
|
||||
const data = reader.readFile(filename);
|
||||
if (!data) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({ errorText: 'Unable to read XML file' }));
|
||||
return null;
|
||||
|
|
@ -261,6 +266,9 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
return null;
|
||||
}
|
||||
|
||||
// record the default filename - for error reporting.
|
||||
KeymanXMLReader.setDefaultFilename(source, filename);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +280,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
* @returns the source file, or null if invalid
|
||||
*/
|
||||
public loadTestData(filename: string): LDMLKeyboardTestDataXMLSourceFile | null {
|
||||
const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks);
|
||||
const reader = this.reader;
|
||||
const data = this.callbacks.loadFile(filename);
|
||||
if (!data) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidFile({ errorText: 'Unable to read XML file' }));
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ export class KeysCompiler extends SectionCompiler {
|
|||
|
||||
if (layer.row.length > keymap.length) {
|
||||
this.callbacks.reportMessage(
|
||||
LdmlCompilerMessages.Error_HardwareLayerHasTooManyRows()
|
||||
LdmlCompilerMessages.Error_HardwareLayerHasTooManyRows(layer)
|
||||
);
|
||||
valid = false;
|
||||
}
|
||||
|
|
@ -485,7 +485,10 @@ export class KeysCompiler extends SectionCompiler {
|
|||
}
|
||||
if (!keydef.output && !keydef.gap && !keydef.layerId) {
|
||||
this.callbacks.reportMessage(
|
||||
LdmlCompilerMessages.Error_KeyMissingToGapOrSwitch({ keyId: key })
|
||||
LdmlCompilerMessages.Error_KeyMissingToGapOrSwitch(
|
||||
{ keyId: key },
|
||||
keydef,
|
||||
)
|
||||
);
|
||||
valid = false;
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { util } from "@keymanapp/common-types";
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def } from '@keymanapp/developer-utils';
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, XML_FILENAME_SYMBOL, CompilerEvent, KeymanXMLReader } from '@keymanapp/developer-utils';
|
||||
// const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
|
|
@ -9,6 +9,32 @@ const SevError = CompilerErrorSeverity.Error | CompilerErrorNamespace.LdmlKeyboa
|
|||
// sub-numberspace for transform errors
|
||||
const SevErrorTransform = SevError | 0xF00;
|
||||
|
||||
/**
|
||||
* Any object with metadata, for line number errs.
|
||||
* Could be for example an LKKeys or KMXPlus.KeysKeys object.
|
||||
* Defined as 'any' here to reduce noise on the client side.
|
||||
* @see {@link KeymanXMLReader.getMetaData()}
|
||||
*/
|
||||
type ObjectWithMetadata = any;
|
||||
|
||||
/**
|
||||
* Convenience function for constructing CompilerEvents with line numbers
|
||||
* @param code Unique numeric value of the event
|
||||
* @param message A short description of the error presented to the user
|
||||
* @param x Object to be used as a source for line number information
|
||||
* @param detail Detailed Markdown-formatted description of the error
|
||||
* including references to documentation, remediation options.
|
||||
* @see CompilerMessageSpec
|
||||
* @returns
|
||||
*/
|
||||
function CompilerMessageObjectSpec(code: number, message: string, x: ObjectWithMetadata, detail?: string): CompilerEvent {
|
||||
let evt = m(code, message, detail); // raw message
|
||||
evt = LdmlCompilerMessages.offset(evt, x); // with offset
|
||||
return evt;
|
||||
};
|
||||
|
||||
const mx = CompilerMessageObjectSpec;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
|
@ -20,7 +46,11 @@ export class LdmlCompilerMessages {
|
|||
static Error_InvalidLocale = (o:{tag: string}) => m(this.ERROR_InvalidLocale, `Invalid BCP 47 locale form '${def(o.tag)}'`);
|
||||
|
||||
static ERROR_HardwareLayerHasTooManyRows = SevError | 0x0003;
|
||||
static Error_HardwareLayerHasTooManyRows = () => m(this.ERROR_HardwareLayerHasTooManyRows, `'hardware' layer has too many rows`);
|
||||
static Error_HardwareLayerHasTooManyRows = (x: any) => mx(
|
||||
this.ERROR_HardwareLayerHasTooManyRows,
|
||||
`'hardware' layer has too many rows`,
|
||||
x,
|
||||
);
|
||||
|
||||
static ERROR_RowOnHardwareLayerHasTooManyKeys = SevError | 0x0004;
|
||||
static Error_RowOnHardwareLayerHasTooManyKeys = (o:{row: number, hardware: string, modifiers: string}) => m(this.ERROR_RowOnHardwareLayerHasTooManyKeys, `Row #${def(o.row)} on 'hardware' ${def(o.hardware)} layer for modifier ${o.modifiers || 'none'} has too many keys`);
|
||||
|
|
@ -97,8 +127,11 @@ export class LdmlCompilerMessages {
|
|||
m(this.ERROR_DisplayIsRepeated, `display ${LdmlCompilerMessages.outputOrKeyId(o)} has more than one display entry.`);
|
||||
|
||||
static ERROR_KeyMissingToGapOrSwitch = SevError | 0x0011;
|
||||
static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}) =>
|
||||
m(this.ERROR_KeyMissingToGapOrSwitch, `key id='${def(o.keyId)}' must have either output=, gap=, or layerId=.`);
|
||||
static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}, x: ObjectWithMetadata) => mx(
|
||||
this.ERROR_KeyMissingToGapOrSwitch,
|
||||
`key id='${def(o.keyId)}' must have either output=, gap=, or layerId=.`,
|
||||
x,
|
||||
);
|
||||
|
||||
static ERROR_ExcessHardware = SevError | 0x0012;
|
||||
static Error_ExcessHardware = (o:{formId: string}) => m(this.ERROR_ExcessHardware,
|
||||
|
|
@ -274,5 +307,18 @@ export class LdmlCompilerMessages {
|
|||
`Invalid transform to="${def(o.to)}": "${def(o.message)}"`,
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Get an offset from o and set e's offset field
|
||||
* @param event a compiler event, such as from functions in this class
|
||||
* @param x any object parsed from XML or with the XML_META_DATA_SYMBOL symbol copied over
|
||||
* @returns modified event object
|
||||
*/
|
||||
static offset(event: CompilerEvent, x?: any): CompilerEvent {
|
||||
if(x) {
|
||||
const metadata = KeymanXMLReader.getMetaData(x) || {};
|
||||
event.offset = metadata?.startIndex;
|
||||
event.filename = event.filename || metadata[XML_FILENAME_SYMBOL];
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {assert} from 'chai';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import { KeymanXMLReader } from "@keymanapp/developer-utils";
|
||||
import { KeymanXMLReader, SymbolUtils } from "@keymanapp/developer-utils";
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -21,5 +21,5 @@ export function compareXml(actual : string, expect: string, mutator?: (input: an
|
|||
const actualParsed = mutator(reader.parse(actualStr));
|
||||
const expectParsed = mutator(reader.parse(expectStr));
|
||||
|
||||
assert.deepEqual(actualParsed, expectParsed);
|
||||
assert.deepEqual(SymbolUtils.removeSymbols(actualParsed), SymbolUtils.removeSymbols(expectParsed));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import * as path from 'path';
|
|||
import { fileURLToPath } from 'url';
|
||||
import { SectionCompiler, SectionCompilerNew } from '../../src/compiler/section-compiler.js';
|
||||
import { util, KMXPlus, LdmlKeyboardTypes } from '@keymanapp/common-types';
|
||||
import { CompilerEvent, compilerEventFormat, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboard, } from "@keymanapp/developer-utils";
|
||||
import { CompilerEvent, compilerEventFormat, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboard, KeymanXMLMetadata, KeymanXMLReader } 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';
|
||||
|
|
@ -290,7 +290,7 @@ export function testCompilationCases(compiler: SectionCompilerNew, cases : Compi
|
|||
// no warnings, so expect zero messages
|
||||
assert.sameDeepMembers(callbacks.messages, [], 'expected zero messages but got ' + callbacks.messages);
|
||||
}
|
||||
|
||||
|
||||
if (expectFailure) {
|
||||
assert.isNull(section, 'expected compilation result failure (null)');
|
||||
} else {
|
||||
|
|
@ -327,3 +327,13 @@ const dontEscape = /[a-zA-Z0-9\.${}\[\]-]/;
|
|||
export function hex_str(s?: string) : string {
|
||||
return [...s].map(ch => dontEscape.test(ch) ? ch : util.escapeRegexChar(ch)).join('');
|
||||
}
|
||||
|
||||
/** return an object simulating an XML object with a column number */
|
||||
export function withColumn(c: number) : KeymanXMLMetadata {
|
||||
// set metadata on an empty object
|
||||
const o = {};
|
||||
KeymanXMLReader.setMetaData(o, {
|
||||
startIndex: c
|
||||
});
|
||||
return o;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { KeysCompiler } from '../src/compiler/keys.js';
|
||||
import { assertCodePoints, compilerTestCallbacks, loadSectionFixture, testCompilationCases } from './helpers/index.js';
|
||||
import { assertCodePoints, compilerTestCallbacks, loadSectionFixture, testCompilationCases, withColumn } from './helpers/index.js';
|
||||
import { KMXPlus, Constants, LdmlKeyboardTypes } from '@keymanapp/common-types';
|
||||
import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js';
|
||||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
|
|
@ -442,7 +442,7 @@ describe('keys.kmap', function () {
|
|||
assert.isNull(keys);
|
||||
assert.equal(compilerTestCallbacks.messages.length, 1);
|
||||
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_HardwareLayerHasTooManyRows());
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_HardwareLayerHasTooManyRows(withColumn(276)));
|
||||
});
|
||||
|
||||
it('should reject layouts with too many hardware keys', async function() {
|
||||
|
|
@ -464,7 +464,10 @@ describe('keys.kmap', function () {
|
|||
const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/invalid-key-missing-attrs.xml', compilerTestCallbacks, keysDependencies) as Keys;
|
||||
assert.isNull(keys);
|
||||
assert.equal(compilerTestCallbacks.messages.length, 1);
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_KeyMissingToGapOrSwitch({keyId: 'Q'}));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_KeyMissingToGapOrSwitch(
|
||||
{keyId: 'Q'},
|
||||
withColumn(188)
|
||||
));
|
||||
});
|
||||
it('should accept layouts with gap/switch keys', async function() {
|
||||
const keys = await loadSectionFixture(KeysCompiler, 'sections/keys/gap-switch.xml', compilerTestCallbacks, keysDependencies) as Keys;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ export class NodeCompilerCallbacks implements CompilerCallbacks {
|
|||
messages: CompilerEvent[] = [];
|
||||
messageCount = 0;
|
||||
messageFilename: string = '';
|
||||
/** cache of the contentes of the text */
|
||||
messageFiletext: string = '';
|
||||
maxLogMessages = MaxMessagesDefault;
|
||||
|
||||
constructor(private options: CompilerCallbackOptions) {
|
||||
|
|
@ -55,6 +57,7 @@ export class NodeCompilerCallbacks implements CompilerCallbacks {
|
|||
this.messages = [];
|
||||
this.messageCount = 0;
|
||||
this.messageFilename = '';
|
||||
this.messageFiletext = '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -154,6 +157,7 @@ export class NodeCompilerCallbacks implements CompilerCallbacks {
|
|||
// Reset max message limit when a new file is being processed
|
||||
this.messageFilename = event.filename;
|
||||
this.messageCount = 0;
|
||||
this.messageFiletext = '';
|
||||
}
|
||||
|
||||
const disable = CompilerFileCallbacks.applyMessageOverridesToEvent(event, this.options.messageOverrides);
|
||||
|
|
|
|||
|
|
@ -67,10 +67,10 @@ https://help.keyman.com/developer/engine/android/latest-version/
|
|||
| KEYMAN_MIN_VERSION_NPM | 10.5.1 |
|
||||
| KEYMAN_MIN_VERSION_VISUAL_STUDIO | 2019 |
|
||||
| KEYMAN_VERSION_CLDR | 46 |
|
||||
| KEYMAN_VERSION_GRADLE | 7.6.4 |
|
||||
| KEYMAN_VERSION_GRADLE | 8.12 |
|
||||
| KEYMAN_VERSION_ICU | 73.1 |
|
||||
| KEYMAN_VERSION_ISO639_3 | 2024-05-22 |
|
||||
| KEYMAN_VERSION_JAVA | 11 |
|
||||
| KEYMAN_VERSION_JAVA | 21 |
|
||||
| KEYMAN_VERSION_LANGTAGS | 2025-02-18 |
|
||||
| KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY | 2025-03-10 |
|
||||
| KEYMAN_VERSION_UNICODE | 16.0.0 |
|
||||
|
|
|
|||
|
|
@ -196,14 +196,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
|
|||
[self clearContext];
|
||||
NSString *kmxPath = [self.kmxList objectAtIndex:self.kbComboBox.indexOfSelectedItem-1];
|
||||
KMXFile *kmx = [[KMXFile alloc] initWithFilePath:kmxPath];
|
||||
if (![kmx isValid]) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert addButtonWithTitle:@"OK"];
|
||||
[alert setMessageText:@"Invalid KMX file"];
|
||||
[alert setInformativeText:@"This KMX file contains some invalid code!"];
|
||||
[alert setAlertStyle:NSAlertStyleWarning];
|
||||
[alert runModal];
|
||||
}
|
||||
|
||||
NSString *packagePath = [kmxPath stringByDeletingLastPathComponent];
|
||||
NSDictionary *infoDict = [KMXFile keyboardInfoFromKmxFile:kmxPath];
|
||||
|
|
@ -217,12 +209,6 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
|
|||
|
||||
[self.kbData setObject:kmx forKey:kKMXFileKey];
|
||||
self.imgView.image = kmx.bitmap;
|
||||
|
||||
int index = 0;
|
||||
for (NSObject *kmStore in kmx.store) {
|
||||
NSLog(@"%d: %@", index, kmStore);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@
|
|||
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
|
||||
NSMutableArray *kmpFiles = nil;
|
||||
for (NSString *filename in filenames) {
|
||||
if ([[filename lastNChars:4] isEqualTo:@".kmp"]) {
|
||||
if ([filename hasSuffix:@".kmp"]) {
|
||||
if (kmpFiles == nil)
|
||||
kmpFiles = [NSMutableArray arrayWithObject:filename];
|
||||
else
|
||||
|
|
|
|||
|
|
@ -740,11 +740,8 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
|
|||
return _contextBuffer;
|
||||
}
|
||||
|
||||
- (void)setContextBuffer:(NSMutableString *)contextBuffer {
|
||||
_contextBuffer = [contextBuffer mutableCopy];
|
||||
if (_contextBuffer.length)
|
||||
[_contextBuffer replaceOccurrencesOfString:@"\0" withString:[NSString nullChar] options:0 range:NSMakeRange(0, 1)];
|
||||
[self.kme setCoreContextIfNeeded:self.contextBuffer];
|
||||
- (void)clearContextBuffer {
|
||||
[self.kme clearCoreContext];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib {
|
||||
|
|
@ -880,8 +877,8 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
|
|||
NSString *keyboardName = [self.activeKeyboards objectAtIndex:0];
|
||||
[self setSelectedKeyboard:keyboardName inMenuItem:menuItem];
|
||||
[self setSelectedKeyboard:keyboardName];
|
||||
[self setContextBuffer:nil];
|
||||
|
||||
[self clearContextBuffer];
|
||||
|
||||
[KMSentryHelper addInfoBreadCrumb:@"startup" message:[NSString stringWithFormat:@"setDefaultSelectedKeyboard: %@", keyboardName]];
|
||||
}
|
||||
|
||||
|
|
@ -893,7 +890,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
|
|||
[self setKvk:nil];
|
||||
[self setKeyboardName:nil];
|
||||
[self setKeyboardIcon:nil];
|
||||
[self setContextBuffer:nil];
|
||||
[self clearContextBuffer];
|
||||
[self setSelectedKeyboard:@""];
|
||||
}
|
||||
|
||||
|
|
@ -932,7 +929,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
|
|||
os_log_info([KMLogs keyboardLog], "Selected keyboard from menu: %{public}@", keyboardName);
|
||||
[self setKeyboardName:keyboardName];
|
||||
[self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]];
|
||||
[self setContextBuffer:nil];
|
||||
[self clearContextBuffer];
|
||||
[self setSelectedKeyboard:path];
|
||||
[self applyPersistedOptions];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,6 @@
|
|||
return _contextBuffer;
|
||||
}
|
||||
|
||||
- (void)setContextBuffer:(NSMutableString *)contextBuffer {
|
||||
_contextBuffer = [contextBuffer mutableCopy];
|
||||
if (_contextBuffer.length)
|
||||
[_contextBuffer replaceOccurrencesOfString:@"\0" withString:[NSString nullChar] options:0 range:NSMakeRange(0, 1)];
|
||||
[self.kme setCoreContextIfNeeded:self.contextBuffer];
|
||||
}
|
||||
|
||||
-(NSEvent *)keyStrokeEventForCharacter:(NSString *)character keyCode:(unsigned short) keyCode {
|
||||
return [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSZeroPoint modifierFlags:0 timestamp:NSTimeIntervalSince1970 windowNumber:0 context:nil characters:character charactersIgnoringModifiers:character isARepeat:NO keyCode:keyCode];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,13 +51,7 @@
|
|||
984C2AD81A79C98D0023F89D /* KeymanEngine4Mac.h in Headers */ = {isa = PBXBuildFile; fileRef = 984C2AD71A79C98D0023F89D /* KeymanEngine4Mac.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
984C2ADE1A79C98D0023F89D /* KeymanEngine4Mac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 984C2AD21A79C98D0023F89D /* KeymanEngine4Mac.framework */; };
|
||||
984C2AE51A79C98D0023F89D /* KMXFileTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 984C2AE41A79C98D0023F89D /* KMXFileTests.m */; };
|
||||
984C2B061A79CA4F0023F89D /* NSString+XString.h in Headers */ = {isa = PBXBuildFile; fileRef = 984C2AF21A79CA4F0023F89D /* NSString+XString.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
984C2B071A79CA4F0023F89D /* NSString+XString.m in Sources */ = {isa = PBXBuildFile; fileRef = 984C2AF31A79CA4F0023F89D /* NSString+XString.m */; };
|
||||
984C2B091A79CA4F0023F89D /* KMBinaryFileFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 984C2AF51A79CA4F0023F89D /* KMBinaryFileFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
984C2B0C1A79CA4F0023F89D /* KMCompGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 984C2AF81A79CA4F0023F89D /* KMCompGroup.h */; };
|
||||
984C2B0D1A79CA4F0023F89D /* KMCompGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 984C2AF91A79CA4F0023F89D /* KMCompGroup.m */; };
|
||||
984C2B0E1A79CA4F0023F89D /* KMCompKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 984C2AFA1A79CA4F0023F89D /* KMCompKey.h */; };
|
||||
984C2B0F1A79CA4F0023F89D /* KMCompKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 984C2AFB1A79CA4F0023F89D /* KMCompKey.m */; };
|
||||
984C2B101A79CA4F0023F89D /* KMCompStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 984C2AFC1A79CA4F0023F89D /* KMCompStore.h */; };
|
||||
984C2B111A79CA4F0023F89D /* KMCompStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 984C2AFD1A79CA4F0023F89D /* KMCompStore.m */; };
|
||||
984C2B121A79CA4F0023F89D /* KMEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 984C2AFE1A79CA4F0023F89D /* KMEngine.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
|
@ -134,13 +128,7 @@
|
|||
984C2ADD1A79C98D0023F89D /* KeymanEngine4MacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeymanEngine4MacTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
984C2AE31A79C98D0023F89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
984C2AE41A79C98D0023F89D /* KMXFileTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMXFileTests.m; sourceTree = "<group>"; };
|
||||
984C2AF21A79CA4F0023F89D /* NSString+XString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+XString.h"; sourceTree = "<group>"; };
|
||||
984C2AF31A79CA4F0023F89D /* NSString+XString.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+XString.m"; sourceTree = "<group>"; };
|
||||
984C2AF51A79CA4F0023F89D /* KMBinaryFileFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMBinaryFileFormat.h; sourceTree = "<group>"; };
|
||||
984C2AF81A79CA4F0023F89D /* KMCompGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMCompGroup.h; sourceTree = "<group>"; };
|
||||
984C2AF91A79CA4F0023F89D /* KMCompGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMCompGroup.m; sourceTree = "<group>"; };
|
||||
984C2AFA1A79CA4F0023F89D /* KMCompKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMCompKey.h; sourceTree = "<group>"; };
|
||||
984C2AFB1A79CA4F0023F89D /* KMCompKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMCompKey.m; sourceTree = "<group>"; };
|
||||
984C2AFC1A79CA4F0023F89D /* KMCompStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMCompStore.h; sourceTree = "<group>"; };
|
||||
984C2AFD1A79CA4F0023F89D /* KMCompStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMCompStore.m; sourceTree = "<group>"; };
|
||||
984C2AFE1A79CA4F0023F89D /* KMEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMEngine.h; sourceTree = "<group>"; };
|
||||
|
|
@ -307,10 +295,6 @@
|
|||
981880641BC1EA5800A1FBA5 /* OnScreenKeyboard */,
|
||||
984C2AEF1A79CA4F0023F89D /* Categories */,
|
||||
984C2AF51A79CA4F0023F89D /* KMBinaryFileFormat.h */,
|
||||
984C2AF81A79CA4F0023F89D /* KMCompGroup.h */,
|
||||
984C2AF91A79CA4F0023F89D /* KMCompGroup.m */,
|
||||
984C2AFA1A79CA4F0023F89D /* KMCompKey.h */,
|
||||
984C2AFB1A79CA4F0023F89D /* KMCompKey.m */,
|
||||
984C2AFC1A79CA4F0023F89D /* KMCompStore.h */,
|
||||
984C2AFD1A79CA4F0023F89D /* KMCompStore.m */,
|
||||
984C2AFE1A79CA4F0023F89D /* KMEngine.h */,
|
||||
|
|
@ -332,8 +316,6 @@
|
|||
984C2AEF1A79CA4F0023F89D /* Categories */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
984C2AF21A79CA4F0023F89D /* NSString+XString.h */,
|
||||
984C2AF31A79CA4F0023F89D /* NSString+XString.m */,
|
||||
);
|
||||
path = Categories;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -351,7 +333,6 @@
|
|||
984C2B141A79CA4F0023F89D /* KMXFile.h in Headers */,
|
||||
981880771BC1EA5800A1FBA5 /* OSKView.h in Headers */,
|
||||
984C2B161A79CA4F0023F89D /* KVKFile.h in Headers */,
|
||||
984C2B061A79CA4F0023F89D /* NSString+XString.h in Headers */,
|
||||
984C2AD81A79C98D0023F89D /* KeymanEngine4Mac.h in Headers */,
|
||||
2993C33429B1D92E00FB5E95 /* CoreAction.h in Headers */,
|
||||
980053351B37C9B50088DDDD /* NFont.h in Headers */,
|
||||
|
|
@ -360,9 +341,7 @@
|
|||
29CBB2C62CA137A50097556B /* CoreKey.h in Headers */,
|
||||
984C2B191A79CB020023F89D /* WindowsVKCodes.h in Headers */,
|
||||
29E50EB12CA121BA00B59959 /* CoreKeyboardInfo.h in Headers */,
|
||||
984C2B0C1A79CA4F0023F89D /* KMCompGroup.h in Headers */,
|
||||
984C2B101A79CA4F0023F89D /* KMCompStore.h in Headers */,
|
||||
984C2B0E1A79CA4F0023F89D /* KMCompKey.h in Headers */,
|
||||
980053371B37C9B50088DDDD /* NKey.h in Headers */,
|
||||
9840F5ED1B43B02500410ABA /* MacVKCodes.h in Headers */,
|
||||
981880711BC1EA5800A1FBA5 /* KeyLabelCell.h in Headers */,
|
||||
|
|
@ -525,7 +504,6 @@
|
|||
29B4A0D92BFAE8A400682049 /* KMELogs.m in Sources */,
|
||||
981880781BC1EA5800A1FBA5 /* OSKView.m in Sources */,
|
||||
2993C32E29B1D92E00FB5E95 /* CoreWrapper.m in Sources */,
|
||||
984C2B0F1A79CA4F0023F89D /* KMCompKey.m in Sources */,
|
||||
984C2B131A79CA4F0023F89D /* KMEngine.m in Sources */,
|
||||
984C2B171A79CA4F0023F89D /* KVKFile.m in Sources */,
|
||||
29A1971F2AF091E600512A37 /* CoreKeyOutput.m in Sources */,
|
||||
|
|
@ -533,12 +511,10 @@
|
|||
981880761BC1EA5800A1FBA5 /* OSKKey.m in Sources */,
|
||||
29BE6F042C9D700B00635B1A /* CoreKey.m in Sources */,
|
||||
2993C32F29B1D92E00FB5E95 /* CoreHelper.m in Sources */,
|
||||
984C2B0D1A79CA4F0023F89D /* KMCompGroup.m in Sources */,
|
||||
984C2B111A79CA4F0023F89D /* KMCompStore.m in Sources */,
|
||||
2993C32C29B1D92E00FB5E95 /* CoreAction.m in Sources */,
|
||||
984C2B151A79CA4F0023F89D /* KMXFile.m in Sources */,
|
||||
980053361B37C9B50088DDDD /* NFont.m in Sources */,
|
||||
984C2B071A79CA4F0023F89D /* NSString+XString.m in Sources */,
|
||||
29E50EB02CA121BA00B59959 /* CoreKeyboardInfo.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// NSString+XString.h
|
||||
// KeymanEngine4Mac
|
||||
//
|
||||
// Created by Serkan Kurt on 8/01/2015.
|
||||
// Copyright (c) 2017 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "KMBinaryFileFormat.h"
|
||||
|
||||
@interface NSString (XString)
|
||||
|
||||
- (NSUInteger)xLength;
|
||||
- (NSUInteger)xLengthIncludingIf;
|
||||
- (NSUInteger)deadKeyCount;
|
||||
- (NSString *)lastNChars:(NSUInteger)n;
|
||||
- (NSString *)codeString;
|
||||
- (BOOL)isValidCode;
|
||||
+ (NSString *)nullChar;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSMutableString (XString)
|
||||
|
||||
- (void)appendDeadkey:(NSUInteger)index;
|
||||
- (void)appendNullChar;
|
||||
- (NSUInteger)deleteLastDeadkeys;
|
||||
- (NSUInteger)deleteLastNullChars;
|
||||
- (NSUInteger)removeAllNullChars;
|
||||
- (void)deleteLastNChars:(NSUInteger)n;
|
||||
|
||||
@end
|
||||
|
|
@ -1,489 +0,0 @@
|
|||
//
|
||||
// NSString+XString.m
|
||||
// KeymanEngine4Mac
|
||||
//
|
||||
// Created by Serkan Kurt on 8/01/2015.
|
||||
// Copyright (c) 2017 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+XString.h"
|
||||
|
||||
@implementation NSString (XString)
|
||||
|
||||
// For calculating offset including system stores
|
||||
- (NSUInteger)xLengthIncludingIf {
|
||||
NSUInteger cx = 0;
|
||||
for (int i = 0; i < self.length;) {
|
||||
unichar c = [self characterAtIndex:i];
|
||||
if (c == UC_SENTINEL) {
|
||||
c = [self characterAtIndex:i+1];
|
||||
switch (c) {
|
||||
case CODE_ANY:
|
||||
case CODE_NOTANY:
|
||||
case CODE_CONTEXTEX:
|
||||
case CODE_DEADKEY:
|
||||
case CODE_NULLCHAR:
|
||||
cx++;
|
||||
i+=3;
|
||||
break;
|
||||
case CODE_INDEX:
|
||||
cx++;
|
||||
i+=4;
|
||||
break;
|
||||
case CODE_IFOPT:
|
||||
case CODE_IFSYSTEMSTORE:
|
||||
cx++;
|
||||
i+=5;
|
||||
break;
|
||||
case CODE_NUL:
|
||||
default:
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cx++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return cx;
|
||||
}
|
||||
|
||||
- (NSUInteger)xLength {
|
||||
NSUInteger cx = 0;
|
||||
for (int i = 0; i < self.length;) {
|
||||
unichar c = [self characterAtIndex:i];
|
||||
if (c == UC_SENTINEL) {
|
||||
c = [self characterAtIndex:i+1];
|
||||
switch (c) {
|
||||
case CODE_ANY:
|
||||
case CODE_NOTANY:
|
||||
case CODE_CONTEXTEX:
|
||||
case CODE_DEADKEY:
|
||||
case CODE_NULLCHAR:
|
||||
cx++;
|
||||
i+=3;
|
||||
break;
|
||||
case CODE_INDEX:
|
||||
cx++;
|
||||
i+=4;
|
||||
break;
|
||||
case CODE_IFOPT:
|
||||
case CODE_IFSYSTEMSTORE:
|
||||
i+=5;
|
||||
break;
|
||||
case CODE_NUL:
|
||||
default:
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cx++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return cx;
|
||||
}
|
||||
|
||||
- (NSUInteger)deadKeyCount {
|
||||
NSUInteger dk = 0;
|
||||
for (int i = 0; i < self.length;) {
|
||||
unichar c = [self characterAtIndex:i];
|
||||
if (c == UC_SENTINEL) {
|
||||
c = [self characterAtIndex:i+1];
|
||||
switch (c) {
|
||||
case CODE_DEADKEY:
|
||||
dk++;
|
||||
i+=3;
|
||||
break;
|
||||
case CODE_NULLCHAR:
|
||||
case CODE_ANY:
|
||||
case CODE_NOTANY:
|
||||
case CODE_CONTEXTEX:
|
||||
i+=3;
|
||||
break;
|
||||
case CODE_INDEX:
|
||||
i+=4;
|
||||
break;
|
||||
case CODE_IFOPT:
|
||||
case CODE_IFSYSTEMSTORE:
|
||||
i+=5;
|
||||
break;
|
||||
case CODE_NUL:
|
||||
default:
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return dk;
|
||||
}
|
||||
|
||||
- (NSString *)lastNChars:(NSUInteger)n {
|
||||
if (n == 0)
|
||||
return @"";
|
||||
|
||||
if (self.length >= 3) {
|
||||
NSInteger len = 0;
|
||||
NSInteger chars = 0;
|
||||
for (NSInteger i = self.length - 1; i >= 0;) {
|
||||
NSInteger index = i - 2;
|
||||
if (index >= 0) {
|
||||
unichar c1 = [self characterAtIndex:index];
|
||||
unichar c2 = [self characterAtIndex:index+1];
|
||||
if (c1 == UC_SENTINEL && (c2 == CODE_DEADKEY || c2 == CODE_NULLCHAR)) {
|
||||
i -= 3;
|
||||
len += 3;
|
||||
}
|
||||
else {
|
||||
i--;
|
||||
len++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
i--;
|
||||
len++;
|
||||
}
|
||||
|
||||
chars++;
|
||||
if (chars == n)
|
||||
break;
|
||||
}
|
||||
|
||||
return [NSString stringWithString:[self substringFromIndex:self.length - len]];
|
||||
}
|
||||
else {
|
||||
if (n >= self.length)
|
||||
return [NSString stringWithString:self];
|
||||
else
|
||||
return [NSString stringWithString:[self substringFromIndex:self.length - n]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)codeString {
|
||||
if (self == nil)
|
||||
return nil;
|
||||
|
||||
NSMutableString *mStr = [NSMutableString stringWithString:@""];
|
||||
for (int i=0; i < self.length;) {
|
||||
unichar c = [self characterAtIndex:i];
|
||||
if (c == UC_SENTINEL) {
|
||||
if ((i+1) < self.length)
|
||||
c = [self characterAtIndex:i+1];
|
||||
else {
|
||||
[mStr appendString:@"sentinel(INVALID)"];
|
||||
return mStr;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case CODE_ANY: {
|
||||
if ((i+2) < self.length) {
|
||||
DWORD index = [self characterAtIndex:i+2];
|
||||
[mStr appendString:[NSString stringWithFormat:@"any(%d)", index]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"any(INVALID)"];
|
||||
}
|
||||
|
||||
i+=3;
|
||||
break;
|
||||
}
|
||||
case CODE_NOTANY: {
|
||||
if ((i+2) < self.length) {
|
||||
DWORD index = [self characterAtIndex:i+2];
|
||||
[mStr appendString:[NSString stringWithFormat:@"notany(%d)", index]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"notany(INVALID)"];
|
||||
}
|
||||
|
||||
i+=3;
|
||||
break;
|
||||
}
|
||||
case CODE_INDEX: {
|
||||
if ((i+3) < self.length) {
|
||||
DWORD x1 = [self characterAtIndex:i+2];
|
||||
DWORD x2 = [self characterAtIndex:i+3];
|
||||
[mStr appendString:[NSString stringWithFormat:@"index(%d,%d)", x1, x2]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"index(INVALID)"];
|
||||
}
|
||||
|
||||
i+=4;
|
||||
break;
|
||||
}
|
||||
case CODE_NUL:
|
||||
[mStr appendString:@"nul()"];
|
||||
i+=2;
|
||||
break;
|
||||
case CODE_DEADKEY: {
|
||||
if ((i+2) < self.length) {
|
||||
c = [self characterAtIndex:i+2];
|
||||
[mStr appendString:[NSString stringWithFormat:@"dk(%d)", c]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"dk(INVALID)"];
|
||||
}
|
||||
|
||||
i+=3;
|
||||
break;
|
||||
}
|
||||
case CODE_NULLCHAR: {
|
||||
if ((i+2) < self.length) {
|
||||
c = [self characterAtIndex:i+2];
|
||||
[mStr appendString:[NSString stringWithFormat:@"nc(%d)", c]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"nc(INVALID)"];
|
||||
}
|
||||
|
||||
i+=3;
|
||||
break;
|
||||
}
|
||||
case CODE_CONTEXT: {
|
||||
[mStr appendString:[NSString stringWithFormat:@"ctx()"]];
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
case CODE_CONTEXTEX: {
|
||||
if ((i+2) < self.length) {
|
||||
c = [self characterAtIndex:i+2];
|
||||
[mStr appendString:[NSString stringWithFormat:@"ctxex(%d)", c]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"ctxex(INVALID)"];
|
||||
}
|
||||
|
||||
i+=3;
|
||||
break;
|
||||
}
|
||||
case CODE_USE: {
|
||||
if ((i+2) < self.length) {
|
||||
c = [self characterAtIndex:i+2];
|
||||
[mStr appendString:[NSString stringWithFormat:@"use(%d)", c]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"use(INVALID)"];
|
||||
}
|
||||
|
||||
i+=3;
|
||||
break;
|
||||
}
|
||||
case CODE_BEEP: {
|
||||
[mStr appendString:[NSString stringWithFormat:@"beep()"]];
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
case CODE_RETURN: {
|
||||
[mStr appendString:[NSString stringWithFormat:@"return()"]];
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
case CODE_IFOPT: {
|
||||
if ((i+4) < self.length) {
|
||||
DWORD x1 = [self characterAtIndex:i+2];
|
||||
DWORD x2 = [self characterAtIndex:i+3];
|
||||
DWORD x3 = [self characterAtIndex:i+4];
|
||||
[mStr appendString:[NSString stringWithFormat:@"ifopt(%d %d %d)", x1, x2, x3]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"ifopt(INVALID)"];
|
||||
}
|
||||
|
||||
i+=5;
|
||||
break;
|
||||
}
|
||||
case CODE_IFSYSTEMSTORE: {
|
||||
if ((i+4) < self.length) {
|
||||
DWORD x1 = [self characterAtIndex:i+2];
|
||||
DWORD x2 = [self characterAtIndex:i+3];
|
||||
DWORD x3 = [self characterAtIndex:i+4];
|
||||
[mStr appendString:[NSString stringWithFormat:@"ifsys(%d %d %d)", x1, x2, x3]];
|
||||
}
|
||||
else {
|
||||
[mStr appendString:@"ifsys(INVALID)"];
|
||||
}
|
||||
|
||||
i+=5;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
[mStr appendString:@"other()"];
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
[mStr appendString:[NSString stringWithFormat:@"U+%x", c]];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return mStr;
|
||||
}
|
||||
|
||||
- (BOOL)isValidCode {
|
||||
if (self == nil)
|
||||
return NO;
|
||||
|
||||
for (int i=0; i < self.length;) {
|
||||
unichar c = [self characterAtIndex:i];
|
||||
if (c == UC_SENTINEL) {
|
||||
if ((i+1) < self.length)
|
||||
c = [self characterAtIndex:i+1];
|
||||
else
|
||||
return NO;
|
||||
|
||||
switch (c) {
|
||||
case CODE_ANY:
|
||||
case CODE_NOTANY:
|
||||
case CODE_DEADKEY:
|
||||
case CODE_NULLCHAR:
|
||||
case CODE_CONTEXTEX:
|
||||
case CODE_USE: {
|
||||
if ((i+2) >= self.length)
|
||||
return NO;
|
||||
|
||||
i+=3;
|
||||
break;
|
||||
}
|
||||
case CODE_INDEX: {
|
||||
if ((i+3) >= self.length)
|
||||
return NO;
|
||||
|
||||
i+=4;
|
||||
break;
|
||||
}
|
||||
case CODE_IFOPT:
|
||||
case CODE_IFSYSTEMSTORE: {
|
||||
if ((i+4) >= self.length)
|
||||
return NO;
|
||||
|
||||
i+=5;
|
||||
break;
|
||||
}
|
||||
case CODE_NUL:
|
||||
case CODE_CONTEXT:
|
||||
case CODE_BEEP:
|
||||
case CODE_RETURN:
|
||||
default:
|
||||
i+=2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)nullChar {
|
||||
return [NSString stringWithFormat:@"%C%C%d", UC_SENTINEL, CODE_NULLCHAR, 1];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSMutableString (XString)
|
||||
|
||||
- (void)appendDeadkey:(NSUInteger)index {
|
||||
NSString *dk = [NSString stringWithFormat:@"%C%C%C", UC_SENTINEL, CODE_DEADKEY, index];
|
||||
[self appendString:dk];
|
||||
}
|
||||
|
||||
- (void)appendNullChar {
|
||||
[self appendString:[[self class] nullChar]];
|
||||
}
|
||||
|
||||
- (NSUInteger)deleteLastDeadkeys {
|
||||
NSUInteger dkc = 0;
|
||||
if (self.length >= 3) {
|
||||
unichar c1 = [self characterAtIndex:self.length - 3];
|
||||
unichar c2 = [self characterAtIndex:self.length - 2];
|
||||
while (c1 == UC_SENTINEL && c2 == CODE_DEADKEY) {
|
||||
NSRange range = NSMakeRange(self.length - 3, 3);
|
||||
[self deleteCharactersInRange:range];
|
||||
dkc++;
|
||||
if (self.length < 3)
|
||||
break;
|
||||
c1 = [self characterAtIndex:self.length - 3];
|
||||
c2 = [self characterAtIndex:self.length - 2];
|
||||
}
|
||||
}
|
||||
|
||||
return dkc;
|
||||
}
|
||||
|
||||
- (NSUInteger)deleteLastNullChars {
|
||||
NSUInteger dsc = 0;
|
||||
if (self.length >= 3) {
|
||||
unichar c1 = [self characterAtIndex:self.length - 3];
|
||||
unichar c2 = [self characterAtIndex:self.length - 2];
|
||||
while (c1 == UC_SENTINEL && c2 == CODE_NULLCHAR) {
|
||||
NSRange range = NSMakeRange(self.length - 3, 3);
|
||||
[self deleteCharactersInRange:range];
|
||||
dsc++;
|
||||
if (self.length < 3)
|
||||
break;
|
||||
c1 = [self characterAtIndex:self.length - 3];
|
||||
c2 = [self characterAtIndex:self.length - 2];
|
||||
}
|
||||
}
|
||||
|
||||
return dsc;
|
||||
}
|
||||
|
||||
- (NSUInteger)removeAllNullChars {
|
||||
return [self replaceOccurrencesOfString:[[self class] nullChar] withString:@"" options:NSLiteralSearch range:NSMakeRange(0, self.length)];
|
||||
}
|
||||
|
||||
- (void)deleteLastNChars:(NSUInteger)n {
|
||||
if (n > 0) {
|
||||
if (self.length >= 3) {
|
||||
NSInteger len = 0;
|
||||
NSInteger chars = 0;
|
||||
for (NSInteger i = self.length - 1; i >= 0;) {
|
||||
NSInteger index = i - 2;
|
||||
if (index >= 0) {
|
||||
unichar c1 = [self characterAtIndex:index];
|
||||
unichar c2 = [self characterAtIndex:index+1];
|
||||
if (c1 == UC_SENTINEL && (c2 == CODE_DEADKEY || c2 == CODE_NULLCHAR)) {
|
||||
i -= 3;
|
||||
len += 3;
|
||||
}
|
||||
else {
|
||||
i--;
|
||||
len++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
i--;
|
||||
len++;
|
||||
}
|
||||
|
||||
chars++;
|
||||
if (chars == n)
|
||||
break;
|
||||
}
|
||||
|
||||
[self deleteCharactersInRange:NSMakeRange(self.length - len, len)];
|
||||
}
|
||||
else {
|
||||
if (self.length >= n)
|
||||
[self deleteCharactersInRange:NSMakeRange(self.length - n, n)];
|
||||
else
|
||||
[self deleteCharactersInRange:NSMakeRange(0, self.length)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -6,12 +6,18 @@
|
|||
// Copyright (c) 2017 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
* Note that many of the constants defined here are also defined in the common header`kmx_file.h`
|
||||
* Keyman for Mac only reads the kmx file to pass the contents to Keyman Core and for meta data.
|
||||
* If the meta data can instead be returned by Keyman Core in the future, then the duplicate definitions
|
||||
* found here can be removed.
|
||||
*/
|
||||
|
||||
#ifndef KMBinaryFileFormat_h
|
||||
#define KMBinaryFileFormat_h
|
||||
|
||||
typedef uint16_t WORD;
|
||||
typedef uint32_t DWORD;
|
||||
//typedef wchar_t* PWSTR;
|
||||
|
||||
struct COMP_KEYBOARD {
|
||||
DWORD dwIdentifier; // 0000 Keyman compiled keyboard id
|
||||
|
|
@ -49,15 +55,6 @@ struct COMP_KEYBOARD {
|
|||
#define VERSION_MIN VERSION_50
|
||||
#define VERSION_MAX VERSION_170
|
||||
|
||||
struct COMP_GROUP {
|
||||
DWORD dpName; // string (only debug)
|
||||
DWORD dpKeyArray; // [LPCOMP_KEY] address of first item in key array
|
||||
DWORD dpMatch; // extended string
|
||||
DWORD dpNoMatch; // extended string
|
||||
DWORD cxKeyArray; // in array entries
|
||||
BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not
|
||||
};
|
||||
|
||||
struct COMP_STORE {
|
||||
DWORD dwSystemID;
|
||||
DWORD dpName; // string (only debug)
|
||||
|
|
@ -125,78 +122,6 @@ struct COMP_STORE {
|
|||
|
||||
#define TSS__MAX 43
|
||||
|
||||
// ShiftFlags
|
||||
#define LCTRLFLAG 0x0001 // Left Control flag
|
||||
#define RCTRLFLAG 0x0002 // Right Control flag
|
||||
#define LALTFLAG 0x0004 // Left Alt flag
|
||||
#define RALTFLAG 0x0008 // Right Alt flag
|
||||
#define K_SHIFTFLAG 0x0010 // Either shift flag
|
||||
#define K_CTRLFLAG 0x0020 // Either ctrl flag
|
||||
#define K_ALTFLAG 0x0040 // Either alt flag
|
||||
//#define 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
|
||||
#define CAPITALFLAG 0x0100 // Caps lock on
|
||||
#define NOTCAPITALFLAG 0x0200 // Caps lock NOT on
|
||||
#define NUMLOCKFLAG 0x0400 // Num lock on
|
||||
#define NOTNUMLOCKFLAG 0x0800 // Num lock NOT on
|
||||
#define SCROLLFLAG 0x1000 // Scroll lock on
|
||||
#define NOTSCROLLFLAG 0x2000 // Scroll lock NOT on
|
||||
#define ISVIRTUALKEY 0x4000 // It is a Virtual Key Sequence
|
||||
#define VIRTUALCHARKEY 0x8000 // Keyman 6.0: Virtual Key Cap Sequence
|
||||
#define K_MODIFIERFLAG 0x007F
|
||||
#define K_CAPITALMASK (CAPITALFLAG|NOTCAPITALFLAG)
|
||||
|
||||
struct COMP_KEY {
|
||||
WORD Key; // Windows VK code or character value (VIRTUALCHARKEY, ISVIRTUALKEY)
|
||||
DWORD Line;
|
||||
DWORD ShiftFlags;
|
||||
DWORD dpOutput; // extended string
|
||||
DWORD dpContext; // extended string
|
||||
};
|
||||
|
||||
#define UC_SENTINEL 0xFFFF
|
||||
#define UC_SENTINEL_EXTENDEDEND 0x10
|
||||
|
||||
#define CODE_ANY 0x01
|
||||
#define CODE_INDEX 0x02
|
||||
#define CODE_CONTEXT 0x03
|
||||
#define CODE_NUL 0x04
|
||||
#define CODE_USE 0x05
|
||||
#define CODE_RETURN 0x06
|
||||
#define CODE_BEEP 0x07
|
||||
#define CODE_DEADKEY 0x08
|
||||
// 0x09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL
|
||||
#define CODE_EXTENDED 0x0A
|
||||
//#define CODE_EXTENDEDEND 0x0B deprecated
|
||||
#define CODE_SWITCH 0x0C
|
||||
#define CODE_KEY 0x0D
|
||||
#define CODE_CLEARCONTEXT 0x0E
|
||||
#define CODE_CALL 0x0F
|
||||
// UC_SENTINEL_EXTENDEDEND 0x10
|
||||
#define CODE_CONTEXTEX 0x11
|
||||
#define CODE_NOTANY 0x12
|
||||
#define CODE_KEYMAN70_LASTCODE 0x12
|
||||
|
||||
#define CODE_SETOPT 0x13
|
||||
#define CODE_IFOPT 0x14
|
||||
#define CODE_SAVEOPT 0x15
|
||||
#define CODE_RESETOPT 0x16
|
||||
#define CODE_KEYMAN80_LASTCODE 0x16
|
||||
|
||||
/* Keyman 9.0 codes */
|
||||
#define CODE_IFSYSTEMSTORE 0x17
|
||||
#define CODE_SETSYSTEMSTORE 0x18
|
||||
#define CODE_LASTCODE 0x18
|
||||
|
||||
/* Keyman4Mac codes */
|
||||
#define CODE_NULLCHAR 0x20
|
||||
|
||||
|
||||
#define NOT_EQUAL 0
|
||||
#define EQUAL 1
|
||||
|
||||
/* KVK file format definitions */
|
||||
|
||||
// KVK header flags
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// KMCompGroup.h
|
||||
// KeymanEngine4Mac
|
||||
//
|
||||
// Created by Serkan Kurt on 28/11/2014.
|
||||
// Copyright (c) 2017 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef KMCompGroup_h
|
||||
#define KMCompGroup_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "KMBinaryFileFormat.h"
|
||||
|
||||
@interface KMCompGroup : NSObject
|
||||
|
||||
@property (strong, nonatomic) NSString *name;
|
||||
@property (strong, nonatomic) NSMutableArray *keys;
|
||||
@property (strong, nonatomic) NSString *match;
|
||||
@property (strong, nonatomic) NSString *noMatch;
|
||||
@property (assign, nonatomic) BOOL fUsingKeys;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* KMCompGroup_h */
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// KMCompGroup.m
|
||||
// KeymanEngine4Mac
|
||||
//
|
||||
// Created by Serkan Kurt on 28/11/2014.
|
||||
// Copyright (c) 2017 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
#import "KMCompGroup.h"
|
||||
#import "NSString+XString.h"
|
||||
|
||||
@implementation KMCompGroup
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = @"";
|
||||
_keys = [[NSMutableArray alloc] initWithCapacity:0];
|
||||
_match = @"";
|
||||
_noMatch = @"";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
NSString *format = @"<%@:%p, Name:%@ Match:%@ NoMatch:%@ fUsingKeys:%d Keys:%@>";
|
||||
NSString *str = [NSString stringWithFormat:format,
|
||||
[self className], self, _name, [_match codeString], [_noMatch codeString], _fUsingKeys, _keys];
|
||||
return str;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// KMCompKey.h
|
||||
// KeymanEngine4Mac
|
||||
//
|
||||
// Created by Serkan Kurt on 28/11/2014.
|
||||
// Copyright (c) 2017 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef KMCompKey_h
|
||||
#define KMCompKey_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "KMBinaryFileFormat.h"
|
||||
|
||||
@interface KMCompKey : NSObject
|
||||
|
||||
@property (assign, nonatomic) WORD key;
|
||||
@property (assign, nonatomic) DWORD line;
|
||||
@property (assign, nonatomic) DWORD shiftFlags;
|
||||
@property (strong, nonatomic) NSString *output;
|
||||
@property (strong, nonatomic) NSString *context;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* KMCompKey_h */
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
//
|
||||
// KMCompKey.m
|
||||
// KeymanEngine4Mac
|
||||
//
|
||||
// Created by Serkan Kurt on 28/11/2014.
|
||||
// Copyright (c) 2017 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
#import "KMCompKey.h"
|
||||
#import "NSString+XString.h"
|
||||
|
||||
@implementation KMCompKey
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_output = @"";
|
||||
_context = @"";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
NSString *format = @"<%@:%p K:0x%X L:0x%X F:0x%X O:%@ C:%@>";
|
||||
NSString *str = [NSString stringWithFormat:format,
|
||||
self.className, self, self.key, self.line, self.shiftFlags, [self.output codeString], [self.context codeString]];
|
||||
return str;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -8,10 +8,6 @@
|
|||
|
||||
#import "KMEngine.h"
|
||||
#import "KMBinaryFileFormat.h"
|
||||
#import "KMCompStore.h"
|
||||
#import "KMCompGroup.h"
|
||||
#import "KMCompKey.h"
|
||||
#import "NSString+XString.h"
|
||||
#import "WindowsVKCodes.h"
|
||||
#import "MacVKCodes.h"
|
||||
#import "CoreWrapper.h"
|
||||
|
|
|
|||
|
|
@ -27,17 +27,10 @@ extern NSString *const kKMVisualKeyboardKey;
|
|||
@property (assign, nonatomic, readonly) BOOL isRegistered;
|
||||
@property (assign, nonatomic, readonly) BOOL isMnemonic;
|
||||
@property (assign, nonatomic, readonly) DWORD version;
|
||||
@property (strong, nonatomic, readonly) NSArray *store;
|
||||
@property (strong, nonatomic, readonly) NSArray *storeSaved;
|
||||
@property (strong, nonatomic, readonly) NSArray *group;
|
||||
@property (strong, nonatomic, readonly) NSArray *startGroup;
|
||||
@property (assign, nonatomic, readonly) DWORD flags;
|
||||
@property (assign, nonatomic, readonly) DWORD hotKey;
|
||||
@property (strong, nonatomic, readonly) NSImage *bitmap;
|
||||
@property (strong, nonatomic, readonly) NSString *filePath;
|
||||
|
||||
- (id)initWithFilePath:(NSString *)path;
|
||||
- (BOOL)isValid;
|
||||
|
||||
/*!
|
||||
* Provides an info dictionary from a KMX file which includes keyboard name, version, copyright information and keyboard icon.
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@
|
|||
|
||||
#import "KMXFile.h"
|
||||
#import "KMCompStore.h"
|
||||
#import "KMCompGroup.h"
|
||||
#import "KMCompKey.h"
|
||||
#import "NSString+XString.h"
|
||||
#import "KMELogs.h"
|
||||
|
||||
NSString *const kKMKeyboardNameKey = @"KMKeyboardNameKey";
|
||||
|
|
@ -22,6 +19,7 @@ NSString *const kKMVisualKeyboardKey = @"KMVisualKeyboardKey";
|
|||
@implementation KMXFile
|
||||
|
||||
- (id)initWithFilePath:(NSString *)path {
|
||||
os_log_info([KMELogs configLog], "initWithFilePath, path: %{public}@", path);
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (path == nil) {
|
||||
|
|
@ -29,7 +27,6 @@ NSString *const kKMVisualKeyboardKey = @"KMVisualKeyboardKey";
|
|||
return nil;
|
||||
}
|
||||
|
||||
|
||||
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path];
|
||||
if (file == nil) {
|
||||
os_log_error([KMELogs configLog], "Failed to open kmx file");
|
||||
|
|
@ -56,22 +53,45 @@ NSString *const kKMVisualKeyboardKey = @"KMVisualKeyboardKey";
|
|||
_keyboardID = cmp_kb.KeyboardID;
|
||||
_isRegistered = (cmp_kb.IsRegistered == 0?NO:YES);
|
||||
_version = cmp_kb.version;
|
||||
NSMutableArray *mStartGroup = [[NSMutableArray alloc] initWithCapacity:2];
|
||||
[mStartGroup addObject:[NSNumber numberWithInt:cmp_kb.StartGroup[0]]];
|
||||
[mStartGroup addObject:[NSNumber numberWithInt:cmp_kb.StartGroup[1]]];
|
||||
_startGroup = [[NSArray alloc] initWithArray:mStartGroup];
|
||||
_flags = cmp_kb.dwFlags;
|
||||
_hotKey = cmp_kb.dwHotKey;
|
||||
|
||||
// stores not used at init time
|
||||
//NSArray *systemStores = [self readSystemStores:cmp_kb file: file];
|
||||
|
||||
[file seekToFileOffset:cmp_kb.dpBitmapOffset];
|
||||
NSData *bitmapData = [file readDataOfLength:cmp_kb.dwBitmapSize];
|
||||
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:bitmapData];
|
||||
_bitmap = [[NSImage alloc] initWithCGImage:imageRep.CGImage size:NSMakeSize(CGImageGetWidth(imageRep.CGImage), CGImageGetHeight(imageRep.CGImage))];
|
||||
|
||||
struct COMP_STORE cmp_str[cmp_kb.cxStoreArray];
|
||||
[file seekToFileOffset:cmp_kb.dpStoreArray];
|
||||
size = sizeof(cmp_str);
|
||||
dataBuffer = [file readDataOfLength:size];
|
||||
[dataBuffer getBytes:cmp_str length:size];
|
||||
NSMutableArray *mStore = [[NSMutableArray alloc] initWithCapacity:cmp_kb.cxStoreArray];
|
||||
for (int i = 0; i < cmp_kb.cxStoreArray; i++) {
|
||||
KMCompStore *kmStore = [[KMCompStore alloc] init];
|
||||
kmStore.dwSystemID = cmp_str[i].dwSystemID;
|
||||
[file closeFile];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the system stores from the kmx file into an array.
|
||||
* Skips over all the normal stores as Keyman for Mac does not process keys but only needs the
|
||||
* meta data contained in system stores.
|
||||
*/
|
||||
- (NSArray *) readSystemStores:(struct COMP_KEYBOARD)cmp_kb file: (NSFileHandle *) file {
|
||||
struct COMP_STORE cmp_str[cmp_kb.cxStoreArray];
|
||||
[file seekToFileOffset:cmp_kb.dpStoreArray];
|
||||
size_t size = sizeof(cmp_str);
|
||||
NSData *dataBuffer = [file readDataOfLength:size];
|
||||
[dataBuffer getBytes:cmp_str length:size];
|
||||
|
||||
NSMutableArray *systemStoreArray = [[NSMutableArray alloc] initWithCapacity:cmp_kb.cxStoreArray];
|
||||
|
||||
os_log_info([KMELogs configLog], "COMP_STORE, number of entries: %u", cmp_kb.cxStoreArray);
|
||||
for (int i = 0; i < cmp_kb.cxStoreArray; i++) {
|
||||
KMCompStore *kmStore = [[KMCompStore alloc] init];
|
||||
kmStore.dwSystemID = cmp_str[i].dwSystemID;
|
||||
|
||||
/* Keyman for Mac is not processing keys, so no need to save normal stores.
|
||||
If dwSystemID is not zero, then this is a system store and may contain
|
||||
useful metadata */
|
||||
|
||||
if (kmStore.dwSystemID != 0) {
|
||||
kmStore.name = [KMXFile UTF16StringWithPointer:cmp_str[i].dpName inFile:file];
|
||||
|
||||
if (kmStore.dwSystemID == TSS_VERSION)
|
||||
|
|
@ -87,79 +107,23 @@ NSString *const kKMVisualKeyboardKey = @"KMVisualKeyboardKey";
|
|||
else
|
||||
kmStore.string = [KMXFile UTF16StringWithPointer:cmp_str[i].dpString inFile:file];
|
||||
|
||||
[mStore addObject:kmStore];
|
||||
[systemStoreArray addObject:kmStore];
|
||||
os_log_info([KMELogs configLog], "loaded store, systemId: %u name: %{public}@ string: %{public}@", kmStore.dwSystemID, kmStore.name, kmStore.string);
|
||||
}
|
||||
|
||||
_store = [[NSArray alloc] initWithArray:mStore];
|
||||
_storeSaved = [[NSArray alloc] initWithArray:mStore copyItems:YES];
|
||||
|
||||
struct COMP_GROUP cmp_grp[cmp_kb.cxGroupArray];
|
||||
[file seekToFileOffset:cmp_kb.dpGroupArray];
|
||||
size = sizeof(cmp_grp);
|
||||
dataBuffer = [file readDataOfLength:size];
|
||||
[dataBuffer getBytes:cmp_grp length:size];
|
||||
|
||||
NSMutableArray *mGroup = [[NSMutableArray alloc] initWithCapacity:cmp_kb.cxGroupArray];
|
||||
for (int i = 0; i < cmp_kb.cxGroupArray; i++) {
|
||||
KMCompGroup *kmGrp = [[KMCompGroup alloc] init];
|
||||
kmGrp.name = [KMXFile UTF16StringWithPointer:cmp_grp[i].dpName inFile:file];
|
||||
kmGrp.match = [KMXFile UTF16StringWithPointer:cmp_grp[i].dpMatch inFile:file];
|
||||
kmGrp.noMatch = [KMXFile UTF16StringWithPointer:cmp_grp[i].dpNoMatch inFile:file];
|
||||
kmGrp.fUsingKeys = cmp_grp[i].fUsingKeys;
|
||||
|
||||
struct COMP_KEY cmp_keys[cmp_grp[i].cxKeyArray];
|
||||
[file seekToFileOffset:cmp_grp[i].dpKeyArray];
|
||||
size = sizeof(cmp_keys);
|
||||
dataBuffer = [file readDataOfLength:size];
|
||||
[dataBuffer getBytes:cmp_keys length:size];
|
||||
|
||||
for (int j = 0; j < cmp_grp[i].cxKeyArray; j++) {
|
||||
KMCompKey *kmKey = [[KMCompKey alloc] init];
|
||||
kmKey.key = cmp_keys[j].Key;
|
||||
kmKey.line = cmp_keys[j].Line;
|
||||
kmKey.shiftFlags = cmp_keys[j].ShiftFlags;
|
||||
kmKey.output = [KMXFile UTF16StringWithPointer:cmp_keys[j].dpOutput inFile:file];
|
||||
kmKey.context = [KMXFile UTF16StringWithPointer:cmp_keys[j].dpContext inFile:file];
|
||||
[kmGrp.keys addObject:kmKey];
|
||||
}
|
||||
|
||||
[mGroup addObject:kmGrp];
|
||||
}
|
||||
_group = [[NSArray alloc] initWithArray:mGroup];
|
||||
|
||||
[file seekToFileOffset:cmp_kb.dpBitmapOffset];
|
||||
NSData *bitmapData = [file readDataOfLength:cmp_kb.dwBitmapSize];
|
||||
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:bitmapData];
|
||||
_bitmap = [[NSImage alloc] initWithCGImage:imageRep.CGImage size:NSMakeSize(CGImageGetWidth(imageRep.CGImage), CGImageGetHeight(imageRep.CGImage))];
|
||||
|
||||
[file closeFile];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
return systemStoreArray;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
NSString *format = @"<%@: %p, File version: 0x%X Flags: 0x%X Store: %@ Group: %@>";
|
||||
NSString *format = @"<%@: %p, File version: 0x%X>";
|
||||
NSString *str = [NSString stringWithFormat:format,
|
||||
self.className, self, self.fileVersion, self.flags, self.store, self.group];
|
||||
self.className, self, self.fileVersion];
|
||||
return str;
|
||||
}
|
||||
|
||||
- (BOOL)isValid {
|
||||
for (KMCompGroup *gp in self.group) {
|
||||
if ((gp.match && ![gp.match isValidCode]) || (gp.noMatch && ![gp.noMatch isValidCode]))
|
||||
return NO;
|
||||
|
||||
for (KMCompKey *kmKey in gp.keys) {
|
||||
if (![kmKey.context isValidCode] || ![kmKey.output isValidCode])
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSDictionary *)keyboardInfoFromKmxFile:(NSString *)path {
|
||||
os_log_info([KMELogs configLog], "keyboardInfoFromKmxFile, path: %{public}@", path);
|
||||
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path];
|
||||
|
||||
if (file == nil) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ FOUNDATION_EXPORT const unsigned char KeymanEngine4MacVersionString[];
|
|||
#import <KeymanEngine4Mac/KVKFile.h>
|
||||
#import <KeymanEngine4Mac/KMXFile.h>
|
||||
#import <KeymanEngine4Mac/NFont.h>
|
||||
#import <KeymanEngine4Mac/NSString+XString.h>
|
||||
#import <KeymanEngine4Mac/CoreWrapper.h>
|
||||
#import <KeymanEngine4Mac/CoreHelper.h>
|
||||
#import <KeymanEngine4Mac/CoreAction.h>
|
||||
|
|
|
|||
|
|
@ -36,10 +36,4 @@
|
|||
XCTAssert(kmxFile == nil, @"Expected nil file");
|
||||
}
|
||||
|
||||
- (void)testinitWithFilePath_Valid_ReturnsInitializedKmxFile {
|
||||
KMXFile *kmxFile = [KeymanEngineTestsStaticHelperMethods getKmxFileTestMacEngine];
|
||||
XCTAssert(kmxFile != nil, @"Expected non-nil file");
|
||||
XCTAssert([kmxFile isValid], @"Expected valid KMX file.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ android {
|
|||
compileSdk 34
|
||||
namespace="com.firstvoices.keyboards"
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.firstvoices.keyboards"
|
||||
minSdkVersion 21
|
||||
|
|
@ -66,12 +70,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(11))
|
||||
}
|
||||
}
|
||||
|
||||
// how to configure the sentry android gradle plugin
|
||||
task publishSentry {
|
||||
doLast {
|
||||
|
|
@ -124,10 +122,10 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api(name: 'keyman-engine', ext: 'aar')
|
||||
implementation 'io.sentry:sentry-android:6.9.2'
|
||||
implementation 'io.sentry:sentry-android:7.8.0'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'io.sentry:sentry-android-gradle-plugin:2.1.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
classpath 'io.sentry:sentry-android-gradle-plugin:4.6.0'
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ builder_parse "$@"
|
|||
if builder_is_debug_build; then
|
||||
builder_heading "### Debug config ####"
|
||||
CONFIG="debug"
|
||||
BUILD_FLAGS="assembleDebug -x lint -x test"
|
||||
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
|
||||
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -19,4 +19,8 @@
|
|||
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -341,7 +341,7 @@
|
|||
"dependencies": {
|
||||
"@keymanapp/common-types": "*",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"fast-xml-parser": "^5.0.9",
|
||||
"fast-xml-parser": "^5.2.2",
|
||||
"path-browserify": "^1.0.1",
|
||||
"restructure": "^3.0.1",
|
||||
"sax": ">=0.6.0",
|
||||
|
|
@ -9124,9 +9124,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.0.9.tgz",
|
||||
"integrity": "sha512-2mBwCiuW3ycKQQ6SOesSB8WeF+fIGb6I/GG5vU5/XEptwFFhp9PE8b9O7fbs2dpq9fXn4ULR3UsfydNUCntf5A==",
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.2.tgz",
|
||||
"integrity": "sha512-ZaCmslH75Jkfowo/x44Uq8KT5SutC5BFxHmY61nmTXPccw11PVuIXKUqC2hembMkJ3nPwTkQESXiUlsKutCbMg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -9135,7 +9135,7 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^2.0.5"
|
||||
"strnum": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
|
|
@ -13993,9 +13993,9 @@
|
|||
"link": true
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.0.5.tgz",
|
||||
"integrity": "sha512-YAT3K/sgpCUxhxNMrrdhtod3jckkpYwH6JAuwmUdXZsmzH1wUyzTMrrK2wYCEEqlKwrWDd35NeuUkbBy/1iK+Q==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.0.tgz",
|
||||
"integrity": "sha512-w0S//9BqZZGw0L0Y8uLSelFGnDJgTyyNQLmSlPnVz43zPAiqu3w4t8J8sDqqANOGeZIZ/9jWuPguYcEnsoHv4A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
|
|||
|
|
@ -330,11 +330,22 @@ set_keyman_standard_build_path() {
|
|||
PATH="$KEYMAN_ROOT/node_modules/.bin:$PATH"
|
||||
}
|
||||
|
||||
# For CI compatbility of building Keyman for Android 16.0 with OpenJDK 8,
|
||||
# this overrides JAVA_HOME for the builder script to use OpenJDK 11.
|
||||
# For CI compatibility of building Keyman for Android 18.0 with OpenJDK 11,
|
||||
# this overrides JAVA_HOME for the builder script to use OpenJDK 21.
|
||||
set_java_home() {
|
||||
set_java_home_21
|
||||
}
|
||||
|
||||
set_java_home_21() {
|
||||
if [[ ! -z ${JAVA_HOME_21+x} ]]; then
|
||||
builder_echo "Setting JAVA_HOME to JAVA_HOME_21 (${JAVA_HOME_21})"
|
||||
export JAVA_HOME="${JAVA_HOME_21}"
|
||||
fi
|
||||
}
|
||||
|
||||
set_java_home_11() {
|
||||
if [[ ! -z ${JAVA_HOME_11+x} ]]; then
|
||||
builder_echo "Setting JAVA_HOME to JAVA_HOME_11 ($JAVA_HOME_11)"
|
||||
builder_echo "Setting JAVA_HOME to JAVA_HOME_11 (${JAVA_HOME_11})"
|
||||
export JAVA_HOME="${JAVA_HOME_11}"
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,13 +31,13 @@ KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.58 # Use KEYMAN_USE_EMSDK to automati
|
|||
KEYMAN_MIN_VERSION_VISUAL_STUDIO=2019
|
||||
KEYMAN_MIN_VERSION_MESON=1.0.0
|
||||
|
||||
KEYMAN_VERSION_GRADLE=7.6.4 # See /android/KMEA/gradle/wrapper/gradle-wrapper.properties
|
||||
KEYMAN_VERSION_GRADLE=8.12 # See /android/KMEA/gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
# Dependencies with fixed versions
|
||||
KEYMAN_VERSION_ICU=73.1 # See /core/subprojects/icu-minimal.wrap
|
||||
|
||||
# Language and runtime versions
|
||||
KEYMAN_VERSION_JAVA=11 # We're using Java/OpenJDK 11
|
||||
KEYMAN_VERSION_JAVA=21 # We're using Java/OpenJDK 21
|
||||
KEYMAN_MIN_VERSION_CPP=17 # C++17
|
||||
KEYMAN_MIN_VERSION_ANDROID_SDK=21
|
||||
|
||||
|
|
|
|||
|
|
@ -67,14 +67,6 @@ run_web() {
|
|||
"${builder_extra_params[@]}"
|
||||
}
|
||||
|
||||
if [[ -z "${DISTRO_VERSION:-}" ]]; then
|
||||
image_version=default
|
||||
build_dir=default
|
||||
else
|
||||
image_version="${DISTRO:-}-${DISTRO_VERSION}-java${KEYMAN_VERSION_JAVA}-node$(_print_expected_node_version)-emsdk${KEYMAN_MIN_VERSION_EMSCRIPTEN}"
|
||||
build_dir="${DISTRO:-}-${DISTRO_VERSION}"
|
||||
fi
|
||||
|
||||
mkdir -p "${KEYMAN_ROOT}/core/build/docker-core/${build_dir}"
|
||||
|
||||
builder_run_action android run_android
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue