Merge pull request #13040 from keymanapp/fix/web/cherrypick-complex-nul-context-handling

fix(web): do not simplify nul-prefixed contexts when rule-matching 🍒 🏠
This commit is contained in:
Joshua Horton 2025-01-27 08:11:08 +07:00 committed by GitHub
commit 4be65cab1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 143 additions and 20 deletions

View file

@ -399,18 +399,6 @@ export default class KeyboardInterface extends KeyboardHarness {
// Now that we have the cache...
var subCache = cache;
subCache.valContext = subCache.valContext.slice(0, ln);
for(var i=0; i < subCache.valContext.length; i++) {
if(subCache[i] == '\ufffe') {
subCache.valContext.splice(0, 1);
subCache.deadContext.splice(0, 1);
}
}
if(subCache.valContext.length == 0) {
subCache.valContext = ['\ufffe'];
subCache.deadContext = [];
}
this.cachedContextEx.set(n, ln, subCache);
return subCache;

View file

@ -8,6 +8,8 @@ import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-
import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core';
const NUL = '\uFFFE';
/*
* ABOUT THIS TEST SUITE
* ---------------------
@ -35,20 +37,15 @@ let device = {
browser: 'native'
}
/** @type {KeyboardInterface} */
let keyboardWithHarness;
var toSupplementaryPairString = function(code){
var H = Math.floor((code - 0x10000) / 0x400) + 0xD800;
var L = (code - 0x10000) % 0x400 + 0xDC00;
return String.fromCharCode(H, L);
}
function runEngineRuleSet(ruleSet, defaultNoun) {
defaultNoun = defaultNoun ? defaultNoun : "Rule";
for(var i = 0; i < ruleSet.length; i++) {
var ruleDef = ruleSet[i]; // for example, DEADKEY_TEST_1.
keyboardWithHarness.resetContextCache();
var matchDefs = [{
sequence: ruleDef.baseSequence,
@ -843,6 +840,122 @@ var DEADKEY_STORE_TEST_4 = {
}]
};
/* Keyman language equivalent:
*
* nul > 'success'
*/
var NUL_TEST_1 = {
id: 1,
// Match condition for rule
rule: [{t: 'n'}],
// Start of context relative to cursor
n: 1,
ln: 1,
// Resulting context map
contextCache: [ NUL ],
baseSequence: { "output": "", "inputs": []},
fullMatchDefs: []
};
/* Keyman language equivalent:
*
* nul any(abc) > 'success'
*/
var NUL_TEST_2 = {
id: 1,
// Match condition for rule
rule: [{t: 'n'}, {t: 'a', a: ['a', 'b', 'c']}],
// Start of context relative to cursor
n: 2,
ln: 2,
// Resulting context map
contextCache: [ NUL, 'a' ],
baseSequence: { "output": "a", "inputs": [
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
]},
fullMatchDefs: []
};
/* Keyman language equivalent:
*
* nul nul any(abc) context(3) > 'success'
*
* This one may... "stretch" what's actually allowed by Keyman language rules,
* but we wish to ensure that the actual context management is capable of
* handling this.
*/
var NUL_TEST_3 = {
id: 1,
// Match condition for rule
rule: [{t: 'n'}, {t: 'n'}, {t: 'a', a: ['a', 'b', 'c']}, {t: 'c', c: 3}],
// Start of context relative to cursor
n: 4,
ln: 4,
// Resulting context map
contextCache: [ NUL, NUL, 'a', 'a' ],
baseSequence: { "output": "a", "inputs": [
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
]},
fullMatchDefs: [
{
sequence: { "output": "a", "inputs": [
{"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //a
]},
result: false,
msg: "Rule 2: matched deadkey with nul"
}, {
sequence: { "output": "a", "inputs": [
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //a
]},
result: false,
msg: "Rule 2: matched character with nul"
}
]
};
/* Keyman language equivalent:
*
* nul nul dk(1) any(abc) > 'success'
*
* This may also "stretch" what's actually allowed by Keyman language rules,
* but we wish to ensure that the actual context management is capable of
* handling this.
*/
var NUL_TEST_4 = {
id: 1,
// Match condition for rule
rule: [{t: 'n'}, {t: 'n'}, {t: 'd', d: 1}, {t: 'a', a: ['a', 'b', 'c']}],
// Start of context relative to cursor
n: 4,
ln: 4,
// Resulting context map
contextCache: [ NUL, NUL, 1, 'a' ],
baseSequence: { "output": "a", "inputs": [
{"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //a
]},
fullMatchDefs: [
{
sequence: { "output": "a", "inputs": [
{"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
{"type":"key","keyCode":50,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true}, //1
{"type":"key","keyCode":65,"states":10752,"modifiers":0,"modifierChanged":false,"isVirtualKey":true} //a
]},
result: false,
msg: "Rule 2: matched extra deadkey with nul"
}
]
};
/* Keyman language equivalent:
*
* store(match) 'abc'
@ -974,10 +1087,12 @@ var ANY_INDEX_RULE_SET = [ ANY_INDEX_TEST_1, ANY_INDEX_TEST_2, ANY_INDEX_TEST_3
var DEADKEY_STORE_RULE_SET = [ DEADKEY_STORE_TEST_1, DEADKEY_STORE_TEST_2, DEADKEY_STORE_TEST_3,
DEADKEY_STORE_TEST_4 ];
var NUL_RULE_SET = [ NUL_TEST_1, NUL_TEST_2, NUL_TEST_3, NUL_TEST_4 ];
var NOTANY_NUL_RULE_SET = [ NOTANY_NUL_TEST_1, NOTANY_NUL_TEST_2, NOTANY_NUL_TEST_3 ];
var FULL_RULE_SET = [].concat(DEADKEY_RULE_SET, ANY_CONTEXT_RULE_SET, ANY_INDEX_RULE_SET,
DEADKEY_STORE_RULE_SET, NOTANY_NUL_RULE_SET);
DEADKEY_STORE_RULE_SET, NUL_RULE_SET, NOTANY_NUL_RULE_SET);
// -----------
@ -1086,6 +1201,24 @@ describe('Engine - Context Matching', function() {
});
});
describe('handles interactions with nul in requested context', function() {
it('with only a single nul in context range; no text: NUL_TEST_1', function () {
runEngineRuleSet([NUL_TEST_1]);
});
it(`with context [nul, 'a'] in range: NUL_TEST_2`, function () {
runEngineRuleSet([NUL_TEST_2]);
});
it(`with context [nul, 'a', 'a'] in range: NUL_TEST_3`, function () {
runEngineRuleSet([NUL_TEST_3]);
});
it(`with context [nul, dk(1), 'a'] in range: NUL_TEST_4`, function () {
runEngineRuleSet([NUL_TEST_4]);
});
});
describe('handles interactions between notany and nul in context', function() {
it('with notany against a store with pure characters: NOTANY_NUL_TEST_1', function() {
runEngineRuleSet([NOTANY_NUL_TEST_1]);

View file

@ -19,10 +19,12 @@ const device = {
browser: 'native'
}
/** @type {KeyboardInterface} */
let keyboardWithHarness;
function runEngineRuleSet(ruleSet) {
for(let ruleDef of ruleSet) {
keyboardWithHarness.resetContextCache();
// Prepare the context!
const ruleSeq = new RecordedKeystrokeSequence(ruleDef);
const proctor = new NodeProctor(keyboardWithHarness, device, assert.equal);