Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(developer): ldml: more improvement for key-not-found 🙀 #10236

Merged
merged 11 commits into from
Dec 20, 2023
Merged
3 changes: 1 addition & 2 deletions core/src/ldml/ldml_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,8 @@ ldml_processor::process_key_down(km_core_state *state, km_core_virtual_key vk, u
// no key was found, so pass the keystroke on to the Engine
emit_invalidate_passthrough_keystroke(state, vk, modifier_state);
} else if (!key_str.empty()) {
// TODO-LDML: Right now we take no action on empty (i.e. gap) keys. Should we take other action?
process_key_string(state, key_str);
}
} // else no action: It's a gap or gap-like key.
}

void
Expand Down
2 changes: 1 addition & 1 deletion core/tests/unit/ldml/ldml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ apply_action(
assert(context.back().character == ch);
context.pop_back();
} else {
// assume it's otherwise KM-coRE_BT_UNKNOWN
// assume it's otherwise KM_CORE_BT_UNKNOWN
assert(act.backspace.expected_type == KM_CORE_BT_UNKNOWN);
assert(context.empty()); // if KM_CORE_BT_UNKNOWN, context should be empty.
}
Expand Down
78 changes: 66 additions & 12 deletions developer/src/kmc-ldml/src/compiler/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import ListItem = KMXPlus.ListItem;
import KeysFlicks = KMXPlus.KeysFlicks;
import { allUsedKeyIdsInFlick, allUsedKeyIdsInKey, allUsedKeyIdsInLayers, calculateUniqueKeys, hashFlicks, hashKeys, translateLayerAttrToModifier, validModifier } from '../util/util.js';
import { MarkerTracker, MarkerUse } from './marker-tracker.js';
import { KeysKeys } from '../../../../../common/web/types/src/kmx/kmx-plus.js';
srl295 marked this conversation as resolved.
Show resolved Hide resolved

/** reserved name for the special gap key. space is not allowed in key ids. */
const reserved_gap = "gap (reserved)";


export class KeysCompiler extends SectionCompiler {
static validateMarkers(
Expand Down Expand Up @@ -193,9 +198,48 @@ export class KeysCompiler extends SectionCompiler {
}
} // else: TODO-LDML do nothing if only touch layers

// Now load the reserved keys and slip them in here
const reservedKeys = this.getReservedKeys(sections);
for (const key of reservedKeys.values()) {
sect.keys.push(key);
}

return sect;
}

/** list of reserved keys, for tests */
public static readonly reserved_keys = [ reserved_gap ];
/** count of reserved keys, for tests */
public static readonly reserved_count = KeysCompiler.reserved_keys.length;

/** load up all reserved keys */
getReservedKeys(sections: KMXPlus.DependencySections) : Map<String, KeysKeys> {
const r = new Map<String, KeysKeys>();

// set up some constants..
const no_string = sections.strs.allocString('');
const no_list = sections.list.allocList([], {}, sections);

// now add the reserved key(s).
r.set(reserved_gap, {
flags: constants.keys_key_flags_gap | constants.keys_key_flags_extend,
id: sections.strs.allocString(reserved_gap),
flicks: '',
longPress: no_list,
longPressDefault: no_string,
multiTap: no_list,
switch: no_string,
to: no_string,
width: 1.0,
});

if (r.size !== KeysCompiler.reserved_count) {
throw Error(`Internal Error: KeysCompiler.reserved_count=${KeysCompiler.reserved_count} != ${r.size} actual reserved keys.`);
}

return r;
}

static addUsedGestureKeys(layerKeyIds: string[], keyBag: Map<string, LDMLKeyboard.LKKey>, usedKeys: Set<string>) {
for (let keyId of layerKeyIds) {
const key = keyBag.get(keyId);
Expand Down Expand Up @@ -436,22 +480,32 @@ export class KeysCompiler extends SectionCompiler {
const mod = translateLayerAttrToModifier(layer);
const keymap = this.getKeymapFromForm(hardware);

let y = -1;
for (let row of layer.row) {
y++;
// Iterate over rows (y) and cols (x) of the scancodes table.
// Any assigned keys will be used until we run out of keys in each row,
// and run out of rows. The rest will be reserved_gap.

const keys = row.keys.split(" ");
let x = -1;
for (let key of keys) {
x++;
for (let y = 0; y < keymap.length; y++) {
let keys : string[];

// if there are keys, use them.
if (y < layer.row.length ) {
const row = layer.row[y];
keys = row.keys.split(" ");
} else {
keys = [];
}

// TODO-LDML: we already validated that the key exists, above.
// So here we only need the ID?
// let keydef = this.keyboard3.keys?.key?.find(x => x.id == key);
// all columns in this row
for (let x = 0; x < keymap[y].length; x++) {
const vkey = keymap[y][x]; // from the scan table

let key = reserved_gap; // unless there's a key in this row
if (x < keys.length) {
key = keys[x];
}
sect.kmap.push({
vkey: keymap[y][x],
mod: mod,
vkey,
mod,
key, // key id, to be changed into key index at finalization
});
}
Expand Down
27 changes: 15 additions & 12 deletions developer/src/kmc-ldml/test/test-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('keys', function () {
const keys = <Keys> sect;
assert.ok(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 2);
assert.equal(keys.keys.length, 2 + KeysCompiler.reserved_count); //
assert.equal(keys.flicks.length, 1); // there's always a 'null' flick
// ids are in sorted order in memory`
assert.isTrue(keys.keys[0].to.isOneChar);
Expand All @@ -37,7 +37,7 @@ describe('keys', function () {
const keys = <Keys> sect;
assert.ok(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 12); // includes flick and gesture keys
assert.equal(keys.keys.length, 12 + KeysCompiler.reserved_count); // includes flick and gesture keys

const [w] = keys.keys.filter(({ id }) => id.value === 'w');
assert.ok(w);
Expand Down Expand Up @@ -71,7 +71,7 @@ describe('keys', function () {
const keys = <Keys> sect;
assert.ok(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 12); // flick and gesture keys
assert.equal(keys.keys.length, 12 + KeysCompiler.reserved_count); // flick and gesture keys

const [q] = keys.keys.filter(({ id }) => id.value === 'q');
assert.ok(q);
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('keys', function () {
callback(sect) {
const keys = <Keys> sect;
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 4);
assert.equal(keys.keys.length, 4 + KeysCompiler.reserved_count);

const [Qgap] = keys.keys.filter(({ id }) => id.value === 'Q');
assert.ok(Qgap);
Expand All @@ -116,7 +116,7 @@ describe('keys', function () {
subpath: 'sections/keys/escaped2.xml',
callback: (keys, subpath, callbacks) => {
assert.isNotNull(keys);
assert.equal((<Keys>keys).keys.length, 1);
assert.equal((<Keys>keys).keys.length, 1 + KeysCompiler.reserved_count);
const [q] = (<Keys>keys).keys.filter(({ id }) => id.value === 'grave');
assert.equal(q.to.value, String.fromCodePoint(0x1faa6));
},
Expand All @@ -127,7 +127,7 @@ describe('keys', function () {
const keys = <Keys> sect;
assert.ok(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 5);
assert.equal(keys.keys.length, 5 + KeysCompiler.reserved_count);

const ww = keys.keys.find(({ id }) => id.value === 'ww');
assert.ok(ww);
Expand All @@ -151,7 +151,9 @@ describe('keys.kmap', function () {
let keys = await loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks) as Keys;
assert.isNotNull(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.kmap.length, 2);
// skip reserved (gap) keys
assert.equal(keys.kmap.filter(({key}) => !/ /.test(key)).length, 2);
assert.equal(keys.kmap.length, 48); // # of non-frame keys on US keyboard
});

testCompilationCases(KeysCompiler, [
Expand All @@ -161,8 +163,9 @@ describe('keys.kmap', function () {
const keys = sect as Keys;
assert.isNotNull(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 4);
assert.sameDeepMembers(keys.kmap, [
assert.equal(keys.keys.length, 4 + KeysCompiler.reserved_count);
// skip reserved (gap) keys
assert.sameDeepMembers(keys.kmap.filter(({key}) => !/ /.test(key)), [
{
vkey: K.K_BKQUOTE,
key: 'qqq',
Expand Down Expand Up @@ -262,7 +265,7 @@ describe('keys.kmap', function () {
const keys = sect as Keys;
assert.isNotNull(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 3);
assert.equal(keys.keys.length, 3 + KeysCompiler.reserved_count);
assert.sameDeepMembers(keys.kmap, [
{
vkey: K.K_K,
Expand Down Expand Up @@ -292,7 +295,7 @@ describe('keys.kmap', function () {
const keys = sect as Keys;
assert.isNotNull(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 3);
assert.equal(keys.keys.length, 3 + KeysCompiler.reserved_count);
assert.sameDeepMembers(keys.kmap, [
{
vkey: K.K_K,
Expand Down Expand Up @@ -365,6 +368,6 @@ describe('keys.kmap', function () {
let keys = await loadSectionFixture(KeysCompiler, 'sections/keys/gap-switch.xml', compilerTestCallbacks) as Keys;
assert.isNotNull(keys);
assert.equal(compilerTestCallbacks.messages.length, 0);
assert.equal(keys.keys.length, 4);
assert.equal(keys.keys.length, 4 + KeysCompiler.reserved_count);
});
});