diff --git a/HISTORY.md b/HISTORY.md index 21cceb07eb..3f6bdeda65 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,21 @@ # Keyman Version History +## 18.0.51 alpha 2024-06-07 + +* fix(web): fix osk touch-focus tracking (#11705) +* fix(web): defer keyboard activation requests made during engine initialization (#11713) +* chore(developer): add context/character-map (#11656) +* chore(developer): add context/wordlist-editor (#11658) +* chore(developer): add context/new-model-project-parameters (#11677) +* fix(common): remove allowJs from web's tsconfig.base.json (#11718) +* change(web): precompile all TS-based tests (#11723) +* chore(developer): add extra logging for assertion failure when pressing backspace in debugger (#11707) +* chore: add cherry-pick information in commit messages (#11708) +* fix(developer): handle encoding errors when loading wordlists (#11711) +* chore(ios): remove dead Swift-side keyboard gesture code (#11672) +* fix(mac): change build configuration to prevent cycle error in Xcode 15 (#11730) +* refactor(web): Replace deprecated substr with substring (#11637) + ## 18.0.50 alpha 2024-06-06 * chore(common): adds retry mechanism for build script npm ci calls (#11451) diff --git a/VERSION.md b/VERSION.md index dd6ab3716f..7f0a827856 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.51 \ No newline at end of file +18.0.52 \ No newline at end of file diff --git a/common/predictive-text/src/web/sourcemappedWorker.ts b/common/predictive-text/src/web/sourcemappedWorker.ts index da7ed77eb9..18a89ab05f 100644 --- a/common/predictive-text/src/web/sourcemappedWorker.ts +++ b/common/predictive-text/src/web/sourcemappedWorker.ts @@ -2,6 +2,9 @@ import unwrap from '../unwrap.js'; import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.js"; export default class SourcemappedWorker { + // the only difference to DefaultWorker is that this class uses + // the unminified LM* blobs + static constructInstance(): Worker { return new Worker(this.asBlobURI(LMLayerWorkerCode)); } diff --git a/core/meson.build b/core/meson.build index 0049c95741..4a9ea257ea 100644 --- a/core/meson.build +++ b/core/meson.build @@ -13,7 +13,7 @@ project('keyman_core', 'cpp', 'c', 'b_vscrt=static_from_buildtype', 'warning_level=2', 'debug=true'], - meson_version: '>=0.57.0') + meson_version: '>=1.0') # Import our standard compiler defines; this is copied from # /resources/build/standard.meson.build by build.sh, because diff --git a/core/tests/meson.build b/core/tests/meson.build index e2e2997582..84c17d4220 100644 --- a/core/tests/meson.build +++ b/core/tests/meson.build @@ -21,7 +21,7 @@ if get_option('keyman_core_tests') if get_option('default_library') != 'static' ctypes_void_p_size = ['-c', 'import ctypes; print(ctypes.sizeof(ctypes.c_void_p))'] - r = run_command(python, ctypes_void_p_size) + r = run_command(python, ctypes_void_p_size, check: true) python_ctypes_compatible = r.stdout().to_int() == cpp_compiler.sizeof('void *') if not python_ctypes_compatible message('Python ctypes is incompatible with built shared object. Disabling some tests.') diff --git a/core/tests/unit/kmx/cat.bat b/core/tests/unit/kmx/cat.bat new file mode 100644 index 0000000000..a5568e3919 --- /dev/null +++ b/core/tests/unit/kmx/cat.bat @@ -0,0 +1,4 @@ +@echo off +set infile=%1 +set infileb=%infile:/=\% +type %infileb% diff --git a/core/tests/unit/kmx/fixtures/binary/meson.build b/core/tests/unit/kmx/fixtures/binary/meson.build index 46b0e78d31..cf96bc75ce 100644 --- a/core/tests/unit/kmx/fixtures/binary/meson.build +++ b/core/tests/unit/kmx/fixtures/binary/meson.build @@ -17,9 +17,9 @@ binary_tests = [ foreach kbd : binary_tests configure_file( - command: copy_cmd + ['@INPUT@', '@OUTPUT@'], input: kbd + '.kmn', - output: kbd + '.kmn' + output: kbd + '.kmn', + copy: true ) configure_file( diff --git a/core/tests/unit/kmx/meson.build b/core/tests/unit/kmx/meson.build index d244e162a5..548ab89522 100644 --- a/core/tests/unit/kmx/meson.build +++ b/core/tests/unit/kmx/meson.build @@ -89,10 +89,8 @@ kmc_root = meson.current_source_dir() / '../../../../developer/src/kmc/build/src kmc_cmd = [node, '--enable-source-maps', kmc_root] if build_machine.system() == 'windows' - copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] - cat_cmd = [find_program('cmd.exe', required: true), '/c', 'type'] + cat_cmd = [find_program(meson.current_source_dir() / 'cat.bat', required: true)] else - copy_cmd = [find_program('cp', required: true)] cat_cmd = [find_program('cat', required: true)] endif @@ -127,7 +125,7 @@ foreach kbd : tests kbd_src_path = common_test_keyboards_baseline / kbd + '.kmn' content = run_command( - cat_cmd, files(kbd_src_path), + cat_cmd, files(kbd_src_path), check: true, ).stdout().strip() cfg = configuration_data() diff --git a/core/tests/unit/ldml/invalid-keyboards/meson.build b/core/tests/unit/ldml/invalid-keyboards/meson.build index da8fb85ad0..e85646b68e 100644 --- a/core/tests/unit/ldml/invalid-keyboards/meson.build +++ b/core/tests/unit/ldml/invalid-keyboards/meson.build @@ -8,19 +8,13 @@ invalid_tests = [ 'ik_000_null_invalid' ] -if build_machine.system() == 'windows' - copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] -else - copy_cmd = [find_program('cp', required: true)] -endif - # Build all keyboards in output folder foreach kbd : invalid_tests configure_file( - command: copy_cmd + ['@INPUT@', '@OUTPUT@'], input: kbd + '.xml', - output: kbd + '.xml' + output: kbd + '.xml', + copy: true ) configure_file( diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 25d5d12ac1..15f458c587 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -56,8 +56,8 @@ tests += tests_from_cldr # Setup kmc -kmc_root = join_paths(meson.source_root(),'..','developer','src','kmc') -ldml_root = join_paths(meson.source_root(),'..','resources','standards-data','ldml-keyboards','45') +kmc_root = meson.global_source_root() / '../developer/src/kmc' +ldml_root = meson.global_source_root() / '../resources/standards-data/ldml-keyboards/45' ldml_data = join_paths(ldml_root, '3.0') ldml_testdata = join_paths(ldml_root, 'test') kmc_cmd = [node, '--enable-source-maps', kmc_root] diff --git a/core/tests/unit/ldml/meson.build b/core/tests/unit/ldml/meson.build index da60757750..f164e90f12 100644 --- a/core/tests/unit/ldml/meson.build +++ b/core/tests/unit/ldml/meson.build @@ -22,12 +22,6 @@ invalid_tests = [] # Setup copying of source files, used in child subdir calls -if build_machine.system() == 'windows' - copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] -else - copy_cmd = [find_program('cp', required: true)] -endif - if node.found() # Note: if node is not available, we cannot build the keyboards; build.sh # emits a warning that the ldml keyboard tests will be skipped diff --git a/core/tests/unit/meson.build b/core/tests/unit/meson.build index e3d9f76607..ee141cc12d 100644 --- a/core/tests/unit/meson.build +++ b/core/tests/unit/meson.build @@ -2,10 +2,10 @@ node = find_program('node', required: true) common_test_files = [ meson.current_source_dir() / 'emscripten_filesystem.cpp', - meson.source_root() / '../common/include/test_color.cpp' + meson.global_source_root() / '../common/include/test_color.cpp' ] -hextobin_root = join_paths(meson.source_root(),'..','common','tools','hextobin','build','hextobin.js') +hextobin_root = meson.global_source_root() / '../common/tools/hextobin/build/hextobin.js' hextobin_cmd = [node, hextobin_root] subdir('json') diff --git a/developer/src/kmcmplib/meson.build b/developer/src/kmcmplib/meson.build index c996981f47..d494e884d9 100644 --- a/developer/src/kmcmplib/meson.build +++ b/developer/src/kmcmplib/meson.build @@ -5,7 +5,7 @@ # project('kmcmplib', 'cpp', 'c', - version: run_command(find_program('getversion.bat', 'getversion.sh')).stdout().strip(), + version: run_command(find_program('getversion.bat', 'getversion.sh'), check: true).stdout().strip(), license: 'MIT', default_options : ['buildtype=release', 'cpp_std=c++14', diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index a7a603c7b3..8d0f11af8f 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -1208,7 +1208,11 @@ int GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store) { } KMX_BOOL IsValidKeyboardVersion(KMX_WCHAR *dpString) { // I4140 - /* version format \d+(\.\d+)* e.g. 9.0.3, 1.0, 1.2.3.4, 6.2.1.4.6.4, blank is not allowed */ + /** + version format: /^\d+(\.\d+)*$/ + e.g. 9.0.3, 1.0, 1.2.3.4, 6.2.1.4.6.4, 11.22.3 are all ok; + empty string is not permitted; whitespace is not permitted + */ do { if (!iswdigit(*dpString)) { diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 61b909a75e..ce9c27b488 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -105,7 +105,7 @@ if cpp_compiler.get_id() == 'emscripten' cpp_args: defns, include_directories: inc, link_args: links + lib_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep) if get_option('buildtype') == 'release' diff --git a/developer/src/kmcmplib/subprojects/.gitignore b/developer/src/kmcmplib/subprojects/.gitignore index 37346d9045..0c5134859c 100644 --- a/developer/src/kmcmplib/subprojects/.gitignore +++ b/developer/src/kmcmplib/subprojects/.gitignore @@ -2,4 +2,4 @@ /*.zip /*.tgz /packagecache -/googletest-1.14.0 +/googletest-* diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp new file mode 100644 index 0000000000..7d9dd97245 --- /dev/null +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -0,0 +1,460 @@ +#include +#include "..\include\kmcompx.h" +#include "..\include\kmcmplibapi.h" +#include "..\src\kmx_u16.h" +#include "..\src\compfile.h" +#include "..\src\CompMsg.h" +#include "..\..\common\include\kmn_compiler_errors.h" +#include "..\..\..\..\common\include\km_types.h" +#include "..\..\..\..\common\include\kmx_file.h" + +PKMX_WCHAR strtowstr(PKMX_STR in); +PKMX_STR wstrtostr(PKMX_WCHAR in); +KMX_BOOL AddCompileError(KMX_DWORD msg); +KMX_DWORD ProcessBeginLine(PFILE_KEYBOARD fk, PKMX_WCHAR p); +KMX_DWORD ValidateMatchNomatchOutput(PKMX_WCHAR p); +KMX_BOOL IsValidKeyboardVersion(KMX_WCHAR *dpString); +KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_WCHAR const * token, + PKMX_WCHAR output, int max, int offset, PKMX_WCHAR *newp, int isUnicode +); +KMX_DWORD GetRHS(PFILE_KEYBOARD fk, PKMX_WCHAR p, PKMX_WCHAR buf, int bufsize, int offset, int IsUnicode); +bool hasPreamble(std::u16string result); + +extern kmcmp_CompilerMessageProc msgproc; + +namespace kmcmp { + extern int nErrors; + extern int ErrChr; + extern int BeginLine[4]; + KMX_BOOL AddCompileWarning(char* buf); +} + +#define ERR_EXTRA_LIB_LEN 256 +extern char ErrExtraLIB[ERR_EXTRA_LIB_LEN]; + +class CompilerTest : public testing::Test { + protected: + FILE_KEYBOARD fileKeyboard; + + CompilerTest() {} + ~CompilerTest() override {} + void SetUp() override { + initGlobals(); + initFileKeyboard(fileKeyboard); + } + void TearDown() override { + deleteFileKeyboard(fileKeyboard); + } + + void initGlobals() { + msgproc = NULL; + szText_stub[0] = '\0'; + kmcmp::nErrors = 0; + kmcmp::ErrChr = 0; + ErrExtraLIB[0] = '\0'; + kmcmp::BeginLine[BEGIN_ANSI] = -1; + kmcmp::BeginLine[BEGIN_UNICODE] = -1; + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; + } + + void initFileKeyboard(FILE_KEYBOARD &fk) { + fk.KeyboardID = 0; + fk.version = VERSION_90; + fk.dpStoreArray = nullptr; + fk.dpGroupArray = nullptr; + fk.cxStoreArray = 0; + fk.cxGroupArray = 0; + fk.StartGroup[0] = 0; + fk.StartGroup[1] = 0; + fk.dwHotKey = 0; + fk.szName[0] = u'\0'; + fk.szLanguageName[0] = u'\0'; + fk.szCopyright[0] = u'\0'; + fk.szMessage[0] = u'\0'; + fk.lpBitmap = nullptr; + fk.dwBitmapSize = 0; + fk.dwFlags = 0; + fk.currentGroup = 0; + fk.currentStore = 0; + fk.cxDeadKeyArray = 0; + fk.dpDeadKeyArray = nullptr; + fk.cxVKDictionary = 0; + fk.dpVKDictionary = nullptr; + fk.extra = nullptr; + } + + void deleteFileKeyboard(FILE_KEYBOARD &fk) { + if (fk.dpStoreArray) { delete[] fk.dpStoreArray; } + if (fk.dpGroupArray) { delete[] fk.dpGroupArray; } + if (fk.lpBitmap) { delete fk.lpBitmap; } + if (fk.dpDeadKeyArray) { delete[] fk.dpDeadKeyArray; } + if (fk.dpVKDictionary) { delete fk.dpVKDictionary; } + if (fk.extra) { delete fk.extra; } + } + + public: + static KMX_CHAR szText_stub[]; + + static int msgproc_true_stub(int line, uint32_t dwMsgCode, const char* szText, void* context) { + strcpy(szText_stub, szText); + return 1; + }; + + static int msgproc_false_stub(int line, uint32_t dwMsgCode, const char* szText, void* context) { + strcpy(szText_stub, szText); + return 0; + }; +}; + +#define COMPILE_ERROR_MAX_LEN (SZMAX_ERRORTEXT + 1 + 280) +KMX_CHAR CompilerTest::szText_stub[COMPILE_ERROR_MAX_LEN]; + +TEST_F(CompilerTest, strtowstr_test) { + EXPECT_EQ(0, u16cmp(u"hello", strtowstr((PKMX_STR)"hello"))); + EXPECT_EQ(0, u16cmp(u"", strtowstr((PKMX_STR)""))); +}; + +TEST_F(CompilerTest, wstrtostr_test) { + EXPECT_EQ(0, strcmp("hello", wstrtostr((PKMX_WCHAR)u"hello"))); + EXPECT_EQ(0, strcmp("", wstrtostr((PKMX_WCHAR)u""))); +}; + +TEST_F(CompilerTest, AddCompileWarning_test) { + msgproc = msgproc_false_stub; + const char *const WARNING_TEXT = "warning"; + EXPECT_EQ(0, kmcmp::nErrors); + EXPECT_FALSE(kmcmp::AddCompileWarning((PKMX_CHAR)WARNING_TEXT)); + EXPECT_EQ(0, strcmp(WARNING_TEXT, szText_stub)); + EXPECT_EQ(0, kmcmp::nErrors); +}; + +TEST_F(CompilerTest, AddCompileError_test) { + msgproc = msgproc_true_stub; + kmcmp::ErrChr = 0; + ErrExtraLIB[0] = '\0'; + KMX_CHAR expected[COMPILE_ERROR_MAX_LEN]; + + // CERR_FATAL + EXPECT_EQ(0, kmcmp::nErrors); + EXPECT_EQ(CERR_FATAL, CERR_CannotCreateTempfile & CERR_FATAL); + EXPECT_TRUE(AddCompileError(CERR_CannotCreateTempfile)); + EXPECT_EQ(0, strcmp(GetCompilerErrorString(CERR_CannotCreateTempfile), szText_stub)); + EXPECT_EQ(1, kmcmp::nErrors); + + // CERR_ERROR + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_FALSE(AddCompileError(CERR_InvalidLayoutLine)); + EXPECT_EQ(0, strcmp(GetCompilerErrorString(CERR_InvalidLayoutLine), szText_stub)); + EXPECT_EQ(2, kmcmp::nErrors); + + // Unknown + const KMX_DWORD UNKNOWN_ERROR = 0x00004FFF; // top of range ERROR + EXPECT_EQ(CERR_ERROR, UNKNOWN_ERROR & CERR_ERROR); + EXPECT_FALSE(AddCompileError(UNKNOWN_ERROR)); + sprintf(expected, "Unknown error %x", UNKNOWN_ERROR); + EXPECT_EQ(0, strcmp(expected, szText_stub)); + EXPECT_EQ(3, kmcmp::nErrors); + + // ErrChr + const int ERROR_CHAR_INDEX = 42; + kmcmp::ErrChr = ERROR_CHAR_INDEX ; + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_FALSE(AddCompileError(CERR_InvalidLayoutLine)); + sprintf(expected, "%s character offset: %d", GetCompilerErrorString(CERR_InvalidLayoutLine), ERROR_CHAR_INDEX); + EXPECT_EQ(0, strcmp(expected, szText_stub)); + kmcmp::ErrChr = 0; + EXPECT_EQ(4, kmcmp::nErrors); + + // ErrExtraLIB + const char *const EXTRA_LIB_TEXT = " extra lib"; + strcpy(ErrExtraLIB, EXTRA_LIB_TEXT); + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_FALSE(AddCompileError(CERR_InvalidLayoutLine)); + sprintf(expected, "%s%s", GetCompilerErrorString(CERR_InvalidLayoutLine), EXTRA_LIB_TEXT); + EXPECT_EQ(0, strcmp(expected, szText_stub)); + ErrExtraLIB[0] = '\0'; + EXPECT_EQ(5, kmcmp::nErrors); + + // msgproc returns FALSE + msgproc = msgproc_false_stub; + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_TRUE(AddCompileError(CERR_InvalidLayoutLine)); + EXPECT_EQ(0, strcmp(GetCompilerErrorString(CERR_InvalidLayoutLine), szText_stub)); + EXPECT_EQ(6, kmcmp::nErrors); +}; + +TEST_F(CompilerTest, ProcessBeginLine_test) { + KMX_WCHAR str[LINESIZE]; + + // CERR_NoTokensFound + str[0] = '\0'; + EXPECT_EQ(CERR_NoTokensFound, ProcessBeginLine(&fileKeyboard, str)); + + // CERR_InvalidToken + u16cpy(str, u"abc >"); + EXPECT_EQ(CERR_InvalidToken, ProcessBeginLine(&fileKeyboard, str)); + + // CERR_RepeatedBegin, BEGIN_UNICODE + kmcmp::BeginLine[BEGIN_UNICODE] = 0; // not -1 + u16cpy(str, u" unicode>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_UNICODE] = -1; + + // CERR_RepeatedBegin, BEGIN_ANSI + kmcmp::BeginLine[BEGIN_ANSI] = 0; // not -1 + u16cpy(str, u" ansi>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_ANSI] = -1; + + // CERR_RepeatedBegin, BEGIN_NEWCONTEXT + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = 0; // not -1 + u16cpy(str, u" newContext>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; + + // CERR_RepeatedBegin, BEGIN_POSTKEYSTROKE + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = 0; // not -1 + u16cpy(str, u" postKeystroke>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; +}; + +TEST_F(CompilerTest, ValidateMatchNomatchOutput_test) { + EXPECT_EQ(CERR_None, ValidateMatchNomatchOutput(NULL)); + EXPECT_EQ(CERR_None, ValidateMatchNomatchOutput((PKMX_WCHAR)u"")); + const KMX_WCHAR context[] = { 'a', 'b', 'c', UC_SENTINEL, CODE_CONTEXT, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_ContextAndIndexInvalidInMatchNomatch, ValidateMatchNomatchOutput((PKMX_WCHAR)context)); + const KMX_WCHAR contextex[] = { 'a', 'b', 'c', UC_SENTINEL, CODE_CONTEXTEX, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_ContextAndIndexInvalidInMatchNomatch, ValidateMatchNomatchOutput((PKMX_WCHAR)contextex)); + const KMX_WCHAR index[] = { 'a', 'b', 'c', UC_SENTINEL, CODE_INDEX, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_ContextAndIndexInvalidInMatchNomatch, ValidateMatchNomatchOutput((PKMX_WCHAR)index)); + const KMX_WCHAR sentinel[] = { 'a', 'b', 'c', UC_SENTINEL, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_None, ValidateMatchNomatchOutput((PKMX_WCHAR)sentinel)); +}; + +// KMX_DWORD ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) +// KMX_DWORD ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// int kmcmp::cmpkeys(const void *key, const void *elem) +// KMX_DWORD ProcessGroupFinish(PFILE_KEYBOARD fk) +// KMX_DWORD ProcessStoreLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// bool resizeStoreArray(PFILE_KEYBOARD fk) +// bool resizeKeyArray(PFILE_GROUP gp, int increment) +// KMX_DWORD AddStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, const KMX_WCHAR * str, KMX_DWORD *dwStoreID) +// KMX_DWORD AddDebugStore(PFILE_KEYBOARD fk, KMX_WCHAR const * str) +// KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE sp) +// int GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store) + +TEST_F(CompilerTest, IsValidKeyboardVersion_test) { + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u" ")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"\t")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u" 1.1")); + EXPECT_TRUE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.1")); + EXPECT_TRUE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.0")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.")); + EXPECT_TRUE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.2.3")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"a")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.a")); +}; + +// KMX_DWORD kmcmp::AddCompilerVersionStore(PFILE_KEYBOARD fk) +// KMX_DWORD CheckStatementOffsets(PFILE_KEYBOARD fk, PFILE_GROUP gp, PKMX_WCHAR context, PKMX_WCHAR output, PKMX_WCHAR key) +// KMX_BOOL CheckContextStatementPositions(PKMX_WCHAR context) +// KMX_DWORD CheckUseStatementsInOutput(PKMX_WCHAR output) +// KMX_DWORD CheckVirtualKeysInOutput(PKMX_WCHAR output) +// KMX_DWORD InjectContextToReadonlyOutput(PKMX_WCHAR pklOut) +// KMX_DWORD CheckOutputIsReadonly(const PFILE_KEYBOARD fk, const PKMX_WCHAR output) +// KMX_DWORD ProcessKeyLine(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnicode) +// KMX_DWORD ProcessKeyLineImpl(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnicode, PKMX_WCHAR pklIn, PKMX_WCHAR pklKey, PKMX_WCHAR pklOut) +// KMX_DWORD ExpandKp_ReplaceIndex(PFILE_KEYBOARD fk, PFILE_KEY k, KMX_DWORD keyIndex, int nAnyIndex) +// KMX_DWORD ExpandKp(PFILE_KEYBOARD fk, PFILE_KEY kpp, KMX_DWORD storeIndex) +// PKMX_WCHAR GetDelimitedString(PKMX_WCHAR *p, KMX_WCHAR const * Delimiters, KMX_WORD Flags) +// LinePrefixType GetLinePrefixType(PKMX_WCHAR *p) +// int LineTokenType(PKMX_WCHAR *str) +// KMX_BOOL StrValidChrs(PKMX_WCHAR q, KMX_WCHAR const * chrs) +// KMX_DWORD GetXString(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_WCHAR const * token, +// PKMX_WCHAR output, int max, int offset, PKMX_WCHAR *newp, int /*isVKey*/, int isUnicode +// ) + +TEST_F(CompilerTest, GetXStringImpl_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // CERR_BufferOverflow, max=0 + EXPECT_EQ(CERR_BufferOverflow, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 0, 0, &newp, FALSE)); + + // CERR_None, no token + str[0] = '\0'; + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // CERR_NoTokensFound, empty + u16cpy(str, u""); + EXPECT_EQ(CERR_NoTokensFound, GetXStringImpl(tstr, &fileKeyboard, str, u"c", output, 80, 0, &newp, FALSE)); + + // CERR_NoTokensFound, whitespace + u16cpy(str, u" "); + EXPECT_EQ(CERR_NoTokensFound, GetXStringImpl(tstr, &fileKeyboard, str, u"c", output, 80, 0, &newp, FALSE)); +} + +// tests strings starting with 'x' or 'd' +TEST_F(CompilerTest, GetXStringImpl_type_xd_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // hex 32-bit + u16cpy(str, u"x10330"); // Gothic A + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_GothicA[] = { 0xD800, 0xDF30, 0 }; // see UTF32ToUTF16 + EXPECT_EQ(0, u16cmp(tstr_GothicA, tstr)); + + // decimal 8-bit + u16cpy(str, u"d18"); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"\u0012", tstr)); + + // hex capital 8-bit + u16cpy(str, u"X12"); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"\u0012", tstr)); + + // hex 32-bit, CERR_InvalidCharacter + u16cpy(str, u"x110000"); + EXPECT_EQ(CERR_InvalidCharacter, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // dk, valid + u16cpy(str, u"dk(A)"); + EXPECT_EQ(0, (int)fileKeyboard.cxDeadKeyArray); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_dk_valid[] = { UC_SENTINEL, CODE_DEADKEY, 1, 0 }; // setup deadkeys + EXPECT_EQ(0, u16cmp(tstr_dk_valid, tstr)); + fileKeyboard.cxDeadKeyArray = 0; + + // deadkey, valid + u16cpy(str, u"deadkey(A)"); + EXPECT_EQ(0, (int)fileKeyboard.cxDeadKeyArray); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_deadkey_valid[] = { UC_SENTINEL, CODE_DEADKEY, 1, 0 }; // setup deadkeys + EXPECT_EQ(0, u16cmp(tstr_deadkey_valid, tstr)); + fileKeyboard.cxDeadKeyArray = 0; + + // dk, CERR_InvalidDeadkey, bad character + u16cpy(str, u"dk(%)"); + EXPECT_EQ(CERR_InvalidDeadkey, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // dk, CERR_InvalidDeadkey, no close delimiter => NULL + u16cpy(str, u"dk("); + EXPECT_EQ(CERR_InvalidDeadkey, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // dk, CERR_InvalidDeadkey, empty delimiters => empty string + u16cpy(str, u"dk()"); + EXPECT_EQ(CERR_InvalidDeadkey, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); +} + +// tests strings starting with double quote +TEST_F(CompilerTest, GetXStringImpl_type_double_quote_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // valid + u16cpy(str, u"\"abc\""); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"abc", tstr)); + + // CERR_UnterminatedString + u16cpy(str, u"\"abc"); + EXPECT_EQ(CERR_UnterminatedString, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // CERR_ExtendedStringTooLong + u16cpy(str, u"\"abc\""); + EXPECT_EQ(CERR_ExtendedStringTooLong, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 2, 0, &newp, FALSE)); // max reduced to force error + + // CERR_StringInVirtualKeySection *** TODO *** +} + +// tests strings starting with single quote +TEST_F(CompilerTest, GetXStringImpl_type_single_quote_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // valid + u16cpy(str, u"\'abc\'"); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"abc", tstr)); + + // CERR_UnterminatedString + u16cpy(str, u"\'abc"); + EXPECT_EQ(CERR_UnterminatedString, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // CERR_ExtendedStringTooLong + u16cpy(str, u"\'abc\'"); + EXPECT_EQ(CERR_ExtendedStringTooLong, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 2, 0, &newp, FALSE)); // max reduced to force error + + // CERR_StringInVirtualKeySection *** TODO *** +} + +// KMX_DWORD process_baselayout(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_platform(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_if_synonym(KMX_DWORD dwSystemID, PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_if(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_reset(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_expansion(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx, int max) +// KMX_DWORD process_set_synonym(KMX_DWORD dwSystemID, PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_set(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_save(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// int xatoi(PKMX_WCHAR *p) +// int GetGroupNum(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// KMX_DWORD ProcessEthnologueStore(PKMX_WCHAR p) +// KMX_DWORD ProcessHotKey(PKMX_WCHAR p, KMX_DWORD *hk) +// void SetChecksum(PKMX_BYTE buf, PKMX_DWORD CheckSum, KMX_DWORD sz) +// KMX_BOOL kmcmp::CheckStoreUsage(PFILE_KEYBOARD fk, int storeIndex, KMX_BOOL fIsStore, KMX_BOOL fIsOption, KMX_BOOL fIsCall) +// KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataSize) +// KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess) + +TEST_F(CompilerTest, GetRHS_test) { + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR tstr[128]; + + // CERR_NoTokensFound, empty string + str[0] = '\0'; + EXPECT_EQ(CERR_NoTokensFound, GetRHS(&fileKeyboard, str, tstr, 80, 0, FALSE)); + + // CERR_NoTokensFound, no '>' + u16cpy(str, u"abc"); + EXPECT_EQ(CERR_NoTokensFound, GetRHS(&fileKeyboard, str, tstr, 80, 0, FALSE)); + + // CERR_None + u16cpy(str, u"> nul c\n"); + EXPECT_EQ(CERR_None, GetRHS(&fileKeyboard, str, tstr, 80, 0, FALSE)); +} + +// void safe_wcsncpy(PKMX_WCHAR out, PKMX_WCHAR in, int cbMax) +// KMX_BOOL IsSameToken(PKMX_WCHAR *p, KMX_WCHAR const * token) +// static bool endsWith(const std::string& str, const std::string& suffix) +// KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +// int atoiW(PKMX_WCHAR p) +// KMX_DWORD kmcmp::CheckUTF16(int n) +// KMX_DWORD kmcmp::UTF32ToUTF16(int n, int *n1, int *n2) +// KMX_DWORD BuildVKDictionary(PFILE_KEYBOARD fk) +// int GetVKCode(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// int GetDeadKey(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// void kmcmp::RecordDeadkeyNames(PFILE_KEYBOARD fk) +// KMX_BOOL kmcmp::IsValidCallStore(PFILE_STORE fs) + +TEST_F(CompilerTest, hasPreamble_test) { + EXPECT_FALSE(hasPreamble(u"")); + EXPECT_FALSE(hasPreamble(u"\uFEFE")); // not \uFEFF + EXPECT_TRUE(hasPreamble(u"\uFEFF")); + EXPECT_FALSE(hasPreamble(u"a\uFEFF")); +} + +// bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) +// PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID) diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index 1095b48095..2f3442212f 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -20,7 +20,7 @@ kmcompxtest = executable('kmcompxtest', ['kmcompxtest.cpp','util_filesystem.cpp' include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep, ) @@ -129,7 +129,7 @@ if get_option('full_test') endif -common_test_files = [ meson.source_root() / '../../../common/include/test_color.cpp' ] +common_test_files = [ meson.global_source_root() / '../../../common/include/test_color.cpp' ] # Test the API endpoints @@ -138,7 +138,7 @@ apitest = executable('api-test', ['api-test.cpp','util_filesystem.cpp','util_cal include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep ) @@ -149,7 +149,7 @@ usetapitest = executable('uset-api-test', 'uset-api-test.cpp', common_test_files include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep, ) @@ -157,6 +157,17 @@ test('uset-api-test', usetapitest) # Google Test +gtestcompilertest = executable('gtest-compiler-test', 'gtest-compiler-test.cpp', + cpp_args: defns + flags, + include_directories: inc, + name_suffix: name_suffix, + link_args: links + tests_links, + objects: lib.extract_all_objects(), + dependencies: [ icuuc_dep, gtest_dep, gmock_dep ], + ) + +test('gtest-compiler-test', gtestcompilertest) + gtestcompmsgtest = executable('gtest-compmsg-test', 'gtest-compmsg-test.cpp', cpp_args: defns + flags, include_directories: inc, @@ -166,4 +177,4 @@ gtestcompmsgtest = executable('gtest-compmsg-test', 'gtest-compmsg-test.cpp', dependencies: [ icuuc_dep, gtest_dep, gmock_dep ], ) -test('gtest-compmsg-test', gtestcompmsgtest) +test('gtest-compmsg-test', gtestcompmsgtest) \ No newline at end of file diff --git a/developer/src/tike/child/UfrmKeymanWizard.pas b/developer/src/tike/child/UfrmKeymanWizard.pas index d8a62db410..baa906e836 100644 --- a/developer/src/tike/child/UfrmKeymanWizard.pas +++ b/developer/src/tike/child/UfrmKeymanWizard.pas @@ -453,13 +453,13 @@ type procedure ConfirmSaveOfOldEditorWindows; procedure ConfirmSaveOfOldEditorWindow(FeatureID: TKeyboardParser_FeatureID; FModified: Boolean; const FOldFilename: string; DoSave: TProc; DoLoad: TProc); - procedure LoadFeature(ID: TKeyboardParser_FeatureID); + function LoadFeature(ID: TKeyboardParser_FeatureID): Boolean; function FeatureTab(kf: TKeyboardParser_FeatureID): TTabSheet; procedure InitFeatureTab(ID: TKeyboardParser_FeatureID); procedure FeatureModified(Sender: TObject); function SaveFeature(ID: TKeyboardParser_FeatureID): Boolean; procedure SelectTouchLayoutTemplate(APromptChange: Boolean); - procedure LoadTouchLayout; // I4034 + function LoadTouchLayout: Boolean; // I4034 function GetFontInfo(Index: TKeyboardFont): TKeyboardFontInfo; // I4057 procedure SetFontInfo(Index: TKeyboardFont; const Value: TKeyboardFontInfo); // I4057 @@ -1628,7 +1628,7 @@ begin FLayoutSetup := FOldLayoutSetup; end; -procedure TfrmKeymanWizard.LoadFeature(ID: TKeyboardParser_FeatureID); +function TfrmKeymanWizard.LoadFeature(ID: TKeyboardParser_FeatureID): Boolean; begin if FKeyboardParser.Features.ContainsKey(ID) then begin @@ -1651,7 +1651,8 @@ begin end; kfTouchLayout: begin - LoadTouchLayout; // I4034 + if not LoadTouchLayout then + Exit(False); end; else begin @@ -1667,6 +1668,7 @@ begin end; end; FFeature[ID].Modified := False; + Result := True; end; function TfrmKeymanWizard.SaveFeature(ID: TKeyboardParser_FeatureID): Boolean; @@ -2007,7 +2009,12 @@ begin LoadSettings; for kf in FKeyboardParser.Features.Keys do - LoadFeature(kf); + begin + if not LoadFeature(kf) then + begin + Exit(False); + end; + end; if FKeyboardParser.IsComplex then // I4557 pagesLayout.ActivePage := pageLayoutCode; @@ -3163,18 +3170,17 @@ begin FFeature[kfTouchLayout].Modified := True; end; -procedure TfrmKeymanWizard.LoadTouchLayout; // I4034 +function TfrmKeymanWizard.LoadTouchLayout: Boolean; // I4034 begin if pagesTouchLayout.ActivePage = pageTouchLayoutDesign then begin - if not frameTouchLayout.Load(FFeature[kfTouchLayout].Filename, False, False) then - begin - pagesTouchLayout.ActivePage := pageTouchLayoutCode; - frameTouchLayoutSource.LoadFromFile(FFeature[kfTouchLayout].Filename, tffUTF8); - end; + Result := frameTouchLayout.Load(FFeature[kfTouchLayout].Filename, False, False); end else + begin frameTouchLayoutSource.LoadFromFile(FFeature[kfTouchLayout].Filename, tffUTF8); + Result := True; + end; end; procedure TfrmKeymanWizard.SaveTouchLayout; // I3885 diff --git a/developer/src/tike/main/UfrmMain.pas b/developer/src/tike/main/UfrmMain.pas index 44b5873245..8fa7a45c9b 100644 --- a/developer/src/tike/main/UfrmMain.pas +++ b/developer/src/tike/main/UfrmMain.pas @@ -1477,8 +1477,12 @@ begin if n >= 0 then Result.ProjectFile := FGlobalProject.Files[n]; - (Result as frmClass).OpenFile(FFileName); LockWindowUpdate(0); + + if not (Result as frmClass).OpenFile(FFileName) then + begin + Result.Release; + end; end; procedure TfrmKeymanDeveloper.HelpTopic(s: string); diff --git a/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas b/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas index 4b0405a32f..68bff9fd60 100644 --- a/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas +++ b/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas @@ -90,7 +90,7 @@ type procedure cefCommand(Sender: TObject; const command: string; params: TStringList); procedure cefLoadEnd(Sender: TObject); - procedure RegisterSource; + procedure RegisterSources(const AState: string); procedure CharMapDragDrop(Sender, Source: TObject; X, Y: Integer); procedure CharMapDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); @@ -238,10 +238,12 @@ begin modWebHttpServer.AppSource.UnregisterSource(FFilename+'#state'); end; -procedure TframeTouchLayoutBuilder.RegisterSource; +procedure TframeTouchLayoutBuilder.RegisterSources(const AState: string); begin if FFilename <> '' then modWebHttpServer.AppSource.RegisterSource(FFilename, FSavedLayoutJS); + if (FFileName <> '') and (AState <> '') then + modWebHttpServer.AppSource.RegisterSource(FFilename + '#state', AState, True); end; procedure TframeTouchLayoutBuilder.ImportFromKVK(const KVKFileName: string); // I3945 @@ -332,71 +334,85 @@ begin end; UnregisterSources; - try - if ALoadFromString then + if ALoadFromString then + begin + FNewLayoutJS := AFilename; + FFilename := GetNextFilename; + end + else + begin + if ALoadFromTemplate or (AFileName = '') or not FileExists(AFileName) then begin - FNewLayoutJS := AFilename; + FBaseFileName := FTemplateFileName; FFilename := GetNextFilename; end else begin - if ALoadFromTemplate or (AFileName = '') or not FileExists(AFileName) then - begin - FBaseFileName := FTemplateFileName; - FFilename := GetNextFilename; - end - else - begin - FBaseFileName := AFileName; - FFilename := AFileName; - end; - - with TStringList.Create do - try - LoadFromFile(FBaseFileName, TEncoding.UTF8); - FNewLayoutJS := Text; - finally - Free; - end; + FBaseFileName := AFileName; + FFilename := AFileName; end; - FTouchLayout := TTouchLayout.Create; // I3642 + with TStringList.Create do try - if not FTouchLayout.Load(FNewLayoutJS) then + LoadFromFile(FBaseFileName, TEncoding.UTF8); + FNewLayoutJS := Text; + finally + Free; + end; + end; + + FTouchLayout := TTouchLayout.Create; // I3642 + try + if not FTouchLayout.Load(FNewLayoutJS) then + begin + FLastError := FTouchLayout.LoadError; // I4083 + FLastErrorOffset := FTouchLayout.LoadErrorOffset; // I4083 + FFilename := FLastFilename; + RegisterSources(FState); + Exit(False); + end + else + begin + if (FSavedLayoutJS <> '') and ALoadFromTemplate then begin - FLastError := FTouchLayout.LoadError; // I4083 - FLastErrorOffset := FTouchLayout.LoadErrorOffset; // I4083 - FFilename := FLastFilename; - Exit(False); + FOldLayout := TTouchLayout.Create; + try + FOldLayout.Load(FSavedLayoutJS); + if FTouchLayout.Merge(FOldLayout) + then FSavedLayoutJS := FTouchLayout.Save(False) + else FSavedLayoutJS := FNewLayoutJS; + finally + FOldLayout.Free; + end; end else - begin - if (FSavedLayoutJS <> '') and ALoadFromTemplate then - begin - FOldLayout := TTouchLayout.Create; - try - FOldLayout.Load(FSavedLayoutJS); - if FTouchLayout.Merge(FOldLayout) - then FSavedLayoutJS := FTouchLayout.Save(False) - else FSavedLayoutJS := FNewLayoutJS; - finally - FOldLayout.Free; - end; - end - else - FSavedLayoutJS := FNewLayoutJS; - end; - finally - FTouchLayout.Free; + FSavedLayoutJS := FNewLayoutJS; end; - finally - RegisterSource; - if (FFileName <> '') and (FState <> '') then - modWebHttpServer.AppSource.RegisterSource(FFilename + '#state', FState, True); + FTouchLayout.Free; end; + if (FFileName <> '') and modWebHttpServer.AppSource.IsSourceRegistered(FFileName) then + begin + // If two .kmn files are loaded which both reference the same + // .keyman-touch-layout file, it's safest to just block it. This is a rare + // scenario, as most keyboard projects have a single .kmn, and it usually + // indicates a project may be in a bit of chaos anyway. + ShowMessage( + 'The touch layout is already opened for editing in another keyboard '+ + 'editor. Please close the other keyboard editor before opening this one '+ + 'again.'); + + // We want to prevent this window unregistering the sources it doesn't own + // when it is destroyed immediately after this, which we can do by blanking + // the filename. + FFileName := ''; + Exit(False); + end; + + RegisterSources(FState); + try DoLoad; except diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index cd04d5746b..62bd8677ae 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -2,6 +2,10 @@ + +

(TODO: add documentation on group)

+
+

The Filter allows a user to reduce the number of characters displayed in the character map. The standard filter options used are by font name or block name.

@@ -12,63 +16,63 @@
-

The Filter allows a user to reduce the number of characters displayed in the +

The Filter allows a user to reduce the number of characters displayed in the character map. The standard filter options used are by font name or block name.

-

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

-

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

-

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

-

The Filter allows a user to reduce the number of characters displayed in the +

The Filter allows a user to reduce the number of characters displayed in the character map. The standard filter options used are by font name or block name.

-

">" placed at the start of an entry will only show characters in the currently - selected Character Map font. This is helpful when trying to determine which characters +

">" placed at the start of an entry will only show characters in the currently + selected Character Map font. This is helpful when trying to determine which characters a given font supports.



Example: >LAO



finds all characters with names starting in "LAO" in the current font

-

"<" placed at the start of an entry will search Unicode block names instead of - character names. This is helpful when searching for characters within related +

"<" placed at the start of an entry will search Unicode block names instead of + character names. This is helpful when searching for characters within related blocks



Example: <Thai



finds the Thai Unicode block

-

Using "*" in an entry serves as a wildcard for any number of places in that entry. - For example, searching for "greek*alpha" will find characters whose Unicode names begin - with the word "Greek" and contain the word "Alpha" any number of places later. - This is helpful when searching for characters that share a common element in +

Using "*" in an entry serves as a wildcard for any number of places in that entry. + For example, searching for "greek*alpha" will find characters whose Unicode names begin + with the word "Greek" and contain the word "Alpha" any number of places later. + This is helpful when searching for characters that share a common element in their names (e.g. capital).

-

Using "?" anywhere in an entry serves as a wildcard for that single place in the entry. - For example, searching for "s???e" will return both the SPACE and the SMILE characters, +

Using "?" anywhere in an entry serves as a wildcard for that single place in the entry. + For example, searching for "s???e" will return both the SPACE and the SMILE characters, among others.



Example: 1000-119F



-

finds all characters between U+1000 and U+119F (inclusive) - +

finds all characters between U+1000 and U+119F (inclusive) - the Myanmar alphabet in this case

@@ -78,7 +82,7 @@ -

"$" placed at the end of an entry will match from the end of a Unicode character name. +

"$" placed at the end of an entry will match from the end of a Unicode character name. This option works best when used with "*" or "?".



Example: LATIN * LETTER A$



finds only "a" and "A"

@@ -471,18 +475,18 @@ -

The name of the developer of the keyboard. This is either your full name or +

The name of the developer of the keyboard. This is either your full name or the organization you're creating a model for.

-

We recommend the name of the language, dialect, or community that this model is +

We recommend the name of the language, dialect, or community that this model is intended for. The name must be written in all the Latin letters or Arabic numerals.

-

Who owns the rights to this model and its data? Typically, - you can use the automatically generated default value: © 2024 Your Full Name or +

Who owns the rights to this model and its data? Typically, + you can use the automatically generated default value: © 2024 Your Full Name or Your Organization.

@@ -491,18 +495,18 @@
-

If this is the first time you've created a lexical model for you language, you should - leave the version as 1.0. Otherwise, your version number must conform to the following +

If this is the first time you've created a lexical model for you language, you should + leave the version as 1.0. Otherwise, your version number must conform to the following rules: A version string made of major revision number.minor revision number.

-

Specifies the default BCP 47 language tags which will be added to the package +

Specifies the default BCP 47 language tags which will be added to the package metadata and project metadata.

-

To add a language tag, click the Add button to bring up the “Select BCP 47 Tag” +

To add a language tag, click the Add button to bring up the “Select BCP 47 Tag” dialog box.

@@ -524,7 +528,7 @@ -

An Author ID is a unique identifier used to distinguish you from others +

An Author ID is a unique identifier used to distinguish you from others who have the same or similar names.

@@ -533,13 +537,13 @@ -

Enter a unique name of the model. You can use the name of the language, dialect, +

Enter a unique name of the model. You can use the name of the language, dialect, or community that this model is intended for.

-

Keyman automatically generates a model ID for you, given all the - information already filled out. Model ID helps Keyman sorts and organizes +

Keyman automatically generates a model ID for you, given all the + information already filled out. Model ID helps Keyman sorts and organizes different lexical models.

@@ -558,15 +562,15 @@ -

Wordlist tabs have two views: Design, and Code. Changes to one view are reflected - immedaitely in the other view. Wordlist files should be stored in UTF-8 encoding +

Wordlist tabs have two views: Design, and Code. Changes to one view are reflected + immedaitely in the other view. Wordlist files should be stored in UTF-8 encoding (preferably without BOM), and tab-separated format.

-

Every line of the tab-separated format file is shown here, and can be edited directly. - For most wordlists, it will be more effective to use an external dictionary tool, - such as SIL Fieldworks or SIL PrimerPrep to generate the wordlist from a text corpus, +

Every line of the tab-separated format file is shown here, and can be edited directly. + For most wordlists, it will be more effective to use an external dictionary tool, + such as SIL Fieldworks or SIL PrimerPrep to generate the wordlist from a text corpus, and use this tab just to preview the contents of the file.

@@ -575,24 +579,24 @@ -

The Sort by frequency button has no effect on the functioning of the wordlist, +

The Sort by frequency button has no effect on the functioning of the wordlist, but can help you, the editor, by showing more common words earlier in the list.

-

Editor windows in Keyman Developer supports standard Windows editing keystrokes. - Many file formats, including .kmn, .kps, .xml, .html, .js and .json, support syntax - highlighting. The text editor in Keyman uses the Monaco component from Visual Studio Code, +

Editor windows in Keyman Developer supports standard Windows editing keystrokes. + Many file formats, including .kmn, .kps, .xml, .html, .js and .json, support syntax + highlighting. The text editor in Keyman uses the Monaco component from Visual Studio Code, so all the functionality available in that editor is also available here.

-

Attempt to identify the fonts on your system that will support the - characters. You can quickly change fonts by clicking on a font name in the grid of +

Attempt to identify the fonts on your system that will support the + characters. You can quickly change fonts by clicking on a font name in the grid of identified fonts.

@@ -603,47 +607,47 @@ -

The message window appears at the bottom of the screen, or floating in a toolbar - window. It contains a list of error and warning messages returned from a compilation +

The message window appears at the bottom of the screen, or floating in a toolbar + window. It contains a list of error and warning messages returned from a compilation session. You can undock and dock the window by dragging its title bar.

-

The debugger input window is used for typing input to test the keyboard. - In the top half of this window, input you type while testing your keyboard will be - displayed, exactly the same as in use, with one exception: deadkeys will be shown +

The debugger input window is used for typing input to test the keyboard. + In the top half of this window, input you type while testing your keyboard will be + displayed, exactly the same as in use, with one exception: deadkeys will be shown visually with an OBJ symbol.

-

The lower half of the window shows a grid of the characters to the virtual left - of the insertion point, or the selected characters if you make a selection. Deadkeys - will be identified in the grid. The grid will show characters in right-to-left scripts - in backing store order, from left to right. If there are more characters in your text +

The lower half of the window shows a grid of the characters to the virtual left + of the insertion point, or the selected characters if you make a selection. Deadkeys + will be identified in the grid. The grid will show characters in right-to-left scripts + in backing store order, from left to right. If there are more characters in your text than can fit on the screen, then only those that fit will be shown in the grid.

-

The idea in regression testing is to record a sequence of keystrokes and the - output the keyboard produced, in order to test for the same behaviour when you +

The idea in regression testing is to record a sequence of keystrokes and the + output the keyboard produced, in order to test for the same behaviour when you make changes to the keyboard.

-

Use Start Log/Stop Log to record the input and output. You can then use Start Test - to run the test again, or go the Options menu to clear the log, or save or load a test, +

Use Start Log/Stop Log to record the input and output. You can then use Start Test + to run the test again, or go the Options menu to clear the log, or save or load a test, or use the batch mode to run several tests in a row.



-

If the output produced while running a test is different to that stored when recording it, - Keyman will halt the test on the line where the failure occurred, and activate +

If the output produced while running a test is different to that stored when recording it, + Keyman will halt the test on the line where the failure occurred, and activate Single Step mode.

-

In the Options menu, you can clear the log, save or load a test, +

In the Options menu, you can clear the log, save or load a test, or use the batch mode to run several tests in a row.

@@ -655,39 +659,39 @@ -

This window shows the current keystroke state, and the sequence of +

This window shows the current keystroke state, and the sequence of keystrokes that were typed to arrive at this state.

-

This shows the elements that make up rule currently being processed: the context, - the key, and also what the output will be. If the rule uses stores, the contents of +

This shows the elements that make up rule currently being processed: the context, + the key, and also what the output will be. If the rule uses stores, the contents of the store will be shown in the right-hand column, with the matched letter in red.

-

Here all the lines that have been processed to this point are shown in a list. - You can double-click on any entry in the list to display the line in the +

Here all the lines that have been processed to this point are shown in a list. + You can double-click on any entry in the list to display the line in the keyboard source.

-

This lists all the deadkeys that are currently in the context. - You can select one from the list to see it highlighted in the debug input box. - This information can also be seen in the character grid in the lower half of +

This lists all the deadkeys that are currently in the context. + You can select one from the list to see it highlighted in the debug input box. + This information can also be seen in the character grid in the lower half of the debugger input window.

-

The About dialog displays copyright and registration information for Keyman Developer, +

The About dialog displays copyright and registration information for Keyman Developer, and has a link to the Keyman website.

diff --git a/developer/src/tike/xml/help/help.xsl b/developer/src/tike/xml/help/help.xsl index 2416fdff16..5f1eb19fa0 100644 --- a/developer/src/tike/xml/help/help.xsl +++ b/developer/src/tike/xml/help/help.xsl @@ -53,6 +53,7 @@ +
Context help is not available for . @@ -66,6 +67,18 @@ + +
+ language/reference/ +
+ + +
+
+
- diff --git a/docs/linux/ibus-keyman.md b/docs/linux/ibus-keyman.md index ffc7b06c1c..2b6241ccf0 100644 --- a/docs/linux/ibus-keyman.md +++ b/docs/linux/ibus-keyman.md @@ -6,7 +6,7 @@ Source code for Keyman engine for IBus is in [linux/ibus-keyman](../../linux/ibu ## Requirements -meson (>= 0.57) +meson (>= 1.0) ## Building diff --git a/linux/debian/control b/linux/debian/control index 54b186c790..f729999731 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -19,7 +19,7 @@ Build-Depends: libjson-glib-dev (>= 1.4.0), liblocale-gettext-perl, libsystemd-dev, - meson (>= 0.53), + meson (>= 1.0), metacity, ninja-build, perl, diff --git a/linux/ibus-keyman/meson.build b/linux/ibus-keyman/meson.build index 5a8958e983..80318708ac 100644 --- a/linux/ibus-keyman/meson.build +++ b/linux/ibus-keyman/meson.build @@ -1,7 +1,7 @@ project('ibus-keyman', 'c', 'cpp', version: run_command('cat', '../../VERSION.md', check: true).stdout().strip(), license: 'GPL-2+', - meson_version: '>=0.53.0') + meson_version: '>=1.0') cc = meson.get_compiler('c') conf = configuration_data() diff --git a/linux/keyman-system-service/meson.build b/linux/keyman-system-service/meson.build index 66da8f9891..596ba48bcd 100644 --- a/linux/keyman-system-service/meson.build +++ b/linux/keyman-system-service/meson.build @@ -1,7 +1,7 @@ project('keyman-system-service', 'c', 'cpp', version: run_command('cat', '../../VERSION.md', check: true).stdout().strip(), license: 'GPL-2+', - meson_version: '>=0.61') + meson_version: '>=1.0') evdev = dependency('libevdev', version: '>= 1.9') systemd = dependency('libsystemd') diff --git a/web/src/app/webview/src/debug-main.ts b/web/src/app/webview/src/debug-main.ts index 2613cf3a11..d903a9d536 100644 --- a/web/src/app/webview/src/debug-main.ts +++ b/web/src/app/webview/src/debug-main.ts @@ -8,9 +8,9 @@ import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' * This can only be done during load when the active script will be the * last script loaded. Otherwise the script must be identified by name. */ - var scripts = document.getElementsByTagName('script'); - var ss = scripts[scripts.length-1].src; - var sPath = ss.substr(0,ss.lastIndexOf('/')+1); +const scripts = document.getElementsByTagName('script'); +const ss = scripts[scripts.length-1].src; +const sPath = ss.substring(0,ss.lastIndexOf('/')+1); // @ts-ignore window['keyman'] = new KeymanEngine(SourcemappedWorker.constructInstance(), sPath); \ No newline at end of file diff --git a/web/src/app/webview/src/release-main.ts b/web/src/app/webview/src/release-main.ts index 62fefb5ad2..e594e0b70f 100644 --- a/web/src/app/webview/src/release-main.ts +++ b/web/src/app/webview/src/release-main.ts @@ -8,9 +8,9 @@ import { Worker } from '@keymanapp/lexical-model-layer/web' * This can only be done during load when the active script will be the * last script loaded. Otherwise the script must be identified by name. */ - var scripts = document.getElementsByTagName('script'); - var ss = scripts[scripts.length-1].src; - var sPath = ss.substr(0,ss.lastIndexOf('/')+1); +const scripts = document.getElementsByTagName('script'); +const ss = scripts[scripts.length-1].src; +const sPath = ss.substring(0,ss.lastIndexOf('/')+1); // @ts-ignore window['keyman'] = new KeymanEngine(Worker.constructInstance(), sPath); \ No newline at end of file