Backspace, Enter, Tab, Escape and the arrow and page keys are matched by keysym alone -- strans.c:852, 875 and 882 name them, and searchkey names the same set at strans.c:746-786. Every other key above Kspec falls through to the catch-all at strans.c:898, `ks >= Kspec || chord(mod)`, and goes to the application. So the special keys strans knows by name are the ones it takes under a modifier, and the ones it does not know are the ones it hands over. That is backwards: a named special key under a modifier is exactly the one the application has a binding for. In Korean a syllable is pending for nearly all the time anyone is typing, ko.c holding one and no more, and the guards at strans.c:876 and 883 return 0 only when nothing is pending -- so the key is eaten precisely when it is wanted. Type 안녕하세요 and reach for Ctrl+Backspace to take the word back: 요 loses ㅛ, then ㅇ, and the word itself goes on the third press. Ctrl+Enter in a chat box, Ctrl+Tab in a browser and Ctrl+PageDown in either are the same key eaten by the same lines. before after Korean 가, Ctrl+Backspace eaten, pre ㄱ passed, commit 가 Korean 가, Alt+Backspace eaten, pre ㄱ passed, commit 가 Korean 가, Super+Backspace eaten, pre ㄱ passed, commit 가 Korean 가, Backspace eaten, pre ㄱ unchanged Korean 가, Shift+Backspace eaten, pre ㄱ unchanged かく, Ctrl+Enter eaten, commit かく passed, commit かく かく, Ctrl+Tab eaten, commit かく passed, commit かく かく Space, Ctrl+PageDown eaten, sel 0 -> 9 passed, commit 確 かく Space, PageDown eaten, sel 0 -> 9 unchanged かく Space, Shift+Tab eaten, sel 0 -> 30 unchanged Ctrl+E sm, Ctrl+Backspace eaten, query s passed, commit sm Ctrl+E sm, Backspace eaten, query s unchanged chord() cannot be reused here. It is `(mod & ~Mshift) != 0 && mod != Mctrl` and the exclusion is deliberate, since Ctrl+letter is strans's whole command set and chord() has to let plain Ctrl through. The rule this needs is the other one -- any modifier that is not Shift -- and Shift must stay in: Shift+Tab cycles the candidates backwards, pinned by engine/candidate-completion and engine/emoji-navigation at engine_test.c:914 and 1649, and Shift on a Korean key is what makes ㅃ. One line serves both paths because it sits above the searchkey dispatch, and it has to sit below the switch at strans.c:829: 한자, 한/영, 変換 and 無変換 arrive as keys above Kspec and are rewritten there into the Ctrl chords they stand for. Above the switch, 한자 would commit and pass instead of opening the Hanja list; below it, pressing it with Ctrl held still opens the list, because the switch sets the modifier itself. What this does not cover: Space, which is below Kspec, so searchkey:763 still picks a candidate on Ctrl+Space inside a search. transition tests !(mod & ~Mshift) for its own Space at strans.c:865, so the two disagree there. Left alone: what Ctrl+Space should mean wants its own argument, not a widened guard.
2246 lines
58 KiB
C
2246 lines
58 KiB
C
#include "strans.c"
|
||
#include "test.h"
|
||
|
||
static int typekeys(struct ct*, char*, Str*);
|
||
static int draindraw(Drawcmd*);
|
||
|
||
/* One key from the owner, as imhandlekey drives the engine. */
|
||
static int
|
||
keystroke(u32int ks, u32int mod, Str *com)
|
||
{
|
||
int eaten;
|
||
|
||
eaten = keymeaningful(ks) && transition(ks, mod, com);
|
||
redraw();
|
||
return eaten;
|
||
}
|
||
|
||
void
|
||
testengineinit(int lang)
|
||
{
|
||
init();
|
||
im.l = getlang(lang);
|
||
}
|
||
|
||
void
|
||
testenginehandle(Keyreq *req)
|
||
{
|
||
imhandlekey(req);
|
||
}
|
||
|
||
void*
|
||
testengineowner(void)
|
||
{
|
||
return activeowner;
|
||
}
|
||
|
||
void
|
||
testenginepreedit(Str *preedit)
|
||
{
|
||
impre(&im, preedit);
|
||
}
|
||
|
||
void
|
||
testenginecaret(Caret *at)
|
||
{
|
||
*at = caret;
|
||
}
|
||
|
||
void
|
||
vietnamese_state_lifetime(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
init();
|
||
im.l = getlang(LangVI);
|
||
im.pre = mkstr("as");
|
||
im.raw = mkstr("as");
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed Telex", "á", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
|
||
init();
|
||
im.l = getlang(LangVI);
|
||
im.pre = mkstr("as");
|
||
im.raw = mkstr("as");
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke(Kesc, 0, &com));
|
||
checkstr(t, "Escape commits Telex", "á", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
|
||
init();
|
||
im.l = getlang(LangVI);
|
||
im.pre = mkstr("as");
|
||
im.raw = mkstr("as");
|
||
sclear(&com);
|
||
CT_CHECK(t, dotrans('q', &com));
|
||
checkstr(t, "completed Telex", "á", &com);
|
||
checkstr(t, "next preedit", "q", &im.pre);
|
||
checkstr(t, "next raw input", "q", &im.raw);
|
||
}
|
||
|
||
void
|
||
engine_clears_candidates(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
im.pre = mkstr("ㄱ");
|
||
im.nkouho = 2;
|
||
im.sel = 1;
|
||
sclear(&com);
|
||
dotrans('k', &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
}
|
||
|
||
void
|
||
engine_backspace_clears_candidates(struct ct *t)
|
||
{
|
||
Lang *lang;
|
||
Str com, shown;
|
||
|
||
lang = getlang(LangJP);
|
||
init();
|
||
im.l = lang;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('n', 0, &com));
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "before backspace", "な", &shown);
|
||
im.kouho[0] = mkstr("stale-candidate");
|
||
im.nkouho = 1;
|
||
im.sel = 0;
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
checkstr(t, "chosen candidate backspace", "na", &im.raw);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "backspace commit", "", &com);
|
||
checkstr(t, "backspace deletes the kana shown", "", &im.raw);
|
||
impre(&im, &shown);
|
||
checkstr(t, "shown preedit after backspace", "", &shown);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
}
|
||
|
||
void
|
||
engine_selects_visible_candidate(struct ct *t)
|
||
{
|
||
static const struct { int n, sel; Rune key; char *want; } cases[] = {
|
||
{ Maxdisp, 0, '9', "c9" },
|
||
{ Maxkouho, Maxdisp, '1', "c10" },
|
||
{ Maxkouho, 2*Maxdisp, '9', "c27" },
|
||
};
|
||
char name[8];
|
||
Str com;
|
||
int i, j;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
for(j = 0; j < cases[i].n; j++){
|
||
snprint(name, sizeof name, "c%d", j+1);
|
||
im.kouho[j] = mkstr(name);
|
||
}
|
||
im.nkouho = cases[i].n;
|
||
im.sel = cases[i].sel;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, 0, &com));
|
||
checkstr(t, cases[i].want, cases[i].want, &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_shortcut_modifiers(struct ct *t)
|
||
{
|
||
static u32int mods[] = { Mshift, Mctrl, Malt, Msuper };
|
||
Str com, selected;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(mods); i++){
|
||
init();
|
||
im.kouho[9] = mkstr("must-not-select");
|
||
im.nkouho = 10;
|
||
im.sel = -1;
|
||
selected = im.kouho[9];
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('1', mods[i], &com));
|
||
CT_CHECK(t, scmp(&com, &selected) != 0);
|
||
}
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
|
||
Caret *caret)
|
||
{
|
||
Channel *reply;
|
||
Keyreq req;
|
||
Keyres res;
|
||
|
||
reply = chancreate(sizeof(Keyres), 1);
|
||
memset(&req, 0, sizeof req);
|
||
req.owner = owner;
|
||
req.clientpre = cap;
|
||
req.op = op;
|
||
req.ks = key;
|
||
req.mod = mod;
|
||
if(caret != nil)
|
||
req.caret = *caret;
|
||
req.reply = reply;
|
||
imhandlekey(&req);
|
||
chanrecv(reply, &res);
|
||
chanfree(reply);
|
||
return res;
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
|
||
{
|
||
return ownerrequestatcap(owner, 0, op, key, mod, caret);
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequest(void *owner, int op, Rune key, u32int mod)
|
||
{
|
||
return ownerrequestat(owner, op, key, mod, nil);
|
||
}
|
||
|
||
static Keyres
|
||
ownerrequestcap(void *owner, int cap, int op, Rune key, u32int mod)
|
||
{
|
||
return ownerrequestatcap(owner, cap, op, key, mod, nil);
|
||
}
|
||
|
||
void
|
||
engine_active_owner_lifecycle(struct ct *t)
|
||
{
|
||
Keyres res;
|
||
Str shown;
|
||
char a, b, c;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
res = ownerrequest(&a, Keypress, 'k', 0);
|
||
CT_CHECK(t, res.eaten);
|
||
res = ownerrequest(&a, Keypress, 'a', 0);
|
||
checkstr(t, "first owner preedit", "か", &res.preedit);
|
||
CT_EQ_PTR(t, &a, activeowner);
|
||
|
||
res = ownerrequest(&b, Keypress, 'n', 0);
|
||
checkstr(t, "takeover resets old preedit", "ん", &res.preedit);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
res = ownerrequest(&a, Keyreset, 0, 0);
|
||
checkstr(t, "the taken owner gets its reading back", "か", &res.commit);
|
||
CT_EQ_INT(t, 0, res.preedit.n);
|
||
impre(&im, &shown);
|
||
checkstr(t, "stale reset preserves owner", "ん", &shown);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
|
||
res = ownerrequest(&b, Keypress, 'y', 0);
|
||
res = ownerrequest(&b, Keypress, 'a', 0);
|
||
checkstr(t, "current owner continues", "にゃ", &res.preedit);
|
||
ownerrequest(&a, Keyrelease, 0, 0);
|
||
impre(&im, &shown);
|
||
checkstr(t, "stale release preserves owner", "にゃ", &shown);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
|
||
res = ownerrequest(&b, Keyrelease, 0, 0);
|
||
checkstr(t, "release hands the reading back", "にゃ", &res.commit);
|
||
CT_EQ_PTR(t, nil, activeowner);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
ownerrequest(&c, Keypress, 0, 0);
|
||
CT_EQ_PTR(t, nil, activeowner);
|
||
res = ownerrequest(&c, Keypress, 'k', 0);
|
||
CT_EQ_PTR(t, &c, activeowner);
|
||
checkstr(t, "next owner first key", "k", &res.preedit);
|
||
|
||
/* Typing is being heard from, as much as a reset or a release is. */
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
ownerrequest(&a, Keypress, 'k', 0);
|
||
ownerrequest(&a, Keypress, 'a', 0);
|
||
ownerrequest(&b, Keypress, 'n', 0);
|
||
CT_EQ_PTR(t, &b, activeowner);
|
||
res = ownerrequest(&a, Keypress, 'k', 0);
|
||
checkstr(t, "the taken owner types and gets its reading back", "か",
|
||
&res.commit);
|
||
CT_EQ_PTR(t, &a, activeowner);
|
||
res = ownerrequest(&b, Keyreset, 0, 0);
|
||
checkstr(t, "the owner it took from keeps its own", "ん", &res.commit);
|
||
}
|
||
|
||
void
|
||
engine_active_owner_reset(struct ct *t)
|
||
{
|
||
Caret at;
|
||
Keyres res;
|
||
char owner;
|
||
|
||
memset(&at, 0, sizeof at);
|
||
at.valid = 1;
|
||
at.x = 10;
|
||
at.y = 20;
|
||
at.h = 14;
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
ownerrequestat(&owner, Keypress, 'k', 0, &at);
|
||
res = ownerrequest(&owner, Keypress, 'a', 0);
|
||
checkstr(t, "preedit before reset", "か", &res.preedit);
|
||
res = ownerrequest(&owner, Keyreset, 0, 0);
|
||
CT_EQ_PTR(t, &owner, activeowner);
|
||
checkstr(t, "reset hands the reading back", "か", &res.commit);
|
||
CT_EQ_INT(t, 0, res.preedit.n);
|
||
res = ownerrequest(&owner, Keypress, 'n', 0);
|
||
checkstr(t, "same owner after reset", "ん", &res.preedit);
|
||
}
|
||
|
||
void
|
||
engine_active_owner_caret(struct ct *t)
|
||
{
|
||
Caret a, moved;
|
||
Drawcmd dc;
|
||
char one, two;
|
||
|
||
memset(&a, 0, sizeof a);
|
||
a.valid = 1;
|
||
a.x = 10;
|
||
a.y = 20;
|
||
a.h = 14;
|
||
moved = a;
|
||
moved.x = 80;
|
||
init();
|
||
draindraw(nil);
|
||
im.l = getlang(LangEN);
|
||
ownerrequestat(&one, Keypress, 'x', 0, &a);
|
||
CT_CHECK(t, caret.valid);
|
||
CT_EQ_INT(t, 10, caret.x);
|
||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 80, caret.x);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
im.kouho[0] = mkstr("candidate");
|
||
im.nkouho = 1;
|
||
im.sel = 0;
|
||
redraw();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 80, dc.caret.x);
|
||
moved.x = 90;
|
||
ownerrequestat(&two, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 80, caret.x);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 90, caret.x);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 90, dc.caret.x);
|
||
ownerrequestat(&one, Keycaret, 0, 0, &moved);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
ownerrequestat(&one, Keypress, 'x', 0, &moved);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
moved.x = 100;
|
||
ownerrequestat(&one, Keypress, 'x', 0, &moved);
|
||
CT_EQ_INT(t, 100, caret.x);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 100, dc.caret.x);
|
||
ownerrequest(&two, Keypress, 'n', 0);
|
||
CT_EQ_PTR(t, &two, activeowner);
|
||
CT_CHECK(t, !caret.valid);
|
||
}
|
||
|
||
void
|
||
engine_vietnamese_client_preedit(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *shown;
|
||
} cases[] = {
|
||
{ "as", "á" },
|
||
{ "aa", "â" },
|
||
{ "ddoong", "đông" },
|
||
{ "hoaf", "hoà" },
|
||
{ "quas", "quá" },
|
||
};
|
||
Drawcmd dc;
|
||
Keyres res;
|
||
Str com, shown;
|
||
char *k, owner;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = getlang(LangVI);
|
||
draindraw(nil);
|
||
sclear(&com);
|
||
for(k = cases[i].keys; *k != '\0'; k++){
|
||
res = ownerrequestcap(&owner, 1,
|
||
Keypress, *k, 0);
|
||
sappend(&com, &res.commit);
|
||
if(!res.eaten)
|
||
sputr(&com, *k);
|
||
}
|
||
/* What the client shows, what the popup shows, and what
|
||
* Enter commits are the same composed text. */
|
||
shown = com;
|
||
sappend(&shown, &res.preedit);
|
||
checkstr(t, "client preedit", cases[i].shown, &shown);
|
||
clientpre = 0;
|
||
redraw();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 0, scmp(&dc.pre, &res.preedit));
|
||
CT_CHECK(t, !keystroke(Kret, 0, &com));
|
||
checkstr(t, "commit", cases[i].shown, &com);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_commit_contract(struct ct *t)
|
||
{
|
||
static const struct {
|
||
Rune key;
|
||
char *one;
|
||
char *all;
|
||
} cases[] = {
|
||
{ 'z', "ㅋ", "ㅋㅋㅋ" },
|
||
{ 'r', "ㄱ", "ㄱㄱㄱ" },
|
||
{ 'd', "ㅇ", "ㅇㅇㅇ" },
|
||
{ 'k', "ㅏ", "ㅏㅏㅏ" },
|
||
};
|
||
Keyres res;
|
||
Str all, com;
|
||
char owner;
|
||
int i, j;
|
||
|
||
for(j = 0; j < nelem(cases); j++){
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&all);
|
||
for(i = 0; i < 3; i++){
|
||
res = ownerrequestcap(&owner, 1,
|
||
Keypress, cases[j].key, 0);
|
||
CT_CHECK(t, res.eaten);
|
||
checkstr(t, "repeated jamo commit",
|
||
i == 0 ? "" : cases[j].one, &res.commit);
|
||
checkstr(t, "repeated jamo preedit", cases[j].one,
|
||
&res.preedit);
|
||
sappend(&all, &res.commit);
|
||
}
|
||
sappend(&all, &res.preedit);
|
||
checkstr(t, "repeated jamo total", cases[j].all, &all);
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "return commit", "か", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
im.kouho[0] = mkstr("c1");
|
||
im.kouho[1] = mkstr("c2");
|
||
im.nkouho = 2;
|
||
im.sel = 0;
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "selected candidate", "c2", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
im.nkouho = 1;
|
||
im.sel = -1;
|
||
CT_CHECK(t, !keystroke('!', 0, &com));
|
||
checkstr(t, "uneaten-key commit", "か", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
|
||
/* Without candidates the movement keys commit like any other. */
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(!typekeys(t, "dy", &com))
|
||
return;
|
||
CT_CHECK(t, !keystroke(Kdown, 0, &com));
|
||
checkstr(t, "arrow commit", "요", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
return;
|
||
/* A key outside the map commits the reading and passes on. */
|
||
CT_CHECK(t, !keystroke(0xf008, 0, &com));
|
||
checkstr(t, "non-ASCII key commit", "か", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
}
|
||
|
||
void
|
||
engine_telex_history_bound(struct ct *t)
|
||
{
|
||
Str com;
|
||
Rune tone;
|
||
int i;
|
||
|
||
init();
|
||
im.l = getlang(LangVI);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
for(i = 0; i < Maxrunes; i++){
|
||
tone = i % 2 == 0 ? 's' : 'f';
|
||
if(!CT_CHECK(t, keystroke(tone, 0, &com)))
|
||
break;
|
||
}
|
||
checkstr(t, "bounded Telex commit", "à", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
}
|
||
|
||
void
|
||
engine_korean_modifiers_and_backspace(struct ct *t)
|
||
{
|
||
static const struct {
|
||
Rune key;
|
||
char *plain;
|
||
char *shifted;
|
||
} cases[] = {
|
||
{ 'r', "ㄱ", "ㄲ" }, { 'e', "ㄷ", "ㄸ" },
|
||
{ 'q', "ㅂ", "ㅃ" }, { 't', "ㅅ", "ㅆ" },
|
||
{ 'w', "ㅈ", "ㅉ" }, { 'o', "ㅐ", "ㅒ" },
|
||
{ 'p', "ㅔ", "ㅖ" },
|
||
};
|
||
Drawcmd dc;
|
||
Str com;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, 0, &com));
|
||
checkstr(t, "plain Korean key", cases[i].plain, &im.pre);
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, Mshift, &com));
|
||
checkstr(t, "shifted Korean key", cases[i].shifted, &im.pre);
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key - 'a' + 'A', 0, &com));
|
||
checkstr(t, "Caps Lock Korean key", cases[i].plain, &im.pre);
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, Mshift, &com));
|
||
checkstr(t, "Shift+Caps Korean key", cases[i].shifted, &im.pre);
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "rkr", &com)){
|
||
checkstr(t, "Korean engine preedit", "각", &im.pre);
|
||
draindraw(nil);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Korean final backspace", "가", &im.pre);
|
||
if(CT_CHECK(t, draindraw(&dc) == 1))
|
||
checkstr(t, "Korean backspace redraw", "가", &dc.pre);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Korean vowel backspace", "ㄱ", &im.pre);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_CHECK(t, !keystroke(Kback, 0, &com));
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "rk", &com)){
|
||
CT_CHECK(t, !keystroke(Kback, Mctrl, &com));
|
||
checkstr(t, "Ctrl+Backspace commits and passes", "가", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_direct_language_modes(struct ct *t)
|
||
{
|
||
Drawcmd dc;
|
||
Str com;
|
||
char owner;
|
||
|
||
init();
|
||
draindraw(nil);
|
||
sclear(&com);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
/* A switch shows the mode it switched to until the next key. */
|
||
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
checkstr(t, "mode mark", "한", &dc.pre);
|
||
CT_CHECK(t, keystroke('t', Mctrl, &com));
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
checkstr(t, "mode mark", "A", &dc.pre);
|
||
CT_CHECK(t, !keystroke('a', 0, &com));
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_CHECK(t, !keystroke('a', 0, &com));
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
CT_EQ_PTR(t, nil, im.l->mapname);
|
||
CT_EQ_PTR(t, nil, im.l->map);
|
||
CT_EQ_PTR(t, nil, im.l->trans);
|
||
CT_CHECK(t, !keystroke('a', 0, &com));
|
||
CT_CHECK(t, !keystroke('Z', Mshift, &com));
|
||
checkstr(t, "English direct commit", "", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_CHECK(t, !search.lang);
|
||
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
CT_EQ_PTR(t, nil, im.l->mapname);
|
||
CT_EQ_PTR(t, nil, im.l->map);
|
||
CT_EQ_PTR(t, nil, im.l->dictname);
|
||
CT_CHECK(t, strcmp(getlang(LangHANJA)->dictname, "hanja") == 0);
|
||
CT_CHECK(t, keystroke('r', 0, &com));
|
||
checkstr(t, "Korean without map", "ㄱ", &im.pre);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
|
||
/* Leaving takes the mark away as surely as typing does. */
|
||
init();
|
||
draindraw(nil);
|
||
ownerrequest(&owner, Keypress, 's', Mctrl);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
checkstr(t, "mode mark", "한", &dc.pre);
|
||
ownerrequest(&owner, Keyrelease, 0, 0);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
}
|
||
|
||
void
|
||
engine_language_switch_state(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *name;
|
||
Rune key;
|
||
int mod;
|
||
int lang;
|
||
char *pre;
|
||
char *raw;
|
||
char *commit;
|
||
int stale;
|
||
} steps[] = {
|
||
{ "select Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 },
|
||
{ "Vietnamese a", 'a', 0, LangVI, "a", "a", "", 0 },
|
||
{ "Vietnamese tone", 's', 0, LangVI, "as", "as", "", 0 },
|
||
{ "switch to Korean", 's', Mctrl, LangKO, "", "", "á", 1 },
|
||
{ "Korean initial", 'r', 0, LangKO, "ㄱ", "", "", 0 },
|
||
{ "Korean syllable", 'k', 0, LangKO, "가", "", "", 0 },
|
||
{ "switch to Vietnamese", 'v', Mctrl, LangVI, "", "", "가", 0 },
|
||
{ "Vietnamese a again", 'a', 0, LangVI, "a", "a", "", 0 },
|
||
{ "Vietnamese tone again", 's', 0, LangVI, "as", "as", "", 0 },
|
||
{ "commit Vietnamese", 't', Mctrl, LangEN, "", "", "á", 0 },
|
||
};
|
||
char where[80];
|
||
Str com;
|
||
int eaten, i;
|
||
|
||
init();
|
||
for(i = 0; i < nelem(steps); i++){
|
||
if(steps[i].stale){
|
||
im.kouho[0] = mkstr("stale");
|
||
im.nkouho = 1;
|
||
im.sel = -1;
|
||
}
|
||
sclear(&com);
|
||
eaten = keystroke(steps[i].key, steps[i].mod, &com);
|
||
if(eaten != 1){
|
||
CT_ERRORF(t, "%s: want eaten 1, got %d",
|
||
steps[i].name, eaten);
|
||
break;
|
||
}
|
||
if(im.l == nil || im.l->lang != steps[i].lang){
|
||
CT_ERRORF(t, "%s: want language %d, got %d",
|
||
steps[i].name, steps[i].lang,
|
||
im.l == nil ? -1 : im.l->lang);
|
||
break;
|
||
}
|
||
snprint(where, sizeof where, "%s preedit", steps[i].name);
|
||
if(!checkstr(t, where, steps[i].pre, &im.pre))
|
||
break;
|
||
snprint(where, sizeof where, "%s raw", steps[i].name);
|
||
if(!checkstr(t, where, steps[i].raw, &im.raw))
|
||
break;
|
||
snprint(where, sizeof where, "%s commit", steps[i].name);
|
||
if(!checkstr(t, where, steps[i].commit, &com))
|
||
break;
|
||
if(im.nkouho != 0 || im.sel != -1){
|
||
CT_ERRORF(t, "%s: want candidates 0 and selection -1, got %d and %d",
|
||
steps[i].name, im.nkouho, im.sel);
|
||
break;
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "rk", &com)){
|
||
CT_CHECK(t, !keystroke('p', Mctrl, &com));
|
||
checkstr(t, "Korean Ctrl passthrough", "가", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
}
|
||
init();
|
||
im.l = getlang(LangVI);
|
||
sclear(&com);
|
||
if(typekeys(t, "as", &com)){
|
||
CT_CHECK(t, !keystroke('p', Mctrl, &com));
|
||
checkstr(t, "Vietnamese Ctrl passthrough", "á", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
}
|
||
}
|
||
|
||
typedef struct Searchfix Searchfix;
|
||
struct Searchfix
|
||
{
|
||
Im im;
|
||
Search search;
|
||
Lang *dictlang;
|
||
Trie *dict;
|
||
int clientpre;
|
||
Drawcmd lastdraw;
|
||
};
|
||
|
||
static int
|
||
draindraw(Drawcmd *last)
|
||
{
|
||
Drawcmd dc = {0};
|
||
int n;
|
||
|
||
n = 0;
|
||
while(channbrecv(drawc, &dc) > 0){
|
||
n++;
|
||
if(last != nil)
|
||
*last = dc;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
static void
|
||
setcandidates(int n)
|
||
{
|
||
char name[8];
|
||
int i;
|
||
|
||
clearkouho();
|
||
for(i = 0; i < n; i++){
|
||
snprint(name, sizeof name, "c%d", i+1);
|
||
im.kouho[i] = mkstr(name);
|
||
}
|
||
im.nkouho = n;
|
||
}
|
||
|
||
static void
|
||
candidatebegin(int n)
|
||
{
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
draindraw(nil);
|
||
setcandidates(n);
|
||
}
|
||
|
||
static int
|
||
checkcandidatepage(struct ct *t, char *where, int first, int n, int sel)
|
||
{
|
||
Drawcmd dc;
|
||
char want[8];
|
||
|
||
redraw();
|
||
if(!CT_CHECK(t, draindraw(&dc) > 0))
|
||
return 0;
|
||
if(!CT_EQ_INT(t, n, dc.nkouho) || !CT_EQ_INT(t, sel, dc.sel) ||
|
||
!CT_EQ_INT(t, first, dc.first) ||
|
||
!CT_EQ_INT(t, im.nkouho, dc.total))
|
||
return 0;
|
||
if(n == 0)
|
||
return 1;
|
||
snprint(want, sizeof want, "c%d", first+1);
|
||
if(!checkstr(t, where, want, &dc.kouho[0]))
|
||
return 0;
|
||
snprint(want, sizeof want, "c%d", first+n);
|
||
return checkstr(t, where, want, &dc.kouho[n-1]);
|
||
}
|
||
|
||
void
|
||
engine_candidate_page_metadata(struct ct *t)
|
||
{
|
||
static const struct {
|
||
int total;
|
||
int sel;
|
||
int first;
|
||
int shown;
|
||
int local;
|
||
} cases[] = {
|
||
{ 1, 0, 0, 1, 0 },
|
||
{ 9, 8, 0, 9, 8 },
|
||
{ 10, 8, 0, 9, 8 },
|
||
{ 10, 9, 9, 1, 0 },
|
||
{ 18, 8, 0, 9, 8 },
|
||
{ 18, 17, 9, 9, 8 },
|
||
{ 19, 0, 0, 9, 0 },
|
||
{ 32, 8, 0, 9, 8 },
|
||
{ 32, 17, 9, 9, 8 },
|
||
{ 32, 26, 18, 9, 8 },
|
||
{ 32, 31, 27, 5, 4 },
|
||
};
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
candidatebegin(cases[i].total);
|
||
im.sel = cases[i].sel;
|
||
checkcandidatepage(t, "candidate page metadata",
|
||
cases[i].first, cases[i].shown, cases[i].local);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_page_snapshots(struct ct *t)
|
||
{
|
||
Drawcmd dc;
|
||
|
||
/* Clearing a visible popup must publish an empty snapshot. */
|
||
candidatebegin(1);
|
||
redraw();
|
||
draindraw(nil);
|
||
setcandidates(0);
|
||
redraw();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
CT_EQ_INT(t, 0, dc.nkouho);
|
||
CT_EQ_INT(t, -1, dc.sel);
|
||
CT_EQ_INT(t, 0, dc.first);
|
||
CT_EQ_INT(t, 0, dc.total);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_page_movement(struct ct *t)
|
||
{
|
||
static const struct {
|
||
int n;
|
||
int sel;
|
||
Rune key;
|
||
int want;
|
||
} cases[] = {
|
||
{ 1, 0, Kup, 0 },
|
||
{ 9, 8, Kdown, 8 },
|
||
{ 10, 8, Kdown, 9 },
|
||
{ 10, 9, Kup, 8 },
|
||
{ 18, 8, Kpgdown, 17 },
|
||
{ 19, 17, Kdown, 18 },
|
||
{ 19, 18, Kup, 17 },
|
||
{ 32, 26, Kdown, 27 },
|
||
{ 32, 27, Kup, 26 },
|
||
{ 32, 0, Kpgup, 0 },
|
||
{ 32, 9, Kpgup, 0 },
|
||
{ 32, 22, Kpgdown, 31 },
|
||
{ 32, 31, Kpgup, 22 },
|
||
{ 32, 31, Kpgdown, 31 },
|
||
};
|
||
int first, i, n;
|
||
Str com;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
candidatebegin(cases[i].n);
|
||
im.sel = cases[i].sel;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(cases[i].key, 0, &com));
|
||
CT_EQ_INT(t, cases[i].want, im.sel);
|
||
first = cases[i].want / Maxdisp * Maxdisp;
|
||
n = cases[i].n - first;
|
||
if(n > Maxdisp)
|
||
n = Maxdisp;
|
||
checkcandidatepage(t, "moved candidate page", first, n,
|
||
cases[i].want-first);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_candidate_completion(struct ct *t)
|
||
{
|
||
Str com;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
im.pre = mkstr("reading");
|
||
setcandidates(1);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "unselected candidate commits reading", "reading", &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
im.pre = mkstr("reading");
|
||
setcandidates(2);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Ktab, 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Ktab, Mshift, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Tab steps through the candidates", "c2", &com);
|
||
|
||
candidatebegin(2);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "explicit candidate", "c2", &com);
|
||
|
||
candidatebegin(2);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "ordinary zero reading", "reading", &com);
|
||
|
||
candidatebegin(0);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Enter without candidates", "reading", &com);
|
||
|
||
/* A digit types on until Space engages the list. */
|
||
candidatebegin(3);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('2', 0, &com));
|
||
checkstr(t, "digit before the list is engaged", "reading", &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
candidatebegin(3);
|
||
im.pre = mkstr("reading");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_CHECK(t, keystroke('2', 0, &com));
|
||
checkstr(t, "digit after the list is engaged", "c2", &com);
|
||
|
||
/* A digit with no such row on the page is an ordinary key: it
|
||
* confirms the candidate paged to and passes on. */
|
||
candidatebegin(32);
|
||
im.pre = mkstr("reading");
|
||
im.sel = 27;
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('9', 0, &com));
|
||
checkstr(t, "unavailable short-page digit", "c28", &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
}
|
||
|
||
static void
|
||
setdict(Trie *dict, char *key, char *val)
|
||
{
|
||
trieput(dict, key, strlen(key), val, strlen(val));
|
||
}
|
||
|
||
static void
|
||
searchsave(Searchfix *f, int lang)
|
||
{
|
||
f->im = im;
|
||
f->search = search;
|
||
f->dictlang = getlang(lang);
|
||
f->dict = f->dictlang->dict;
|
||
f->clientpre = clientpre;
|
||
f->lastdraw = lastdraw;
|
||
draindraw(nil);
|
||
}
|
||
|
||
static void
|
||
emojibegin(Searchfix *f, int showpre)
|
||
{
|
||
searchsave(f, LangEMOJI);
|
||
f->dictlang->dict = trienew();
|
||
setdict(f->dictlang->dict, "a", "A B");
|
||
setdict(f->dictlang->dict, "ㅁ", "B C");
|
||
setdict(f->dictlang->dict, "alpha", "α");
|
||
setdict(f->dictlang->dict, "smil", "😀 😄");
|
||
setdict(f->dictlang->dict, "smile", "😀 😄");
|
||
setdict(f->dictlang->dict, "웃음", "😀 😄 😂");
|
||
setdict(f->dictlang->dict, "えがお", "😀 😄 😊");
|
||
setdict(f->dictlang->dict, "エガオ", "😀 😄 😊");
|
||
setdict(f->dictlang->dict, "heart", "❤️");
|
||
setdict(f->dictlang->dict, "coffee", "☕️");
|
||
setdict(f->dictlang->dict, "^", "¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁽");
|
||
setdict(f->dictlang->dict, "^0", "⁰");
|
||
setdict(f->dictlang->dict, "_", "₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₍");
|
||
setdict(f->dictlang->dict, "<", "← ≤ ♥ ≠");
|
||
setdict(f->dictlang->dict, "many",
|
||
"c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12");
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
clientpre = showpre ? 0 : 1;
|
||
}
|
||
|
||
static void
|
||
searchend(Searchfix *f)
|
||
{
|
||
Trie *dict;
|
||
|
||
dict = f->dictlang->dict;
|
||
f->dictlang->dict = f->dict;
|
||
trieclose(dict);
|
||
draindraw(nil);
|
||
im = f->im;
|
||
search = f->search;
|
||
clientpre = f->clientpre;
|
||
lastdraw = f->lastdraw;
|
||
}
|
||
|
||
void
|
||
engine_popup_preedit_capability(struct ct *t)
|
||
{
|
||
Drawcmd dc;
|
||
Keyres kres;
|
||
Lang *jp;
|
||
Trie *saved;
|
||
char client, inactive;
|
||
|
||
jp = getlang(LangJP);
|
||
saved = jp->dict;
|
||
jp->dict = trienew();
|
||
setdict(jp->dict, "か", "家");
|
||
init();
|
||
im.l = jp;
|
||
draindraw(nil);
|
||
ownerrequestcap(&client, 1, Keypress, 'k', 0);
|
||
ownerrequestcap(&client, 1, Keypress, 'a', 0);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
redraw();
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_EQ_INT(t, -1, dc.sel);
|
||
checkstr(t, "client-preedit candidate", "家", &dc.kouho[0]);
|
||
}
|
||
|
||
kres = ownerrequestcap(&client, 0, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
checkstr(t, "capability response preedit", "か", &kres.preedit);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
checkstr(t, "popup preedit", "か", &dc.pre);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_EQ_INT(t, -1, dc.sel);
|
||
checkstr(t, "popup candidate", "家", &dc.kouho[0]);
|
||
}
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
checkstr(t, "retained popup candidate", "家", &im.kouho[0]);
|
||
|
||
kres = ownerrequestcap(&client, 0, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
|
||
kres = ownerrequestcap(&inactive, 1, Keycap, 0, 0);
|
||
CT_CHECK(t, !kres.eaten);
|
||
CT_EQ_INT(t, 0, kres.preedit.n);
|
||
CT_EQ_PTR(t, &client, activeowner);
|
||
CT_EQ_INT(t, 0, clientpre);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
|
||
kres = ownerrequestcap(&client, 1, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
checkstr(t, "restored client preedit", "か", &kres.preedit);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0)){
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_EQ_INT(t, -1, dc.sel);
|
||
checkstr(t, "restored client candidate", "家", &dc.kouho[0]);
|
||
}
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
checkstr(t, "retained client candidate", "家", &im.kouho[0]);
|
||
kres = ownerrequestcap(&client, 1, Keycap, 0, 0);
|
||
CT_CHECK(t, kres.eaten);
|
||
CT_EQ_INT(t, 0, draindraw(nil));
|
||
|
||
ownerrequest(&client, Keyrelease, 0, 0);
|
||
draindraw(nil);
|
||
trieclose(jp->dict);
|
||
jp->dict = saved;
|
||
}
|
||
|
||
static void
|
||
hanjabegin(Searchfix *f, int lang)
|
||
{
|
||
searchsave(f, LangHANJA);
|
||
f->dictlang->dict = trienew();
|
||
setdict(f->dictlang->dict, "한", "漢 韓");
|
||
setdict(f->dictlang->dict, "가", "家");
|
||
init();
|
||
im.l = getlang(lang);
|
||
}
|
||
|
||
static int
|
||
typekeys(struct ct *t, char *keys, Str *com)
|
||
{
|
||
for(; *keys != '\0'; keys++)
|
||
if(!CT_CHECK(t, keystroke((uchar)*keys, 0, com)))
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
void
|
||
engine_japanese_readings(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *want;
|
||
} cases[] = {
|
||
{ "nya", "にゃ" },
|
||
{ "n'ya", "んや" },
|
||
{ "nnya", "んや" },
|
||
{ "nnnya", "んにゃ" },
|
||
{ "konnnichiha", "こんにちは" },
|
||
{ "wi", "うぃ" },
|
||
{ "vu", "ゔ" },
|
||
{ "xtu", "っ" },
|
||
{ "axtu", "あっ" },
|
||
{ "lla", "っぁ" },
|
||
{ "matcha", "まっちゃ" },
|
||
{ "zi", "じ" },
|
||
{ "[", "「" },
|
||
{ "kin'youbi", "きんようび" },
|
||
};
|
||
static const struct {
|
||
char *raw;
|
||
char *want;
|
||
} replay[] = {
|
||
{ "kitte", "きって" },
|
||
{ "matcha", "まっちゃ" },
|
||
{ "baggu", "ばっぐ" },
|
||
{ "kanj", "かんj" },
|
||
{ "n'ya", "んや" },
|
||
};
|
||
Str com, raw, shown;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!typekeys(t, cases[i].keys, &com))
|
||
continue;
|
||
checkstr(t, cases[i].keys, "", &com);
|
||
impre(&im, &shown);
|
||
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed reading", cases[i].want, &com);
|
||
}
|
||
for(i = 0; i < nelem(replay); i++){
|
||
raw = mkstr(replay[i].raw);
|
||
transstr(getlang(LangJP), nil, &raw, &shown);
|
||
checkstr(t, replay[i].raw, replay[i].want, &shown);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_japanese_candidates(struct ct *t)
|
||
{
|
||
Trie *saved;
|
||
Lang *jp;
|
||
Str com, shown;
|
||
|
||
jp = getlang(LangJP);
|
||
saved = jp->dict;
|
||
jp->dict = trienew();
|
||
setdict(jp->dict, "かんじ", "漢字 幹事");
|
||
init();
|
||
im.l = jp;
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
impre(&im, &shown);
|
||
checkstr(t, "complete Japanese reading", "かんじ", &shown);
|
||
if(!CT_EQ_INT(t, 2, im.nkouho))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
checkstr(t, "candidate one", "漢字", &im.kouho[0]);
|
||
checkstr(t, "candidate two", "幹事", &im.kouho[1]);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "selected Kanji", "幹事", &com);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
/* Space converts: it steps through the candidates and wraps;
|
||
* typing on takes the chosen one; Backspace goes back. */
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 2, im.sel);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(' ', Mshift, &com));
|
||
CT_EQ_INT(t, 2, im.sel);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_CHECK(t, keystroke('w', 0, &com));
|
||
CT_CHECK(t, keystroke('o', 0, &com));
|
||
checkstr(t, "typing on takes the candidate", "漢字", &com);
|
||
impre(&im, &shown);
|
||
checkstr(t, "reading after the candidate", "を", &shown);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "Space offers Katakana without candidates", "ヲ",
|
||
&im.kouho[0]);
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Katakana committed", "ヲ", &com);
|
||
CT_CHECK(t, !keystroke(' ', 0, &com));
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
if(CT_EQ_INT(t, 3, im.nkouho))
|
||
checkstr(t, "Katakana after the candidates", "カンジ",
|
||
&im.kouho[2]);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
/* SKK's okuri-ari entries convert verbs: stem plus okurigana. */
|
||
setdict(jp->dict, "かk", "書 描");
|
||
setdict(jp->dict, "かく", "確");
|
||
if(!typekeys(t, "kaku", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 3, im.nkouho)){
|
||
checkstr(t, "exact candidate first", "確", &im.kouho[0]);
|
||
checkstr(t, "okurigana put back", "書く", &im.kouho[1]);
|
||
checkstr(t, "second okuri candidate", "描く", &im.kouho[2]);
|
||
}
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
setdict(jp->dict, "おおk", "大");
|
||
if(!typekeys(t, "ookii", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "two-kana okurigana", "大きい", &im.kouho[0]);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
/* Shift on the okurigana asks for that split alone, first. */
|
||
sclear(&com);
|
||
if(!typekeys(t, "ka", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('K', Mshift, &com));
|
||
CT_CHECK(t, keystroke('u', 0, &com));
|
||
if(CT_EQ_INT(t, 3, im.nkouho)){
|
||
checkstr(t, "marked okurigana first", "書く", &im.kouho[0]);
|
||
checkstr(t, "then the second", "描く", &im.kouho[1]);
|
||
checkstr(t, "then the reading's own", "確", &im.kouho[2]);
|
||
}
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
setdict(jp->dict, "いt", "言 行");
|
||
setdict(jp->dict, "いっt", "言 行");
|
||
if(!typekeys(t, "itta", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 2, im.nkouho)){
|
||
checkstr(t, "sokuon written in kana", "言った", &im.kouho[0]);
|
||
checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]);
|
||
}
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
/* A romaji typo stays in the reading for Backspace to mend. */
|
||
if(!typekeys(t, "kanjr", &com))
|
||
goto cleanup;
|
||
impre(&im, &shown);
|
||
checkstr(t, "typo kept", "かんjr", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "typo half mended", "かんj", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
if(!typekeys(t, "ji", &com))
|
||
goto cleanup;
|
||
impre(&im, &shown);
|
||
checkstr(t, "typo mended", "かんじ", &shown);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "0 commits the reading", "かんじ", &com);
|
||
sclear(&com);
|
||
if(!typekeys(t, "wo", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "0 commits a reading without candidates", "を", &com);
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke(' ', 0, &com));
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
impre(&im, &shown);
|
||
checkstr(t, "Escape goes back to the reading", "かんじ", &shown);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
sclear(&com);
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke('s', Mctrl, &com));
|
||
checkstr(t, "language switch takes the candidate", "漢字", &com);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
im.l = jp;
|
||
/* Backspace looks a complete shorter reading up again. */
|
||
setdict(jp->dict, "かん", "缶");
|
||
if(!typekeys(t, "kanji", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "shorter reading candidate", "缶", &im.kouho[0]);
|
||
cleanup:
|
||
trieclose(jp->dict);
|
||
jp->dict = saved;
|
||
}
|
||
|
||
void
|
||
engine_japanese_backspace_and_boundaries(struct ct *t)
|
||
{
|
||
Str com, shown;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "kanji", &com)){
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "backspace deletes じ", "かん", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "backspace deletes ん", "か", &shown);
|
||
if(typekeys(t, "kyu", &com)){
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "backspace deletes ゅ", "かき", &shown);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_CHECK(t, !haspre(&im));
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "kanji", &com)){
|
||
CT_CHECK(t, keystroke('k', Mctrl, &com));
|
||
checkstr(t, "Hiragana mode boundary", "かんじ", &com);
|
||
CT_EQ_INT(t, LangJPK, im.l->lang);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
sclear(&com);
|
||
if(typekeys(t, "ka", &com)){
|
||
CT_CHECK(t, keystroke('n', Mctrl, &com));
|
||
checkstr(t, "Katakana mode boundary", "カ", &com);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "kanji", &com)){
|
||
CT_CHECK(t, !keystroke(L'日', 0, &com));
|
||
checkstr(t, "real input boundary", "かんじ", &com);
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "KA", &com)){
|
||
impre(&im, &shown);
|
||
checkstr(t, "uppercase romaji", "か", &shown);
|
||
}
|
||
}
|
||
|
||
void
|
||
engine_katakana_sequences(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *want;
|
||
} cases[] = {
|
||
{ "kitte", "キッテ" },
|
||
{ "katta", "カッタ" },
|
||
{ "gakkou", "ガッコウ" },
|
||
{ "matcha", "マッチャ" },
|
||
{ "baggu", "バッグ" },
|
||
{ "xu", "ゥ" },
|
||
{ "vu", "ヴ" },
|
||
};
|
||
Str com, shown;
|
||
int i;
|
||
|
||
for(i = 0; i < nelem(cases); i++){
|
||
init();
|
||
im.l = getlang(LangJPK);
|
||
sclear(&com);
|
||
if(!typekeys(t, cases[i].keys, &com))
|
||
continue;
|
||
checkstr(t, cases[i].keys, "", &com);
|
||
impre(&im, &shown);
|
||
checkstr(t, cases[i].keys, cases[i].want, &shown);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "committed Katakana", cases[i].want, &com);
|
||
}
|
||
CT_EQ_PTR(t, nil, getlang(LangJPK)->dictname);
|
||
}
|
||
|
||
void
|
||
engine_emoji_single_candidate(struct ct *t)
|
||
{
|
||
Drawcmd dc = {0};
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
if(CT_CHECK(t, draindraw(&dc) > 0))
|
||
checkstr(t, "empty search shows itself", "☺", &dc.pre);
|
||
if(typekeys(t, "alpha", &com)){
|
||
checkstr(t, "no automatic commit", "", &com);
|
||
checkstr(t, "raw English query", "alpha", &search.text);
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
checkstr(t, "single candidate", "α", &im.kouho[0]);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 1, dc.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "explicit candidate commit", "α", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
redraw();
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
CT_EQ_INT(t, 0, dc.pre.n);
|
||
CT_EQ_INT(t, 0, dc.nkouho);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_queries(struct ct *t)
|
||
{
|
||
Drawcmd dc = {0};
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 1);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
checkstr(t, "raw query wins", "a", &search.text);
|
||
CT_EQ_INT(t, 4, im.nkouho);
|
||
checkstr(t, "raw candidate", "A", &im.kouho[0]);
|
||
checkstr(t, "deduplicated candidate", "B", &im.kouho[1]);
|
||
checkstr(t, "raw prefix candidate", "α", &im.kouho[2]);
|
||
checkstr(t, "local candidate", "C", &im.kouho[3]);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
checkstr(t, "prefix popup query", "a", &dc.pre);
|
||
CT_EQ_INT(t, 4, dc.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
checkstr(t, "English alias", "smile", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "backspaced query", "smil", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke('e', 0, &com));
|
||
CT_CHECK(t, keystroke('2', 0, &com));
|
||
checkstr(t, "selected English alias", "😄", &com);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "SMILE", &com)){
|
||
checkstr(t, "folded English alias", "smile", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "dntdma", &com)){
|
||
checkstr(t, "localized Korean query", "웃음", &search.text);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
checkstr(t, "popup query", "웃음", &dc.pre);
|
||
CT_EQ_INT(t, 3, dc.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
checkstr(t, "cancel commit", "", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_japanese_and_multirune(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "egao", &com)){
|
||
checkstr(t, "localized Hiragana query", "えがお", &search.text);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangJPK);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "egao", &com)){
|
||
checkstr(t, "localized Katakana query", "エガオ", &search.text);
|
||
CT_EQ_INT(t, 3, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangEN);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "heart", &com)){
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "variation-selector emoji", "❤️", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "coffee", &com)){
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "multi-codepoint emoji", "☕️", &com);
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_digit_aliases(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *name;
|
||
Rune prefix;
|
||
Rune digit;
|
||
char *want;
|
||
} cases[] = {
|
||
{ "^1", '^', '1', "¹" },
|
||
{ "_2", '_', '2', "₂" },
|
||
{ "<3", '<', '3', "♥" },
|
||
};
|
||
Searchfix f;
|
||
Str com;
|
||
int i;
|
||
|
||
emojibegin(&f, 0);
|
||
for(i = 0; i < nelem(cases); i++){
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke(cases[i].prefix, Mshift, &com));
|
||
CT_CHECK(t, im.nkouho >= cases[i].digit - '0');
|
||
CT_CHECK(t, keystroke(cases[i].digit, 0, &com));
|
||
checkstr(t, cases[i].name, cases[i].want, &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('^', Mshift, &com));
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "zero query", "^0", &search.text);
|
||
checkstr(t, "zero does not commit", "", &com);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "zero alias", "⁰", &com);
|
||
|
||
sclear(&com);
|
||
im.l = getlang(LangEN);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_CHECK(t, keystroke('9', 0, &com));
|
||
checkstr(t, "missing number query", "smile9", &search.text);
|
||
checkstr(t, "missing number commit", "", &com);
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_navigation(struct ct *t)
|
||
{
|
||
Drawcmd dc = {0};
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "many", &com)){
|
||
CT_EQ_INT(t, 12, im.nkouho);
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kpgdown, 0, &com));
|
||
CT_EQ_INT(t, Maxdisp, im.sel);
|
||
CT_CHECK(t, draindraw(&dc) > 0);
|
||
checkstr(t, "second-page first row", "c10", &dc.kouho[0]);
|
||
checkstr(t, "second-page last row", "c12", &dc.kouho[2]);
|
||
CT_EQ_INT(t, 3, dc.nkouho);
|
||
CT_EQ_INT(t, 0, dc.sel);
|
||
CT_CHECK(t, keystroke('1', 0, &com));
|
||
checkstr(t, "second-page row one", "c10", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Ktab, Mshift, &com));
|
||
CT_EQ_INT(t, 1, im.sel);
|
||
CT_CHECK(t, keystroke(Ktab, 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_search_candidate_keys(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
emojibegin(&f, 0);
|
||
im.l = getlang(LangEN);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "search default Enter", "😀", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "missing", &com)){
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, -1, im.sel);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "search query Enter", "missing", &com);
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('^', Mshift, &com));
|
||
CT_CHECK(t, keystroke('0', 0, &com));
|
||
checkstr(t, "search zero query", "^0", &search.text);
|
||
checkstr(t, "search zero commit", "", &com);
|
||
CT_CHECK(t, search.lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "many", &com)){
|
||
im.sel = Maxdisp;
|
||
CT_CHECK(t, keystroke('1', Mshift, &com));
|
||
checkstr(t, "modified search digit commit", "", &com);
|
||
checkstr(t, "modified search digit query", "many1", &search.text);
|
||
CT_CHECK(t, search.lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_preedit_languages(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com, shown;
|
||
|
||
emojibegin(&f, 0);
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(typekeys(t, "ka", &com)){
|
||
CT_CHECK(t, !keystroke(0, 0, &com));
|
||
impre(&im, &shown);
|
||
checkstr(t, "modifier preserves Japanese", "か", &shown);
|
||
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||
checkstr(t, "committed Japanese preedit", "か", &com);
|
||
CT_CHECK(t, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, !keystroke('E', Mctrl|Malt, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||
CT_CHECK(t, !keystroke('E', Mctrl|Msuper, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||
CT_CHECK(t, !keystroke('V', Mctrl|Mshift, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke('E', Mctrl, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
CT_CHECK(t, !keystroke(0, 0, &com));
|
||
CT_CHECK(t, search.lang);
|
||
checkstr(t, "modifier preserves search", "a", &search.text);
|
||
CT_CHECK(t, !keystroke('P', Mctrl|Mshift, &com));
|
||
CT_CHECK(t, !search.lang);
|
||
sclear(&com);
|
||
if(typekeys(t, "na", &com)){
|
||
impre(&im, &shown);
|
||
checkstr(t, "Japanese resumed", "な", &shown);
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "rk", &com)){
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
checkstr(t, "committed Korean preedit", "가", &com);
|
||
if(typekeys(t, "smile", &com)){
|
||
CT_CHECK(t, keystroke('1', 0, &com));
|
||
checkstr(t, "Korean selection", "가😀", &com);
|
||
sclear(&com);
|
||
if(typekeys(t, "rk", &com))
|
||
checkstr(t, "Korean resumed", "가", &im.pre);
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangVI);
|
||
sclear(&com);
|
||
if(typekeys(t, "as", &com)){
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
checkstr(t, "committed Telex preedit", "á", &com);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_PTR(t, getlang(LangVI), im.l);
|
||
sclear(&com);
|
||
if(typekeys(t, "as", &com)){
|
||
impre(&im, &shown);
|
||
checkstr(t, "Telex resumed", "á", &shown);
|
||
checkstr(t, "Telex raw resumed", "as", &im.raw);
|
||
}
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_emoji_start_and_unknown(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
char want[Maxrunes+1];
|
||
int i;
|
||
|
||
emojibegin(&f, 0);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xyz", &com)){
|
||
/* Keys that match nothing show what they type in the language. */
|
||
CT_CHECK(t, !keystroke(Kspec|0x51, 0, &com));
|
||
checkstr(t, "special commits shown query", "툨", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
im.l = getlang(LangEN);
|
||
|
||
/* A language switch commits the query like any other pending text. */
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xy", &com)){
|
||
CT_CHECK(t, keystroke('t', Mctrl, &com));
|
||
checkstr(t, "language switch commits query", "xy", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
}
|
||
|
||
/* So do the search keys, ending or changing the search. */
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xy", &com)){
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
checkstr(t, "search key commits query", "xy", &com);
|
||
CT_CHECK(t, !search.lang);
|
||
}
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
if(typekeys(t, "xy", &com)){
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
checkstr(t, "other search commits query", "xy", &com);
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
}
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('e', Mctrl, &com));
|
||
for(i = 0; i < Maxrunes; i++)
|
||
CT_CHECK(t, keystroke('q', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes, search.raw.n);
|
||
CT_CHECK(t, search.lang);
|
||
CT_CHECK(t, !keystroke('z', 0, &com));
|
||
memset(want, 'q', Maxrunes);
|
||
want[Maxrunes] = '\0';
|
||
checkstr(t, "bounded query commit", want, &com);
|
||
CT_CHECK(t, !search.lang);
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_search(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
checkstr(t, "Hanja reading", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kdown, 0, &com));
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "navigated Hanja", "韓", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_unknown_and_cancel(struct ct *t)
|
||
{
|
||
static const struct {
|
||
char *keys;
|
||
char *text;
|
||
} empty[] = {
|
||
{ "rmf", "글" },
|
||
{ "r", "ㄱ" },
|
||
{ "gksrmf", "한글" },
|
||
};
|
||
Searchfix f;
|
||
Str com;
|
||
int i;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
for(i = 0; i < nelem(empty); i++){
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)) ||
|
||
!typekeys(t, empty[i].keys, &com))
|
||
goto cleanup;
|
||
checkstr(t, "Hanja reading without candidates",
|
||
empty[i].text, &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Hanja commit without candidates",
|
||
empty[i].text, &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
}
|
||
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
checkstr(t, "cancelled Hanja", "", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_korean_keys(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('r', Mshift, &com));
|
||
CT_CHECK(t, keystroke('k', 0, &com));
|
||
checkstr(t, "shifted Hanja reading", "까", &search.text);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke('R', 0, &com));
|
||
CT_CHECK(t, keystroke('k', 0, &com));
|
||
checkstr(t, "Caps Lock Hanja reading", "가", &search.text);
|
||
CT_EQ_INT(t, 1, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_backspace(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangKO);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
checkstr(t, "Hanja before backspace", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Hanja final backspace", "하", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "Hanja vowel backspace", "ㅎ", &search.text);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "empty Hanja search", "", &search.text);
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
|
||
/* Ctrl+H converts the pending syllable: it seeds the query. */
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
checkstr(t, "nothing committed before Hanja", "", &com);
|
||
checkstr(t, "pending syllable is the query", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
checkstr(t, "seed backspaced", "", &search.text);
|
||
CT_EQ_INT(t, LangHANJA, search.lang);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
/* Escape gives the seed back; keys typed compose from it. */
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
checkstr(t, "seed pending again", "한", &im.pre);
|
||
checkstr(t, "nothing committed", "", &com);
|
||
CT_CHECK(t, keystroke(Kback, 0, &com));
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
checkstr(t, "seed 하", "하", &search.text);
|
||
CT_CHECK(t, keystroke('s', 0, &com));
|
||
checkstr(t, "key composes with the seed", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
checkstr(t, "seed alone comes back", "하", &im.pre);
|
||
CT_CHECK(t, !keystroke(Kesc, 0, &com));
|
||
checkstr(t, "Korean Escape commits", "하", &com);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_hanja_input_languages(struct ct *t)
|
||
{
|
||
Searchfix f;
|
||
Str com;
|
||
|
||
hanjabegin(&f, LangEN);
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "gks", &com))
|
||
goto cleanup;
|
||
checkstr(t, "English Hanja input", "gks", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "English Hanja commit", "gks", &com);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
if(!CT_CHECK(t, keystroke('h', Mctrl, &com)))
|
||
goto cleanup;
|
||
if(!typekeys(t, "ka", &com))
|
||
goto cleanup;
|
||
checkstr(t, "Japanese Hanja input", "か", &search.text);
|
||
CT_EQ_INT(t, 0, im.nkouho);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "Japanese Hanja commit", "か", &com);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
|
||
/* The keyboard's own keys: 한자 seeds like Ctrl+H, 한/영 toggles. */
|
||
init();
|
||
im.l = getlang(LangKO);
|
||
sclear(&com);
|
||
if(typekeys(t, "gks", &com)){
|
||
CT_CHECK(t, keystroke(Khanja, 0, &com));
|
||
checkstr(t, "Hanja key seed", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Khangul, 0, &com));
|
||
checkstr(t, "Hangul key commits the query", "한", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Khangul, 0, &com));
|
||
CT_EQ_INT(t, LangKO, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kzenkaku, 0, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kkana, 0, &com));
|
||
CT_EQ_INT(t, LangJPK, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kkana, 0, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
CT_CHECK(t, keystroke(Kzenkaku, 0, &com));
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
CT_CHECK(t, keystroke(Khenkan, 0, &com));
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
sclear(&com);
|
||
if(typekeys(t, "ka", &com)){
|
||
CT_CHECK(t, keystroke(Khenkan, 0, &com));
|
||
CT_EQ_INT(t, 0, im.sel);
|
||
CT_CHECK(t, keystroke(Kmuhenkan, 0, &com));
|
||
checkstr(t, "無変換 commits", "カ", &com);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
}
|
||
}
|
||
|
||
init();
|
||
im.l = getlang(LangEN);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
CT_CHECK(t, keystroke(L'한', 0, &com));
|
||
checkstr(t, "direct Hanja input", "한", &search.text);
|
||
CT_EQ_INT(t, 2, im.nkouho);
|
||
CT_CHECK(t, keystroke(Kret, 0, &com));
|
||
checkstr(t, "direct Hanja selection", "漢", &com);
|
||
CT_EQ_INT(t, LangEN, im.l->lang);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('h', Mctrl, &com));
|
||
if(!typekeys(t, "ka", &com))
|
||
goto cleanup;
|
||
CT_CHECK(t, !keystroke('e', Mctrl|Malt, &com));
|
||
checkstr(t, "modified search boundary", "か", &com);
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke('h', Mctrl|Msuper, &com));
|
||
CT_EQ_INT(t, 0, search.lang);
|
||
CT_EQ_INT(t, LangJP, im.l->lang);
|
||
cleanup:
|
||
searchend(&f);
|
||
}
|
||
|
||
static u32int
|
||
stressrand(u32int *state)
|
||
{
|
||
*state = *state * 1664525U + 1013904223U;
|
||
return *state;
|
||
}
|
||
|
||
static int
|
||
stressstr(Str *s)
|
||
{
|
||
return s->n >= 0 && s->n <= Maxrunes;
|
||
}
|
||
|
||
void
|
||
engine_randomized_stress(struct ct *t)
|
||
{
|
||
static Rune text[] = {
|
||
'a', 'e', 'g', 'i', 'j', 'k', 'm', 'n', 'o', 'r', 's', 't',
|
||
'u', 'w', 'y', '1', '2', '9', '\'', ' ', L'日',
|
||
};
|
||
static Rune modes[] = { 't', 'n', 'k', 's', 'v' };
|
||
Searchfix f;
|
||
Keyres res;
|
||
Caret pos;
|
||
u32int rnd, mod;
|
||
char contexts[3];
|
||
void *owner, *owners[3];
|
||
int i, j, op;
|
||
|
||
emojibegin(&f, 0);
|
||
owners[0] = &contexts[0];
|
||
owners[1] = &contexts[1];
|
||
owners[2] = &contexts[2];
|
||
rnd = 0x5eed1234U;
|
||
for(i = 0; i < 16000; i++){
|
||
rnd = stressrand(&rnd);
|
||
owner = owners[(rnd >> 8) % nelem(owners)];
|
||
op = rnd % 28;
|
||
mod = (rnd >> 16) & Mmask;
|
||
memset(&pos, 0, sizeof pos);
|
||
pos.valid = (rnd >> 25) & 1;
|
||
pos.x = (rnd >> 3) & 0x3ff;
|
||
pos.y = (rnd >> 13) & 0x3ff;
|
||
pos.h = 1 + ((rnd >> 18) & 0x1f);
|
||
if(op < 13)
|
||
res = ownerrequestat(owner, Keypress,
|
||
text[(rnd >> 5) % nelem(text)], mod, &pos);
|
||
else if(op == 13)
|
||
res = ownerrequestat(owner, Keypress, Kback, 0, &pos);
|
||
else if(op == 14)
|
||
res = ownerrequestat(owner, Keypress, Kret, 0, &pos);
|
||
else if(op == 15)
|
||
res = ownerrequestat(owner, Keypress, Kesc, 0, &pos);
|
||
else if(op == 16)
|
||
res = ownerrequestat(owner, Keypress, Kdown, 0, &pos);
|
||
else if(op == 17)
|
||
res = ownerrequestat(owner, Keypress, Kup, 0, &pos);
|
||
else if(op == 18)
|
||
res = ownerrequestat(owner, Keypress, Ktab,
|
||
(rnd & 0x1000) ? Mshift : 0, &pos);
|
||
else if(op == 19)
|
||
res = ownerrequestat(owner, Keypress,
|
||
modes[(rnd >> 12) % nelem(modes)], Mctrl, &pos);
|
||
else if(op == 20)
|
||
res = ownerrequestat(owner, Keypress, 'e', Mctrl, &pos);
|
||
else if(op == 21)
|
||
res = ownerrequest(owner, Keyreset, 0, 0);
|
||
else if(op == 22)
|
||
res = ownerrequest(owner, Keyrelease, 0, 0);
|
||
else if(op == 23)
|
||
res = ownerrequestat(owner, Keycaret, 0, 0, &pos);
|
||
else if(op == 24)
|
||
res = ownerrequest(owner, Keypress,
|
||
0, 0);
|
||
else if(op == 25)
|
||
res = ownerrequestat(owner, Keypress, 'p', Mctrl, &pos);
|
||
else if(op == 26)
|
||
res = ownerrequestat(owner, Keypress, 0xf008, 0, &pos);
|
||
else
|
||
res = ownerrequestat(owner, Keypress,
|
||
'1' + ((rnd >> 11) % 9), 0, &pos);
|
||
|
||
if(!stressstr(&res.commit) || !stressstr(&res.preedit) ||
|
||
!stressstr(&im.pre) || !stressstr(&im.raw) ||
|
||
!stressstr(&search.raw) || !stressstr(&search.text) ||
|
||
im.l == nil || im.nkouho < 0 || im.nkouho > Maxkouho ||
|
||
im.sel < -1 || im.sel >= Maxkouho ||
|
||
(im.nkouho > 0 && im.sel >= im.nkouho)){
|
||
CT_ERRORF(t, "invalid engine state after stress operation %d", i);
|
||
break;
|
||
}
|
||
for(j = 0; j < im.nkouho; j++)
|
||
if(!stressstr(&im.kouho[j])){
|
||
CT_ERRORF(t, "invalid candidate after stress operation %d", i);
|
||
i = 16000;
|
||
break;
|
||
}
|
||
if(activeowner != nil && activeowner != owners[0] &&
|
||
activeowner != owners[1] && activeowner != owners[2]){
|
||
CT_ERRORF(t, "unknown active owner after stress operation %d", i);
|
||
break;
|
||
}
|
||
}
|
||
searchend(&f);
|
||
}
|
||
|
||
void
|
||
engine_full_boundary_passthrough(struct ct *t)
|
||
{
|
||
Str com, shown;
|
||
int i;
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
for(i = 0; i < Maxrunes; i++)
|
||
im.pre.r[i] = L'あ';
|
||
im.pre.n = Maxrunes;
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke(L'日', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes, com.n);
|
||
for(i = 0; i < Maxrunes; i++)
|
||
CT_EQ_UINT(t, L'あ', com.r[i]);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
CT_EQ_INT(t, 0, im.raw.n);
|
||
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
for(i = 0; i < Maxrunes-1; i++)
|
||
im.pre.r[i] = L'あ';
|
||
im.pre.n = Maxrunes-1;
|
||
sclear(&com);
|
||
CT_CHECK(t, !keystroke(L'日', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes-1, com.n);
|
||
CT_EQ_INT(t, 0, im.pre.n);
|
||
|
||
/* Typing on at the limit commits the kana and keeps the syllable. */
|
||
init();
|
||
im.l = getlang(LangJP);
|
||
for(i = 0; i < Maxrunes-1; i++)
|
||
im.pre.r[i] = L'あ';
|
||
im.pre.n = Maxrunes-1;
|
||
im.raw = mkstr("k");
|
||
sclear(&com);
|
||
CT_CHECK(t, keystroke('a', 0, &com));
|
||
CT_EQ_INT(t, Maxrunes-1, com.n);
|
||
impre(&im, &shown);
|
||
checkstr(t, "syllable after the flush", "か", &shown);
|
||
}
|