okuriletter() puts じ in the z row, so dictqokuri can never build a key ending in j. map/kanji.dict has 17 of them, and 8 have no z twin at all: before after shinjiru 死んじる 信じる 死んじる tojiru (nothing) 綴じる 閉じる tsuujiru (nothing) 通じる shoujiru (nothing) 生じる toujiru 問うじる 投じる 問うじる koujiru 乞うじる 請うじる 高じる 乞うじる 請うじる gozonji 御存知 御存知 御存じ majiru 混じる 先じる 交じる 混じる 信じる, 閉じる, 通じる, 生じる, 投じる, 交じる: the dictionary holds every one and the engine could reach none. What it offered instead was the next split down -- しんじる falls back to しn, so 死んじる is what the popup shows for 信じる, and it is the only candidate. The letter is not ours to choose; it is the one the SKK dictionary was keyed by, which is the one the user typed. Real SKK carries both spellings where both are typed, and here that is 9 of the 17 -- えんj/えんz, かんj/かんz and their kin, identical values on both. Those 8 z rows go unreachable and lose nothing, because their j twin says the same word. ま is the one pair that differs, まj=交 混 against まz=混 先, and both stay reachable: じ takes the j road and ぜ/ず keep the z one. mazeru 混ぜる 先ぜる unchanged kanarazu 必ず ... unchanged mizu 水 見ず unchanged dekizu 出来ず unchanged The z row keeps ざずぜぞ, so nothing that is not じ moves. What this does not cover, deliberately: ち. kanji.dict has 7 keys ending in c, from users who typed "chi", and okuriletter puts ち in the t row. Giving c its own row would have to take ち out of t, and t is where the larger entry lives -- おt is 落 折 負 追 against おc's 落 alone -- so the c rows stay unreachable and lose nothing. づ needed no such choice: it is in the d row, もとd is 基, and もとz is its duplicate.
2263 lines
58 KiB
C
2263 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 },
|
||
{ 32, -1, Kdown, 0 },
|
||
{ 32, -1, Kup, 0 },
|
||
{ 32, -1, Kpgdown, 9 },
|
||
{ 32, -1, Kpgup, 0 },
|
||
};
|
||
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));
|
||
/* じ is keyed j, as SKK types it; ざずぜぞ keep z. */
|
||
setdict(jp->dict, "しんj", "信");
|
||
setdict(jp->dict, "まz", "混");
|
||
if(!typekeys(t, "shinjiru", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "じ okurigana", "信じる", &im.kouho[0]);
|
||
CT_CHECK(t, keystroke(Kesc, 0, &com));
|
||
if(!typekeys(t, "mazeru", &com))
|
||
goto cleanup;
|
||
if(CT_EQ_INT(t, 1, im.nkouho))
|
||
checkstr(t, "ぜ okurigana", "混ぜる", &im.kouho[0]);
|
||
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);
|
||
}
|