fix(engine): make composition transitions deterministic

This commit is contained in:
2026-08-14 22:13:16 +09:00
parent f2fb71cd89
commit 5dc2530951
2 changed files with 199 additions and 82 deletions

187
strans.c
View File

@@ -7,6 +7,7 @@ static void *activeowner;
static int activecap; static int activecap;
static Caret caret; static Caret caret;
static u32int dictseq; static u32int dictseq;
static int candidatechosen;
static void dictqmap(Im*); static void dictqmap(Im*);
static void dictqjp(Im*); static void dictqjp(Im*);
static void backjp(Im*); static void backjp(Im*);
@@ -38,6 +39,7 @@ clearkouho(void)
dictseq++; dictseq++;
im.nkouho = 0; im.nkouho = 0;
im.sel = -1; im.sel = -1;
candidatechosen = 0;
} }
static void static void
@@ -83,6 +85,7 @@ movekouho(int delta)
im.sel = 0; im.sel = 0;
if(im.sel >= im.nkouho) if(im.sel >= im.nkouho)
im.sel = im.nkouho - 1; im.sel = im.nkouho - 1;
candidatechosen = 1;
} }
static int static int
@@ -154,36 +157,43 @@ setkouho(Dictres *res)
} }
static void static void
show(void) snapshot(Drawcmd *dc)
{ {
static Drawcmd old;
Drawcmd dc;
Str pre; Str pre;
int i, first, n; int i, first, n;
sclear(&dc.pre); memset(dc, 0, sizeof *dc);
if(search.lang) if(search.lang)
pre = search.text; pre = search.text;
else else
impre(&pre); impre(&pre);
if(!(activecap & Cclientpreedit)){ if(!(activecap & Cclientpreedit)){
if(search.lang || isjp(&im) || im.l->map == nil || if(search.lang || isjp(&im) || im.l->map == nil ||
!mapget(im.l->map, &pre, &dc.pre)) !mapget(im.l->map, &pre, &dc->pre))
dc.pre = pre; dc->pre = pre;
} }
first = pagefirst(); first = pagefirst();
n = im.nkouho - first; n = im.nkouho - first;
if(n > Maxdisp) n = Maxdisp; if(n > Maxdisp) n = Maxdisp;
dc.nkouho = n; dc->nkouho = n;
dc.sel = im.sel >= 0 ? im.sel - first : -1; dc->sel = im.sel >= 0 ? im.sel - first : -1;
dc.first = first; dc->first = first;
dc.total = im.nkouho; dc->total = im.nkouho;
dc.caret = caret; dc->caret = caret;
for(i = 0; i < n; i++) for(i = 0; i < n; i++)
dc.kouho[i] = im.kouho[first + i]; dc->kouho[i] = im.kouho[first + i];
if(dc.pre.n == 0 && n == 0 && !visible) }
static void
redraw(void)
{
static Drawcmd old;
Drawcmd dc;
snapshot(&dc);
if(dc.pre.n == 0 && dc.nkouho == 0 && !visible)
return; return;
visible = dc.pre.n != 0 || n != 0; visible = dc.pre.n != 0 || dc.nkouho != 0;
/* imthread is the sole producer; keep only the newest pending draw. */ /* imthread is the sole producer; keep only the newest pending draw. */
while(channbrecv(drawc, &old) > 0) while(channbrecv(drawc, &old) > 0)
; ;
@@ -200,7 +210,6 @@ reset(void)
sclear(&search.text); sclear(&search.text);
memset(&caret, 0, sizeof caret); memset(&caret, 0, sizeof caret);
clearkouho(); clearkouho();
show();
} }
static void static void
@@ -227,7 +236,6 @@ dictqmap(Im *im)
Str dict; Str dict;
clearkouho(); clearkouho();
show();
if(im->l->map == nil) if(im->l->map == nil)
return; return;
if(!mapget(im->l->map, &im->pre, &dict)) if(!mapget(im->l->map, &im->pre, &dict))
@@ -241,7 +249,6 @@ dictqjp(Im *im)
Str reading; Str reading;
clearkouho(); clearkouho();
show();
if(im->l->dict == nil || !jpreading(im, &reading) || reading.n == 0) if(im->l->dict == nil || !jpreading(im, &reading) || reading.n == 0)
return; return;
dictsend(im, &reading); dictsend(im, &reading);
@@ -260,19 +267,25 @@ setlang(int c)
} }
static void static void
commit(Str *com) commitim(Im *p, Str *com)
{ {
Str val; Str val;
if(isjp(&im)){ if(isjp(p)){
jpreading(&im, &val); jpreading(p, &val);
sappend(com, &val); sappend(com, &val);
}else if(im.l->map != nil && mapget(im.l->map, &im.pre, &val)) }else if(p->l->map != nil && mapget(p->l->map, &p->pre, &val))
sappend(com, &val); sappend(com, &val);
else else
sappend(com, &im.pre); sappend(com, &p->pre);
sclear(&im.pre); sclear(&p->pre);
sclear(&im.raw); sclear(&p->raw);
}
static void
commit(Str *com)
{
commitim(&im, com);
} }
static void static void
@@ -302,43 +315,39 @@ keeptail(Str *s)
} }
static int static int
dotransjp(Rune c, Str *com) transjp(Im *p, Rune c, Str *com)
{ {
Str key, mapped; Str key, mapped;
if(c == '\'' && im.raw.n == 1 && im.raw.r[0] == 'n'){ if(c == '\'' && p->raw.n == 1 && p->raw.r[0] == 'n'){
if(mapget(im.l->map, &im.raw, &mapped)) if(mapget(p->l->map, &p->raw, &mapped))
sappend(&im.pre, &mapped); sappend(&p->pre, &mapped);
sclear(&im.raw); sclear(&p->raw);
dictqjp(&im);
return 1; return 1;
} }
key = im.raw; key = p->raw;
if(key.n < Maxrunes) if(key.n < Maxrunes)
sputr(&key, c); sputr(&key, c);
sclear(&mapped); sclear(&mapped);
if(maplookup(im.l->map, &key, &mapped)){ if(maplookup(p->l->map, &key, &mapped)){
im.raw = key; p->raw = key;
if(issmalltsu(&mapped)){ if(issmalltsu(&mapped)){
sappend(&im.pre, &mapped); sappend(&p->pre, &mapped);
keeptail(&im.raw); keeptail(&p->raw);
} }
dictqjp(&im);
return 1; return 1;
} }
if(im.raw.n != 0 && mapget(im.l->map, &im.raw, &mapped)){ if(p->raw.n != 0 && mapget(p->l->map, &p->raw, &mapped)){
sappend(&im.pre, &mapped); sappend(&p->pre, &mapped);
sclear(&im.raw); sclear(&p->raw);
sclear(&key); sclear(&key);
sputr(&key, c); sputr(&key, c);
if(maplookup(im.l->map, &key, &mapped)){ if(maplookup(p->l->map, &key, &mapped)){
im.raw = key; p->raw = key;
dictqjp(&im);
return 1; return 1;
} }
} }
commit(com); commitim(p, com);
clearkouho();
return 0; return 0;
} }
@@ -348,8 +357,14 @@ dotrans(Rune c, Str *com)
Emit e; Emit e;
Str mapped; Str mapped;
if(isjp(&im)) if(isjp(&im)){
return dotransjp(c, com); e.eat = transjp(&im, c, com);
if(e.eat)
dictqjp(&im);
else
clearkouho();
return e.eat;
}
e = im.l->trans(&im, c); e = im.l->trans(&im, c);
if(im.l->lang == LangVI){ if(im.l->lang == LangVI){
if(e.s.n > 0 || !e.eat) if(e.s.n > 0 || !e.eat)
@@ -470,7 +485,7 @@ foldascii(Str *s)
static void static void
transstr(Lang *l, Str *raw, Str *out) transstr(Lang *l, Str *raw, Str *out)
{ {
static Im q; Im q;
Emit e; Emit e;
Str mapped; Str mapped;
int i; int i;
@@ -482,6 +497,14 @@ transstr(Lang *l, Str *raw, Str *out)
} }
memset(&q, 0, sizeof q); memset(&q, 0, sizeof q);
q.l = l; q.l = l;
if(isjp(&q)){
for(i = 0; i < raw->n; i++)
if(!transjp(&q, raw->r[i], out) && out->n < Maxrunes)
sputr(out, raw->r[i]);
if(haspre(&q))
commitim(&q, out);
return;
}
for(i = 0; i < raw->n; i++){ for(i = 0; i < raw->n; i++){
e = l->trans(&q, raw->r[i]); e = l->trans(&q, raw->r[i]);
sappend(out, &e.s); sappend(out, &e.s);
@@ -546,7 +569,6 @@ emojiquery(void)
} }
search.text = rawhit || !localhit ? raw : local; search.text = rawhit || !localhit ? raw : local;
selectfirst(); selectfirst();
show();
} }
static void static void
@@ -562,7 +584,6 @@ hanjaquery(void)
setkouho(&res); setkouho(&res);
}else }else
clearkouho(); clearkouho();
show();
} }
static void static void
@@ -591,7 +612,6 @@ endsearch(void)
sclear(&search.raw); sclear(&search.raw);
sclear(&search.text); sclear(&search.text);
clearkouho(); clearkouho();
show();
} }
static void static void
@@ -605,7 +625,6 @@ startsearch(int lang, Str *com)
search.lang = lang; search.lang = lang;
sclear(&search.raw); sclear(&search.raw);
sclear(&search.text); sclear(&search.text);
show();
} }
static void static void
@@ -638,7 +657,6 @@ searchkey(u32int ks, u32int mod, Str *com)
if(im.nkouho == 0) if(im.nkouho == 0)
return 1; return 1;
movekouho(n); movekouho(n);
show();
return 1; return 1;
} }
n = numberedkouho(ks, mod); n = numberedkouho(ks, mod);
@@ -658,7 +676,6 @@ searchkey(u32int ks, u32int mod, Str *com)
im.sel = 0; im.sel = 0;
else else
im.sel++; im.sel++;
show();
return 1; return 1;
} }
if(ks == Kret){ if(ks == Kret){
@@ -712,7 +729,7 @@ searchkey(u32int ks, u32int mod, Str *com)
} }
static int static int
keystroke(u32int ks, u32int mod, Str *com) transition(u32int ks, u32int mod, Str *com)
{ {
int n; int n;
Rune c; Rune c;
@@ -726,7 +743,6 @@ keystroke(u32int ks, u32int mod, Str *com)
if(im.nkouho == 0) if(im.nkouho == 0)
return 0; return 0;
movekouho(n); movekouho(n);
show();
return 1; return 1;
} }
n = numberedkouho(ks, mod); n = numberedkouho(ks, mod);
@@ -736,7 +752,7 @@ keystroke(u32int ks, u32int mod, Str *com)
return 1; return 1;
} }
if(ks == Ktab || ks == Kret){ if(ks == Ktab || ks == Kret){
if(im.sel >= 0 && im.sel < im.nkouho){ if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho){
sappend(com, &im.kouho[im.sel]); sappend(com, &im.kouho[im.sel]);
reset(); reset();
return 1; return 1;
@@ -779,12 +795,14 @@ keystroke(u32int ks, u32int mod, Str *com)
return 1; return 1;
} }
if(getlang(c) != nil){ if(getlang(c) != nil){
if(isjp(&im) && haspre(&im)) if(haspre(&im))
commit(com); commit(com);
reset(); reset();
setlang(c); setlang(c);
return 1; return 1;
} }
if(haspre(&im))
commit(com);
reset(); reset();
return 0; return 0;
} }
@@ -814,10 +832,33 @@ keystroke(u32int ks, u32int mod, Str *com)
if(im.l->lang == LangKO) if(im.l->lang == LangKO)
ks = kokey(ks, mod); ks = kokey(ks, mod);
n = dotrans(ks, com); n = dotrans(ks, com);
show();
return n; return n;
} }
static int
keystrokeforce(u32int ks, u32int mod, Str *com, int force)
{
Im oldim;
Search oldsearch;
int eaten, ncom;
oldim = im;
oldsearch = search;
ncom = com->n;
eaten = transition(ks, mod, com);
if(force || eaten || com->n != ncom ||
memcmp(&oldim, &im, sizeof im) != 0 ||
memcmp(&oldsearch, &search, sizeof search) != 0)
redraw();
return eaten;
}
static int
keystroke(u32int ks, u32int mod, Str *com)
{
return keystrokeforce(ks, mod, com, 0);
}
static void static void
init(void) init(void)
{ {
@@ -829,6 +870,7 @@ init(void)
activeowner = nil; activeowner = nil;
activecap = 0; activecap = 0;
dictseq = 0; dictseq = 0;
candidatechosen = 0;
visible = 0; visible = 0;
} }
@@ -854,6 +896,7 @@ imhandlekey(Keyreq *kr)
{ {
Keyres res; Keyres res;
void *oldowner; void *oldowner;
int changed;
oldowner = activeowner; oldowner = activeowner;
sclear(&res.commit); sclear(&res.commit);
@@ -865,16 +908,19 @@ imhandlekey(Keyreq *kr)
reset(); reset();
activeowner = nil; activeowner = nil;
activecap = 0; activecap = 0;
redraw();
} }
break; break;
case Keyreset: case Keyreset:
if(kr->owner == activeowner) if(kr->owner == activeowner){
reset(); reset();
redraw();
}
break; break;
case Keycaret: case Keycaret:
if(kr->owner == activeowner){ if(kr->owner == activeowner){
caret = kr->caret; caret = kr->caret;
show(); redraw();
} }
break; break;
case Keycap: case Keycap:
@@ -882,24 +928,33 @@ imhandlekey(Keyreq *kr)
if(activeowner != nil && kr->owner == activeowner){ if(activeowner != nil && kr->owner == activeowner){
res.eaten = 1; res.eaten = 1;
if(setactivecap(kr->cap)) if(setactivecap(kr->cap))
show(); redraw();
} }
break; break;
case Keypress: case Keypress:
res.eaten = 0; res.eaten = 0;
changed = 0;
if(activeowner != nil && kr->owner == activeowner && if(activeowner != nil && kr->owner == activeowner &&
setactivecap(kr->cap)) setactivecap(kr->cap))
show(); changed = 1;
if(meaningful(kr)){ if(meaningful(kr)){
if(kr->owner != activeowner){ if(kr->owner != activeowner){
reset(); reset();
activeowner = kr->owner; activeowner = kr->owner;
activecap = kr->cap & Cclientpreedit; activecap = kr->cap & Cclientpreedit;
caret = kr->caret; caret = kr->caret;
}else if(kr->caret.valid) changed = 1;
}else if(kr->caret.valid){
caret = kr->caret; caret = kr->caret;
res.eaten = keystroke(kr->ks, kr->mod, &res.commit); changed = 1;
} }
if(changed)
res.eaten = keystrokeforce(kr->ks, kr->mod,
&res.commit, 1);
else
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
}else if(changed)
redraw();
break; break;
} }
if(activeowner != oldowner) if(activeowner != oldowner)
@@ -923,7 +978,7 @@ dictresult(Dictres *res)
scmp(&res->key, &pre) != 0) scmp(&res->key, &pre) != 0)
return; return;
setkouho(res); setkouho(res);
show(); redraw();
} }
void void

View File

@@ -8,6 +8,7 @@ int checkstr(struct ct*, char*, char*, Str*);
Str shownpre(Im*); Str shownpre(Im*);
static int typekeys(struct ct*, char*, Str*); static int typekeys(struct ct*, char*, Str*);
static int draindraw(Drawcmd*);
void void
testengineinit(int lang) testengineinit(int lang)
@@ -417,7 +418,8 @@ engine_commit_contract(struct ct *t)
im.kouho[0] = mkstr("c1"); im.kouho[0] = mkstr("c1");
im.kouho[1] = mkstr("c2"); im.kouho[1] = mkstr("c2");
im.nkouho = 2; im.nkouho = 2;
im.sel = 1; im.sel = 0;
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "selected candidate", "c2", &com); checkstr(t, "selected candidate", "c2", &com);
CT_EQ_INT(t, 0, im.pre.n); CT_EQ_INT(t, 0, im.pre.n);
@@ -484,6 +486,7 @@ engine_korean_modifiers_and_backspace(struct ct *t)
{ 'w', "", "" }, { 'o', "", "" }, { 'w', "", "" }, { 'o', "", "" },
{ 'p', "", "" }, { 'p', "", "" },
}; };
Drawcmd dc;
Str com; Str com;
int i; int i;
@@ -518,8 +521,11 @@ engine_korean_modifiers_and_backspace(struct ct *t)
sclear(&com); sclear(&com);
if(typekeys(t, "rkr", &com)){ if(typekeys(t, "rkr", &com)){
checkstr(t, "Korean engine preedit", "", &im.pre); checkstr(t, "Korean engine preedit", "", &im.pre);
draindraw(nil);
CT_CHECK(t, keystroke(Kback, 0, &com)); CT_CHECK(t, keystroke(Kback, 0, &com));
checkstr(t, "Korean final backspace", "", &im.pre); 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)); CT_CHECK(t, keystroke(Kback, 0, &com));
checkstr(t, "Korean vowel backspace", "", &im.pre); checkstr(t, "Korean vowel backspace", "", &im.pre);
CT_CHECK(t, keystroke(Kback, 0, &com)); CT_CHECK(t, keystroke(Kback, 0, &com));
@@ -578,10 +584,10 @@ engine_language_switch_state(struct ct *t)
{ "select Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 }, { "select Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 },
{ "Vietnamese a", 'a', 0, LangVI, "a", "a", "", 0 }, { "Vietnamese a", 'a', 0, LangVI, "a", "a", "", 0 },
{ "Vietnamese tone", 's', 0, LangVI, "as", "as", "", 0 }, { "Vietnamese tone", 's', 0, LangVI, "as", "as", "", 0 },
{ "switch to Korean", 's', Mctrl, LangKO, "", "", "", 1 }, { "switch to Korean", 's', Mctrl, LangKO, "", "", "á", 1 },
{ "Korean initial", 'r', 0, LangKO, "", "", "", 0 }, { "Korean initial", 'r', 0, LangKO, "", "", "", 0 },
{ "Korean syllable", 'k', 0, LangKO, "", "", "", 0 }, { "Korean syllable", 'k', 0, LangKO, "", "", "", 0 },
{ "switch to Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 }, { "switch to Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 },
{ "Vietnamese a again", 'a', 0, LangVI, "a", "a", "", 0 }, { "Vietnamese a again", 'a', 0, LangVI, "a", "a", "", 0 },
{ "Vietnamese tone again", 's', 0, LangVI, "as", "as", "", 0 }, { "Vietnamese tone again", 's', 0, LangVI, "as", "as", "", 0 },
{ "commit Vietnamese", Kret, 0, LangVI, "", "", "á", 0 }, { "commit Vietnamese", Kret, 0, LangVI, "", "", "á", 0 },
@@ -632,6 +638,24 @@ engine_language_switch_state(struct ct *t)
break; 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 = &testvi;
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);
}
vi->map = saved; vi->map = saved;
} }
@@ -644,6 +668,7 @@ struct Searchfix
Hmap *dict; Hmap *dict;
int activecap; int activecap;
int visible; int visible;
int candidatechosen;
}; };
static int static int
@@ -691,7 +716,7 @@ checkcandidatepage(struct ct *t, char *where, int first, int n, int sel)
Drawcmd dc; Drawcmd dc;
char want[8]; char want[8];
show(); redraw();
if(!CT_CHECK(t, draindraw(&dc) > 0)) if(!CT_CHECK(t, draindraw(&dc) > 0))
return 0; return 0;
if(!CT_EQ_INT(t, n, dc.nkouho) || !CT_EQ_INT(t, sel, dc.sel) || if(!CT_EQ_INT(t, n, dc.nkouho) || !CT_EQ_INT(t, sel, dc.sel) ||
@@ -732,10 +757,10 @@ engine_candidate_page_metadata(struct ct *t)
/* A clearing draw is the observable zero-candidate snapshot. */ /* A clearing draw is the observable zero-candidate snapshot. */
candidatebegin(1); candidatebegin(1);
show(); redraw();
draindraw(nil); draindraw(nil);
setcandidates(0); setcandidates(0);
show(); redraw();
if(CT_CHECK(t, draindraw(&dc) > 0)){ if(CT_CHECK(t, draindraw(&dc) > 0)){
CT_EQ_INT(t, 0, dc.nkouho); CT_EQ_INT(t, 0, dc.nkouho);
CT_EQ_INT(t, -1, dc.sel); CT_EQ_INT(t, -1, dc.sel);
@@ -761,7 +786,7 @@ engine_candidate_page_snapshots(struct ct *t)
candidatebegin(totals[i]); candidatebegin(totals[i]);
CT_EQ_INT(t, totals[i] == 0 ? -1 : 0, im.sel); CT_EQ_INT(t, totals[i] == 0 ? -1 : 0, im.sel);
if(totals[i] == 0){ if(totals[i] == 0){
show(); redraw();
CT_EQ_INT(t, 0, draindraw(nil)); CT_EQ_INT(t, 0, draindraw(nil));
continue; continue;
} }
@@ -815,17 +840,39 @@ engine_candidate_page_movement(struct ct *t)
void void
engine_candidate_completion(struct ct *t) engine_candidate_completion(struct ct *t)
{ {
static Rune keys[] = { Kret, Ktab };
Dictres res;
Str com; Str com;
int arrived, i;
for(i = 0; i < nelem(keys); i++)
for(arrived = 0; arrived < 2; arrived++){
init();
im.l = getlang(LangJP);
im.pre = mkstr("reading");
memset(&res, 0, sizeof res);
res.key = im.pre;
res.lang = LangJP;
res.seq = dictseq;
res.kouho[0] = mkstr("candidate");
res.nkouho = 1;
if(arrived)
dictresult(&res);
sclear(&com);
CT_CHECK(t, keystroke(keys[i], 0, &com));
if(!arrived)
dictresult(&res);
checkstr(t, "unselected candidate commits reading",
"reading", &com);
CT_EQ_INT(t, 0, im.nkouho);
}
candidatebegin(2); candidatebegin(2);
im.pre = mkstr("reading");
sclear(&com); sclear(&com);
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "default candidate", "c1", &com); checkstr(t, "explicit candidate", "c2", &com);
candidatebegin(2);
sclear(&com);
CT_CHECK(t, keystroke(Ktab, 0, &com));
checkstr(t, "ordinary Tab candidate", "c1", &com);
candidatebegin(2); candidatebegin(2);
im.pre = mkstr("reading"); im.pre = mkstr("reading");
@@ -866,6 +913,7 @@ searchsave(Searchfix *f, int lang)
f->dict = f->dictlang->dict; f->dict = f->dictlang->dict;
f->activecap = activecap; f->activecap = activecap;
f->visible = visible; f->visible = visible;
f->candidatechosen = candidatechosen;
draindraw(nil); draindraw(nil);
} }
@@ -908,6 +956,7 @@ searchend(Searchfix *f)
search = f->search; search = f->search;
activecap = f->activecap; activecap = f->activecap;
visible = f->visible; visible = f->visible;
candidatechosen = f->candidatechosen;
} }
void void
@@ -931,7 +980,7 @@ engine_popup_preedit_capability(struct ct *t)
res.nkouho = 1; res.nkouho = 1;
dictresult(&res); dictresult(&res);
im.sel = 0; im.sel = 0;
show(); redraw();
if(CT_CHECK(t, draindraw(&dc) > 0)){ if(CT_CHECK(t, draindraw(&dc) > 0)){
CT_EQ_INT(t, 0, dc.pre.n); CT_EQ_INT(t, 0, dc.pre.n);
CT_EQ_INT(t, 1, dc.nkouho); CT_EQ_INT(t, 1, dc.nkouho);
@@ -1018,7 +1067,15 @@ engine_japanese_readings(struct ct *t)
{ "nnya", "んにゃ" }, { "nnya", "んにゃ" },
{ "kin'youbi", "きんようび" }, { "kin'youbi", "きんようび" },
}; };
Str com, shown; static const struct {
char *raw;
char *want;
} replay[] = {
{ "kitte", "きって" },
{ "kanj", "かんj" },
{ "n'ya", "んや" },
};
Str com, raw, shown;
int i; int i;
for(i = 0; i < nelem(cases); i++){ for(i = 0; i < nelem(cases); i++){
@@ -1033,6 +1090,11 @@ engine_japanese_readings(struct ct *t)
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "committed reading", cases[i].want, &com); checkstr(t, "committed reading", cases[i].want, &com);
} }
for(i = 0; i < nelem(replay); i++){
raw = mkstr(replay[i].raw);
transstr(getlang(LangJP), &raw, &shown);
checkstr(t, replay[i].raw, replay[i].want, &shown);
}
} }
static int static int
@@ -1194,7 +1256,7 @@ engine_emoji_single_candidate(struct ct *t)
checkstr(t, "explicit candidate commit", "α", &com); checkstr(t, "explicit candidate commit", "α", &com);
CT_CHECK(t, !search.lang); CT_CHECK(t, !search.lang);
CT_EQ_INT(t, LangKO, im.l->lang); CT_EQ_INT(t, LangKO, im.l->lang);
show(); redraw();
CT_CHECK(t, draindraw(&dc) > 0); CT_CHECK(t, draindraw(&dc) > 0);
CT_EQ_INT(t, 0, dc.pre.n); CT_EQ_INT(t, 0, dc.pre.n);
CT_EQ_INT(t, 0, dc.nkouho); CT_EQ_INT(t, 0, dc.nkouho);