mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-24 00:27:40 +00:00
chore(common/web): Merge branch 'master' into test/common/web/types/9052-unit-tests-kvk-file-writer
This commit is contained in:
commit
d311bf7ecd
13 changed files with 436 additions and 71 deletions
10
HISTORY.md
10
HISTORY.md
|
|
@ -1,5 +1,15 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 18.0.156 alpha 2024-12-09
|
||||
|
||||
* fix(developer): remove platforms from kmc-generate LM readme (#12803)
|
||||
* test(developer): add test for ERROR_DescriptionIsMissing to kmc-keyboard-info (#12804)
|
||||
* chore(developer): verify bundled node version when building installer (#12806)
|
||||
* fix(developer): support hint property in displaymap (#12807)
|
||||
* fix(common/web): delete replaceExtension in types/src/util/file-types.ts (#12762)
|
||||
* chore(developer): add some docs for language examples in kmp.json (#12805)
|
||||
* fix(core): implement ldml_processor::get_key_list() (#12644)
|
||||
|
||||
## 18.0.155 alpha 2024-12-07
|
||||
|
||||
* chore(android,windows): Update crowdin for Czech (#12792)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
18.0.156
|
||||
18.0.157
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#pragma once
|
||||
|
||||
enum km_core_modifier_state {
|
||||
KM_CORE_MODIFIER_NONE = 0,
|
||||
KM_CORE_MODIFIER_LCTRL = 1 << 0,
|
||||
KM_CORE_MODIFIER_RCTRL = 1 << 1,
|
||||
KM_CORE_MODIFIER_LALT = 1 << 2,
|
||||
|
|
|
|||
|
|
@ -455,7 +455,13 @@ public:
|
|||
bool setLayr(const COMP_KMXPLUS_LAYR *newLayr);
|
||||
bool valid() const;
|
||||
|
||||
/**
|
||||
* @param list index from 0 to layr->listCount
|
||||
*/
|
||||
const COMP_KMXPLUS_LAYR_LIST *getList(KMX_DWORD list) const;
|
||||
/**
|
||||
* @param entry index value: COMP_KMXPLUS_LAYR_LIST.layer but less than COMP_KMXPLUS_LAYR_LIST.layer+COMP_KMXPLUS_LAYR_LIST.count
|
||||
*/
|
||||
const COMP_KMXPLUS_LAYR_ENTRY *getEntry(KMX_DWORD entry) const;
|
||||
const COMP_KMXPLUS_LAYR_ROW *getRow(KMX_DWORD row) const;
|
||||
const COMP_KMXPLUS_LAYR_KEY *getKey(KMX_DWORD key) const;
|
||||
|
|
|
|||
|
|
@ -328,8 +328,7 @@ km_core_attr const & ldml_processor::attributes() const {
|
|||
}
|
||||
|
||||
km_core_keyboard_key * ldml_processor::get_key_list() const {
|
||||
km_core_keyboard_key* key_list = new km_core_keyboard_key(KM_CORE_KEYBOARD_KEY_LIST_END);
|
||||
return key_list;
|
||||
return keys.get_key_list();
|
||||
}
|
||||
|
||||
km_core_keyboard_imx * ldml_processor::get_imx_list() const {
|
||||
|
|
|
|||
|
|
@ -34,10 +34,6 @@ class ldml_processor : public abstract_processor {
|
|||
const std::vector<uint8_t> & data
|
||||
);
|
||||
|
||||
static bool is_kmxplus_file(
|
||||
const std::vector<uint8_t> & data
|
||||
);
|
||||
|
||||
km_core_status
|
||||
process_event(
|
||||
km_core_state *state,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include "ldml_vkeys.hpp"
|
||||
#include "kmx_file.h"
|
||||
#include <ldml/keyman_core_ldml.h>
|
||||
#include <set>
|
||||
#include <assert.h>
|
||||
|
||||
namespace km {
|
||||
namespace core {
|
||||
|
|
@ -22,6 +24,114 @@ vkeys::add(km_core_virtual_key vk, km_core_ldml_modifier_state modifier_state, s
|
|||
const vkey_id id(vk, modifier_state);
|
||||
// assign the string
|
||||
vkey_to_string[id] = output;
|
||||
// includes all keys - including gaps.
|
||||
all_vkeys.insert(id);
|
||||
}
|
||||
|
||||
km_core_keyboard_key *
|
||||
vkeys::get_key_list() const {
|
||||
// prescan to find out which modifier flags are used
|
||||
|
||||
std::size_t other_key_count = 0; // number of 'OTHER' keys, which will need to expand to all of the other_state set ( so other_state.size())
|
||||
|
||||
std::set<km::core::ldml::km_core_ldml_modifier_state> all_modifiers;
|
||||
for (const auto &k : all_vkeys) {
|
||||
const auto mod = k.second;
|
||||
if (mod == LDML_KEYS_MOD_OTHER) {
|
||||
other_key_count++;
|
||||
}
|
||||
all_modifiers.insert(mod);
|
||||
}
|
||||
std::set<km_core_modifier_state> other_state;
|
||||
|
||||
// Alt
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_ALT) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) {
|
||||
// no ALT keys were seen, so OTHER includes ALT
|
||||
other_state.insert(KM_CORE_MODIFIER_ALT);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_RALT);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_LALT);
|
||||
}
|
||||
|
||||
// ctrl
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_CTRL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) {
|
||||
// no CTRL keys were seen, so OTHER includes CTRL
|
||||
other_state.insert(KM_CORE_MODIFIER_CTRL);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_RCTRL);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_LCTRL);
|
||||
}
|
||||
|
||||
// shift
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_SHIFT) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_SHIFT);
|
||||
}
|
||||
|
||||
// caps
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_CAPS) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_CAPS);
|
||||
}
|
||||
|
||||
// none- it's possible there is no 'none' layer
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_NONE) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_NONE);
|
||||
}
|
||||
|
||||
// We need ALL combinations of the other_state, except for 'all off'.
|
||||
// The number of additions will be (2**(other_state.size())-1
|
||||
// Also, since the LDML_KEYS_MOD_OTHER modifier is excluded, we
|
||||
// will need to subtract 1 when calculating new_list_size
|
||||
const std::size_t other_expanded_count = (1 << other_state.size()) - 1;
|
||||
|
||||
std::vector<uint32_t> other_expanded_mods(other_expanded_count);
|
||||
|
||||
// populate the expanded list.
|
||||
// we start at 1 because 0 is "all bits off" (00000b)
|
||||
for (std::size_t expansion = 1; expansion <= other_expanded_count; expansion++) {
|
||||
uint32_t &expanded_mod = other_expanded_mods.at(expansion-1) = KM_CORE_MODIFIER_NONE;
|
||||
std::size_t bit_mask = 1;
|
||||
for (const auto mod : other_state) {
|
||||
// Check if this modifier is on in this iteration of the expansion
|
||||
// bit_mask will be 2^0 … 2^(other_state().size()-1)
|
||||
if (bit_mask & expansion) {
|
||||
// do we include this entry? check if this bit is on
|
||||
expanded_mod |= mod;
|
||||
}
|
||||
// shift the bitmask over
|
||||
assert(expanded_mod <= KM_CORE_MODIFIER_MASK_CAPS);
|
||||
bit_mask <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t new_list_size = all_vkeys.size() // original size
|
||||
+ (other_key_count * (other_expanded_count - 1))// number of additional entries needed
|
||||
+ 1; // terminator
|
||||
km_core_keyboard_key *list = new km_core_keyboard_key[new_list_size];
|
||||
std::size_t n = 0;
|
||||
for (const auto &k : all_vkeys) {
|
||||
const auto vkey = k.first;
|
||||
const auto mod = k.second;
|
||||
|
||||
if (mod == LDML_KEYS_MOD_OTHER) {
|
||||
// expand to all of other_state
|
||||
for (const auto expanded_mod : other_expanded_mods) {
|
||||
list[n].key = vkey;
|
||||
list[n++].modifier_flag = expanded_mod;
|
||||
assert(n <= new_list_size);
|
||||
}
|
||||
} else {
|
||||
assert(mod <= KM_CORE_MODIFIER_MASK_CAPS); // that no LDMLisms escape
|
||||
list[n].key = vkey;
|
||||
list[n++].modifier_flag = mod;
|
||||
assert(n <= new_list_size);
|
||||
}
|
||||
}
|
||||
// add the list terminator
|
||||
list[n++] = KM_CORE_KEYBOARD_KEY_LIST_END;
|
||||
assert(n == new_list_size);
|
||||
return list;
|
||||
}
|
||||
|
||||
static const uint16_t BOTH_ALT = LALTFLAG | RALTFLAG;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "keyman_core.h"
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ typedef std::pair<km_core_virtual_key, km_core_ldml_modifier_state> vkey_id;
|
|||
class vkeys {
|
||||
private:
|
||||
std::map<vkey_id, std::u16string> vkey_to_string;
|
||||
std::set<vkey_id> all_vkeys;
|
||||
|
||||
public:
|
||||
vkeys();
|
||||
|
|
@ -53,6 +55,11 @@ public:
|
|||
std::u16string
|
||||
lookup(km_core_virtual_key vk, uint16_t modifier_state, bool &found) const;
|
||||
|
||||
/**
|
||||
* For implementing ldml_processor::get_key_list()
|
||||
*/
|
||||
km_core_keyboard_key* get_key_list() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Non-recursive internal lookup of a specific ID
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
<!--
|
||||
|
||||
@@keys: [SHIFT K_BKQUOTE][K_1][K_BKQUOTE]
|
||||
@@expected: \u0037\u1790\u17B6\u0127
|
||||
@@keys: [SHIFT K_BKQUOTE][K_1][K_BKQUOTE][LCTRL K_BKQUOTE][RALT K_BKQUOTE]
|
||||
@@expected: \u0037\u1790\u17B6\u0127\u1790\u17B6\u0065
|
||||
@@keylist: [SHIFT K_BKQUOTE][SHIFT K_1][K_BKQUOTE][K_1][CTRL-do-not-use K_1][ALT-do-not-use K_BKQUOTE][CAPS K_BKQUOTE][ALT-do-not-use CAPS K_BKQUOTE]
|
||||
|
||||
-->
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
|
|
@ -23,5 +24,11 @@
|
|||
<layer id="shift" modifiers="shift">
|
||||
<row keys="seven eee" /> <!-- number row -->
|
||||
</layer>
|
||||
<layer id="control" modifiers="ctrl">
|
||||
<row keys="that gap" /> <!-- number row -->
|
||||
</layer>
|
||||
<layer id="catchall" modifiers="other">
|
||||
<row keys="eee gap" /> <!-- number row -->
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <set>
|
||||
|
||||
#include "path.hpp"
|
||||
#include "state.hpp"
|
||||
|
|
@ -268,6 +269,63 @@ verify_context(std::u16string &text_store, km_core_state *&test_state, std::vect
|
|||
delete[] buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param actual the list from get_key_list()
|
||||
* @param expected optional list with keys to check, can be empty - not exhaistive
|
||||
* @returns true if passing
|
||||
*/
|
||||
bool
|
||||
verify_key_list(std::set<km::tests::key_event> &actual, std::set<km::tests::key_event> &expected) {
|
||||
bool equals = true;
|
||||
// error if any bad modifier keys
|
||||
for(const auto &akey : actual) {
|
||||
if (akey.modifier_state > KM_CORE_MODIFIER_MASK_CAPS) {
|
||||
equals = false;
|
||||
std::u16string dump = convert<char, char16_t>(akey.dump()); // akey.dump()
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had key with bad modifier " << akey.modifier_state << ": " << dump << console_color::reset() << std::endl;
|
||||
}
|
||||
}
|
||||
// error if any expected keys missing (note expected may be empty)
|
||||
for(const auto &ekey : expected) {
|
||||
if (actual.count(ekey) == 0) {
|
||||
equals = false;
|
||||
std::u16string dump = convert<char, char16_t>(ekey.dump()); // akey.dump()
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had missing key " << dump << console_color::reset() << std::endl;
|
||||
}
|
||||
}
|
||||
if (equals) {
|
||||
std::wcout << console_color::fg(console_color::GREEN) << " " << actual.size() << " vkeys OK, verified " << expected.size() << console_color::reset() << std::endl;
|
||||
}
|
||||
return equals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actual_list the list from get_key_list()
|
||||
* @param keylist optional string with keys to check, can be empty
|
||||
* @param test the LDML test source, for additional data
|
||||
* @returns true if passing
|
||||
*/
|
||||
bool
|
||||
verify_key_list(const km_core_keyboard_key *actual_list, const std::u16string &expected_list, const km::tests::LdmlTestSource &test) {
|
||||
std::set<km::tests::key_event> actual, expected;
|
||||
// convert actual list
|
||||
while (actual_list != nullptr && !(actual_list->key == 0 && actual_list->modifier_flag == 0)) {
|
||||
km::tests::key_event k(actual_list->key, (uint16_t)actual_list->modifier_flag);
|
||||
actual.insert(k);
|
||||
actual_list++; // advance pointer
|
||||
}
|
||||
// parse the expected list
|
||||
std::string keylist = convert<char16_t, char>(expected_list);
|
||||
while (!keylist.empty() && keylist[0] == '[') {
|
||||
const km::tests::key_event k = km::tests::LdmlEmbeddedTestSource::parse_next_key(keylist);
|
||||
if (!k.empty()) {
|
||||
expected.emplace(k);
|
||||
}
|
||||
}
|
||||
return verify_key_list(actual, expected);
|
||||
}
|
||||
|
||||
int
|
||||
run_test(const km::core::path &source, const km::core::path &compiled, km::tests::LdmlTestSource& test_source) {
|
||||
km_core_keyboard * test_kb = nullptr;
|
||||
|
|
@ -374,6 +432,17 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
errorLine = __LINE__;
|
||||
}
|
||||
} break;
|
||||
case km::tests::LDML_ACTION_CHECK_KEYLIST: {
|
||||
std::cout << "- checking keylist" << std::endl;
|
||||
// get keylist from kbd
|
||||
const km_core_keyboard_key* actual_list = test_kb->get_key_list();
|
||||
if (!verify_key_list(actual_list, action.string, test_source)) {
|
||||
errorLine = __LINE__;
|
||||
} else {
|
||||
std::cout << " .. passes." << std::endl;
|
||||
}
|
||||
delete [] actual_list;
|
||||
} break;
|
||||
case km::tests::LDML_ACTION_FAIL: {
|
||||
// test requested failure
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL: " << action.string << console_color::reset()
|
||||
|
|
@ -424,7 +493,7 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled,
|
|||
|
||||
std::vector<std::string> failures; // track failures for summary
|
||||
|
||||
int embedded_result = embedded_test_source.load_source(source);
|
||||
int embedded_result = embedded_test_source.load_source(source, compiled);
|
||||
|
||||
if (!filter.empty()) {
|
||||
// Always skip the embedded test if there's a filter.
|
||||
|
|
|
|||
|
|
@ -127,6 +127,50 @@ bool LdmlTestSource::get_expected_beep() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
int LdmlTestSource::load_kmx_plus(const km::core::path &compiled) {
|
||||
// check and load the KMX (yes, once again)
|
||||
rawdata = km::tests::load_kmx_file(compiled);
|
||||
if(!km::core::ldml_processor::is_handled(rawdata)) {
|
||||
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data();
|
||||
// initialize the kmxplus object with our copy
|
||||
kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size()));
|
||||
|
||||
if (!kmxplus->is_valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
if (!kmxplus->key2Helper.valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
bool LdmlTestSource::get_vkey_table(std::set<key_event> &fillin) const {
|
||||
if (!kmxplus || !kmxplus->is_valid()) {
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
// just dump the kmap table
|
||||
for (KMX_DWORD kmapIdx = 0; kmapIdx < kmxplus->key2->kmapCount; kmapIdx++) {
|
||||
const km::core::kmx::COMP_KMXPLUS_KEYS_KMAP *kmap = kmxplus->key2Helper.getKmap(kmapIdx);
|
||||
if (kmap == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (kmap->vkey > 0xFF) {
|
||||
continue; // synthetic key- skip
|
||||
}
|
||||
fillin.insert(key_event(kmap->vkey, kmap->mod));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// String trim functions from https://stackoverflow.com/a/217605/1836776
|
||||
// trim from start (in place)
|
||||
static inline void
|
||||
|
|
@ -264,12 +308,13 @@ LdmlEmbeddedTestSource::is_token(const std::string token, std::string &line) {
|
|||
}
|
||||
|
||||
int
|
||||
LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
|
||||
LdmlEmbeddedTestSource::load_source( const km::core::path &path, const km::core::path &compiled ) {
|
||||
const std::string s_keys = "@@keys: ";
|
||||
const std::string s_expected = "@@expected: ";
|
||||
const std::string s_context = "@@context: ";
|
||||
const std::string s_capsLock = "@@capsLock: ";
|
||||
const std::string s_expecterror = "@@expect-error: ";
|
||||
const std::string s_keylist = "@@keylist: ";
|
||||
|
||||
// Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected
|
||||
// result
|
||||
|
|
@ -301,6 +346,10 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
|
|||
context = parse_source_string(line);
|
||||
} else if (is_token(s_capsLock, line)) {
|
||||
set_caps_lock_on(parse_source_string(line).compare(u"1") == 0);
|
||||
} else if (is_token(s_keylist, line)) {
|
||||
set_keylist(line);
|
||||
} else if (line[0] == '@') {
|
||||
std::cerr << path << " warning, unknown @-command " << line << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -316,7 +365,13 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
|
|||
return __LINE__;
|
||||
}
|
||||
|
||||
return 0;
|
||||
// and load KMX+ for keylist
|
||||
if (!expected_error) {
|
||||
// don't attempt to load KMX+ on expected error
|
||||
return load_kmx_plus(compiled);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
km_core_status
|
||||
|
|
@ -357,7 +412,7 @@ LdmlTestSource::char_to_event(char ch) {
|
|||
}
|
||||
|
||||
uint16_t
|
||||
LdmlTestSource::get_modifier(std::string const m) {
|
||||
LdmlTestSource::get_modifier(std::string const &m) {
|
||||
for (int i = 0; km::core::kmx::s_modifier_names[i].name; i++) {
|
||||
if (m == km::core::kmx::s_modifier_names[i].name) {
|
||||
return km::core::kmx::s_modifier_names[i].modifier;
|
||||
|
|
@ -366,6 +421,12 @@ LdmlTestSource::get_modifier(std::string const m) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
std::string key_event::dump() const {
|
||||
std::stringstream f;
|
||||
f << "Key: {" << km::core::kmx::Debug_VirtualKey(vk) << ", " << km::core::kmx::Debug_ModifierName(modifier_state) << "}";
|
||||
return f.str();
|
||||
}
|
||||
|
||||
key_event
|
||||
LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
|
||||
// vkey format is MODIFIER MODIFIER K_NAME
|
||||
|
|
@ -401,34 +462,37 @@ LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
|
|||
|
||||
void
|
||||
LdmlEmbeddedTestSource::next_action(ldml_action &fillin) {
|
||||
if (is_done || keys.empty()) {
|
||||
// We were already done. return done.
|
||||
fillin.type = LDML_ACTION_DONE;
|
||||
return;
|
||||
if (keys.empty()) {
|
||||
// #3 we are almost done, let's run the key check
|
||||
if (check_keylist) {
|
||||
fillin.type = LDML_ACTION_CHECK_KEYLIST;
|
||||
fillin.string = expected_keylist; // could be empty
|
||||
check_keylist = false;
|
||||
} else {
|
||||
fillin.type = LDML_ACTION_DONE;
|
||||
}
|
||||
} else if(keys[0].empty()) {
|
||||
// #2. Then, when we finish a key set, we check the 'expected' at the end of it.
|
||||
// Got to the end of a key set. time to check
|
||||
fillin.type = LDML_ACTION_CHECK_EXPECTED;
|
||||
fillin.string = expected[0]; // copy expected
|
||||
expected.pop_front();
|
||||
keys.pop_front();
|
||||
if (keys.empty()) {
|
||||
is_done = true; // so we get DONE next time
|
||||
}
|
||||
} else {
|
||||
// #1 First, we process each key
|
||||
fillin.type = LDML_ACTION_KEY_EVENT;
|
||||
fillin.k = next_key();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
key_event
|
||||
LdmlEmbeddedTestSource::next_key() {
|
||||
// mutate this->keys
|
||||
return next_key(keys[0]);
|
||||
return parse_next_key(keys[0]);
|
||||
}
|
||||
|
||||
key_event
|
||||
LdmlEmbeddedTestSource::next_key(std::string &keys) {
|
||||
LdmlEmbeddedTestSource::parse_next_key(std::string &keys) {
|
||||
// Parse the next element of the string, chop it off, and return it
|
||||
// mutates keys
|
||||
if (keys.length() == 0)
|
||||
|
|
@ -453,10 +517,10 @@ LdmlEmbeddedTestSource::next_key(std::string &keys) {
|
|||
|
||||
class LdmlJsonTestSource : public LdmlTestSource {
|
||||
public:
|
||||
LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus);
|
||||
LdmlJsonTestSource(const std::string &path);
|
||||
virtual ~LdmlJsonTestSource();
|
||||
virtual const std::u16string &get_context();
|
||||
int load(const nlohmann::json &test);
|
||||
int load(const nlohmann::json &test, const km::core::path &compiled);
|
||||
virtual void next_action(ldml_action &fillin);
|
||||
private:
|
||||
std::string path;
|
||||
|
|
@ -467,14 +531,14 @@ private:
|
|||
* Which action are we on?
|
||||
*/
|
||||
std::size_t action_index = -1;
|
||||
const km::core::kmx::kmx_plus *kmxplus;
|
||||
/** @return false if not found */
|
||||
bool set_key_from_id(key_event& k, const std::u16string& id);
|
||||
bool loaded_context = false;
|
||||
bool check_keys = true;
|
||||
};
|
||||
|
||||
LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *k)
|
||||
:path(path), kmxplus(k) {
|
||||
LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path)
|
||||
:path(path) {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -519,6 +583,13 @@ bool LdmlJsonTestSource::set_key_from_id(key_event& k, const std::u16string& id)
|
|||
void
|
||||
LdmlJsonTestSource::next_action(ldml_action &fillin) {
|
||||
if ((action_index+1) >= data["/actions"_json_pointer].size()) {
|
||||
// add check keylist
|
||||
if (check_keys) {
|
||||
fillin.type = LDML_ACTION_CHECK_KEYLIST;
|
||||
fillin.string.clear();
|
||||
check_keys = false;
|
||||
return;
|
||||
}
|
||||
// at end, done
|
||||
fillin.type = LDML_ACTION_DONE;
|
||||
return;
|
||||
|
|
@ -586,19 +657,21 @@ LdmlJsonTestSource::get_context() {
|
|||
return context;
|
||||
}
|
||||
|
||||
int LdmlJsonTestSource::load(const nlohmann::json &data) {
|
||||
int LdmlJsonTestSource::load(const nlohmann::json &data, const km::core::path &compiled) {
|
||||
this->data = data;
|
||||
// TODO-LDML: validate here?
|
||||
return 0;
|
||||
// TODO-LDML: validate JSON here?
|
||||
|
||||
// load up the kmx_plus
|
||||
return load_kmx_plus(compiled);
|
||||
}
|
||||
|
||||
#if defined(HAVE_ICU4C)
|
||||
class LdmlJsonRepertoireTestSource : public LdmlTestSource {
|
||||
public:
|
||||
LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus);
|
||||
LdmlJsonRepertoireTestSource(const std::string &path );
|
||||
virtual ~LdmlJsonRepertoireTestSource();
|
||||
virtual const std::u16string &get_context();
|
||||
int load(const nlohmann::json &test);
|
||||
int load(const nlohmann::json &test, const km::core::path &compiled);
|
||||
virtual void next_action(ldml_action &fillin);
|
||||
private:
|
||||
std::string path;
|
||||
|
|
@ -610,11 +683,10 @@ private:
|
|||
std::unique_ptr<icu::UnicodeSet> uset;
|
||||
std::unique_ptr<icu::UnicodeSetIterator> iterator;
|
||||
bool need_check = false; // set this after each char
|
||||
const km::core::kmx::kmx_plus *kmxplus;
|
||||
};
|
||||
|
||||
LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *k)
|
||||
:path(path), kmxplus(k){
|
||||
LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path)
|
||||
:path(path) {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -702,7 +774,7 @@ LdmlJsonRepertoireTestSource::get_context() {
|
|||
return context; // no context needed
|
||||
}
|
||||
|
||||
int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) {
|
||||
int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data, const km::core::path &compiled) {
|
||||
this->data = data; // TODO-LDML
|
||||
// Need an update to json.hpp to use contains()
|
||||
// if (data.contains("/type"_json_pointer)) {
|
||||
|
|
@ -733,7 +805,7 @@ int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) {
|
|||
// }
|
||||
// #endif
|
||||
iterator = std::unique_ptr<icu::UnicodeSetIterator>(new icu::UnicodeSetIterator(*uset));
|
||||
return 0;
|
||||
return load_kmx_plus(compiled);
|
||||
}
|
||||
#endif // HAVE_ICU4C
|
||||
|
||||
|
|
@ -757,26 +829,6 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
|
|||
return __LINE__; // empty
|
||||
}
|
||||
|
||||
// check and load the KMX (yes, once again)
|
||||
rawdata = km::tests::load_kmx_file(compiled);
|
||||
if (!km::core::ldml_processor::is_handled(rawdata)) {
|
||||
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data();
|
||||
// initialize the kmxplus object with our copy
|
||||
kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size()));
|
||||
|
||||
if (!kmxplus->is_valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
if (!kmxplus->key2Helper.valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
auto conformsTo = data["/keyboardTest3/conformsTo"_json_pointer].get<std::string>();
|
||||
assert_or_return(std::string(LDML_CLDR_TEST_VERSION_LATEST) == conformsTo);
|
||||
|
|
@ -799,8 +851,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
|
|||
test_path.append(info_name).append("/tests/").append(tests_name).append("/").append(test_name);
|
||||
// std::cout << "JSON: reading " << info_name << "/" << test_path << std::endl;
|
||||
|
||||
std::unique_ptr<LdmlJsonTestSource> subtest(new LdmlJsonTestSource(test_path, kmxplus.get()));
|
||||
assert_or_return(subtest->load(test) == 0);
|
||||
std::unique_ptr<LdmlJsonTestSource> subtest(new LdmlJsonTestSource(test_path));
|
||||
assert_or_return(subtest->load(test, compiled) == 0);
|
||||
test_map[test_path] = std::unique_ptr<LdmlTestSource>(subtest.release());
|
||||
}
|
||||
}
|
||||
|
|
@ -815,8 +867,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
|
|||
std::string test_path;
|
||||
test_path.append(info_name).append("/repertoire/").append(rep_name);
|
||||
|
||||
std::unique_ptr<LdmlJsonRepertoireTestSource> reptest(new LdmlJsonRepertoireTestSource(test_path, kmxplus.get()));
|
||||
assert_or_return(reptest->load(rep) == 0);
|
||||
std::unique_ptr<LdmlJsonRepertoireTestSource> reptest(new LdmlJsonRepertoireTestSource(test_path));
|
||||
assert_or_return(reptest->load(rep, compiled) == 0);
|
||||
test_map[test_path] = std::unique_ptr<LdmlTestSource>(reptest.release());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "path.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include "kmx/kmx_plus.h"
|
||||
|
|
@ -17,6 +18,34 @@ namespace tests {
|
|||
struct key_event {
|
||||
km_core_virtual_key vk;
|
||||
uint16_t modifier_state;
|
||||
public:
|
||||
key_event() : vk(0), modifier_state(0) {
|
||||
}
|
||||
key_event(km_core_virtual_key k, uint16_t m) :vk(k), modifier_state(m) {
|
||||
}
|
||||
std::string dump() const;
|
||||
int compare(const key_event &other) const {
|
||||
if (vk < other.vk) return -1;
|
||||
if (vk > other.vk) return 1;
|
||||
if (modifier_state < other.modifier_state) return -1;
|
||||
if (modifier_state > other.modifier_state) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool operator<(const key_event &other) const {
|
||||
return compare(other) < 0;
|
||||
}
|
||||
bool operator>(const key_event &other) const {
|
||||
return compare(other) > 0;
|
||||
}
|
||||
bool operator==(const key_event &other) const {
|
||||
return compare(other) == 0;
|
||||
}
|
||||
/** true if unset (null) key */
|
||||
bool empty() const {
|
||||
return vk == 0 && modifier_state == 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
enum ldml_action_type {
|
||||
|
|
@ -40,6 +69,10 @@ enum ldml_action_type {
|
|||
* expected text
|
||||
*/
|
||||
LDML_ACTION_CHECK_EXPECTED,
|
||||
/**
|
||||
* string - keylist to check
|
||||
*/
|
||||
LDML_ACTION_CHECK_KEYLIST,
|
||||
// TODO-LDML: gestures, etc? Depends on touch.
|
||||
|
||||
/**
|
||||
|
|
@ -80,10 +113,17 @@ public:
|
|||
virtual km_core_status get_expected_load_status();
|
||||
virtual const std::u16string &get_context() = 0;
|
||||
virtual bool get_expected_beep() const;
|
||||
/**
|
||||
* fillin a list
|
||||
* @param fillin key list to be filled in with all vkeys in the hardware map.
|
||||
* @param modifier modifier to load key list for
|
||||
* @returns false on load fail, true on OK
|
||||
*/
|
||||
bool get_vkey_table(std::set<key_event> &fillin) const;
|
||||
|
||||
// helper functions
|
||||
static key_event char_to_event(char ch);
|
||||
static uint16_t get_modifier(std::string const m);
|
||||
static uint16_t get_modifier(std::string const &m);
|
||||
static std::u16string parse_source_string(std::string const &s);
|
||||
static std::u16string parse_u8_source_string(std::string const &s);
|
||||
|
||||
|
|
@ -105,6 +145,12 @@ private:
|
|||
|
||||
private:
|
||||
bool _caps_lock_on = false;
|
||||
protected:
|
||||
/** populate rawdata and kmxplus */
|
||||
int load_kmx_plus(const km::core::path &compiled);
|
||||
// copy of the kbd data, for lookups
|
||||
std::vector<uint8_t> rawdata;
|
||||
std::unique_ptr<km::core::kmx::kmx_plus> kmxplus;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, std::unique_ptr<km::tests::LdmlTestSource>> JsonTestMap;
|
||||
|
|
@ -123,9 +169,6 @@ class LdmlJsonTestSourceFactory {
|
|||
const JsonTestMap& get_tests() const;
|
||||
private:
|
||||
JsonTestMap test_map;
|
||||
// copy of the kbd data, for lookups
|
||||
std::vector<uint8_t> rawdata;
|
||||
std::unique_ptr<km::core::kmx::kmx_plus> kmxplus;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -137,7 +180,7 @@ public:
|
|||
/**
|
||||
* Load the test_source from comments in the .xml source
|
||||
*/
|
||||
int load_source(const km::core::path &path);
|
||||
int load_source(const km::core::path &path, const km::core::path &compiled);
|
||||
|
||||
virtual km_core_status get_expected_load_status();
|
||||
virtual const std::u16string &get_context();
|
||||
|
|
@ -146,10 +189,6 @@ public:
|
|||
virtual void next_action(ldml_action &fillin);
|
||||
|
||||
private:
|
||||
|
||||
bool is_token(const std::string token, std::string &line);
|
||||
key_event vkey_to_event(std::string const &vk_event);
|
||||
key_event next_key(std::string &keys);
|
||||
key_event next_key();
|
||||
|
||||
std::deque<std::string> keys;
|
||||
|
|
@ -158,6 +197,24 @@ private:
|
|||
bool expected_beep = false;
|
||||
bool expected_error = false;
|
||||
bool is_done = false;
|
||||
/** did we check the keylist yet? */
|
||||
bool check_keylist = true;
|
||||
/** set the expected keys in the keylist */
|
||||
void set_keylist(std::string const& s) {
|
||||
expected_keylist = parse_source_string(s);
|
||||
}
|
||||
|
||||
std::u16string expected_keylist;
|
||||
|
||||
/** returns false on fail and updates the message */
|
||||
bool handle_check_keylist(std::string &message) const;
|
||||
|
||||
// utility
|
||||
static key_event vkey_to_event(std::string const &vk_event);
|
||||
static bool is_token(const std::string token, std::string &line);
|
||||
|
||||
public:
|
||||
static key_event parse_next_key(std::string &keys);
|
||||
};
|
||||
|
||||
} // namespace tests
|
||||
|
|
|
|||
|
|
@ -243,6 +243,12 @@ The `Keyboard` object describes an individual keyboard in the Keyman package. A
|
|||
|
||||
The filename of the font for the OSK
|
||||
|
||||
`examples`
|
||||
|
||||
: `Array`
|
||||
|
||||
An array of [`Example`](#obj-example) objects linked to the keyboard.
|
||||
|
||||
### The Language object
|
||||
|
||||
The `Language` object describes the language that can be typed with the keyboard
|
||||
|
|
@ -259,6 +265,51 @@ The `Language` object describes the language that can be typed with the keyboard
|
|||
|
||||
[BCP 47 language code](../bcp-47)
|
||||
|
||||
### The Example object
|
||||
|
||||
The `Example` object describes a keying sequence example text for the keyboard.
|
||||
This can be the easiest way for a new user to start using a keyboard, and is
|
||||
particularly helpful when the keyboard makes use of keying sequences that may
|
||||
not be immediately obvious.
|
||||
|
||||
`id`
|
||||
|
||||
: `string`
|
||||
|
||||
The BCP 47 language code for the example.
|
||||
|
||||
`keys`
|
||||
|
||||
: `string`
|
||||
|
||||
The key sequence to type the example. The key sequence must list each key
|
||||
combination, separated by space. The actual text for each key is reasonably
|
||||
arbitrary, to allow you to provide examples for touch keyboards as well as
|
||||
desktop keyboards. There are three special kinds of key strings:
|
||||
* Modifier keys may be specified with the `+` character, e.g. `shift+e` or
|
||||
`right-alt+k`. Use lower case keys. The suggested standard modifiers are:
|
||||
`shift`, `ctrl`, `alt`, `left-alt`, `right-alt`, `left-ctrl`, `right-ctrl`,
|
||||
and `option` (mac).
|
||||
* The <kbd>space</kbd> key itself may be specified with `space`.
|
||||
* To avoid confusion with modifier keys, the <kbd>+</kbd> key can be specified
|
||||
with `plus`.
|
||||
|
||||
For example, the key sequence <kbd>x</kbd>, <kbd>j</kbd>, <kbd>m</kbd>,
|
||||
<kbd>Shift</kbd>+<kbd>e</kbd>, <kbd>r</kbd> may be specified as
|
||||
`x j m shift+e r` or `x j m E r`.
|
||||
|
||||
`text`
|
||||
|
||||
: `string`
|
||||
|
||||
The expected output when the `keys` are typed
|
||||
|
||||
`note`
|
||||
|
||||
: `string`
|
||||
|
||||
A brief explanation of the example, e.g. "Name of language"
|
||||
|
||||
### The LexicalModel object
|
||||
|
||||
The `LexicalModel` object describes an individual model in the Keyman package. A package cannot contain both lexical models and keyboards.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue