emoji: repair one-shot candidate selection

This commit is contained in:
2026-08-11 23:30:41 +09:00
parent 0fff17c8b8
commit 2ae87a3926
14 changed files with 473 additions and 54 deletions

View File

@@ -302,11 +302,19 @@ emojibegin(Emojifix *f, int showpre)
f->popup = popup;
f->visible = visible;
draindraw(nil);
l->dict = hmapalloc(8);
l->dict = hmapalloc(32);
setemoji(&l->dict, "a", "A B");
setemoji(&l->dict, "", "B C");
setemoji(&l->dict, "alpha", "α");
setemoji(&l->dict, "smil", "😀 😄");
setemoji(&l->dict, "smile", "😀 😄");
setemoji(&l->dict, "웃음", "😀 😄 😂");
setemoji(&l->dict, "^", "¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁽");
setemoji(&l->dict, "^0", "");
setemoji(&l->dict, "_", "₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₍");
setemoji(&l->dict, "<", "← ≤ ♥ ≠");
setemoji(&l->dict, "many",
"c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12");
init();
im.l = getlang(LangKO);
popup = showpre;
@@ -345,8 +353,10 @@ engine_emoji_single_candidate(struct ct *t)
Emojifix f;
Str com;
emojibegin(&f, 0);
emojibegin(&f, 1);
sclear(&com);
CT_CHECK(t, keystroke('p', Mctrl, &com));
CT_EQ_INT(t, 0, popup);
CT_CHECK(t, keystroke('e', Mctrl, &com));
CT_CHECK(t, search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
@@ -363,6 +373,10 @@ engine_emoji_single_candidate(struct ct *t)
checkstr(t, "explicit candidate commit", "α", &com);
CT_CHECK(t, !search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
show();
CT_CHECK(t, draindraw(&dc) > 0);
CT_EQ_INT(t, 0, dc.pre.n);
CT_EQ_INT(t, 0, dc.nkouho);
}
emojiend(&f);
}
@@ -376,6 +390,18 @@ engine_emoji_queries(struct ct *t)
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, 3, im.nkouho);
checkstr(t, "raw candidate", "A", &im.kouho[0]);
checkstr(t, "deduplicated candidate", "B", &im.kouho[1]);
checkstr(t, "local candidate", "C", &im.kouho[2]);
CT_CHECK(t, draindraw(&dc) > 0);
checkstr(t, "prefix popup query", "a", &dc.pre);
CT_EQ_INT(t, 3, 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);
@@ -384,11 +410,19 @@ engine_emoji_queries(struct ct *t)
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', Malt, &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)){
@@ -405,11 +439,187 @@ engine_emoji_queries(struct ct *t)
emojiend(&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', "" },
};
Emojifix 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.on);
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);
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.on);
CT_EQ_INT(t, 0, im.nkouho);
CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "missing number text", "smile9", &com);
}
emojiend(&f);
}
void
engine_emoji_navigation(struct ct *t)
{
Drawcmd dc = {0};
Emojifix f;
Str com;
int i;
emojibegin(&f, 0);
sclear(&com);
CT_CHECK(t, keystroke('e', Mctrl, &com));
if(typekeys(t, "many", &com)){
CT_EQ_INT(t, 12, im.nkouho);
for(i = 0; i < Maxdisp+1; i++)
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_EQ_INT(t, Maxdisp, im.sel);
CT_CHECK(t, draindraw(&dc) > 0);
checkstr(t, "scrolled first row", "c2", &dc.kouho[0]);
checkstr(t, "scrolled ninth row", "c10", &dc.kouho[8]);
CT_EQ_INT(t, 8, dc.sel);
CT_CHECK(t, keystroke('1', 0, &com));
checkstr(t, "scrolled row one", "c2", &com);
}
sclear(&com);
CT_CHECK(t, keystroke('e', Mctrl, &com));
if(typekeys(t, "many", &com)){
for(i = 0; i < 12; i++)
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_EQ_INT(t, 11, im.sel);
CT_CHECK(t, keystroke('9', 0, &com));
checkstr(t, "scrolled row nine", "c12", &com);
}
sclear(&com);
CT_CHECK(t, keystroke('e', Mctrl, &com));
if(typekeys(t, "smile", &com)){
CT_CHECK(t, keystroke(Ktab, 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(Kup, 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, "navigated candidate", "😄", &com);
}
emojiend(&f);
}
void
engine_emoji_preedit_languages(struct ct *t)
{
Emojifix f;
Str com, shown;
int p;
emojibegin(&f, 0);
init();
im.l = getlang(LangJP);
sclear(&com);
if(typekeys(t, "ka", &com)){
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
checkstr(t, "modifier preserves Japanese", "ka", &im.pre);
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
checkstr(t, "committed Japanese preedit", "", &com);
CT_CHECK(t, search.on);
CT_EQ_INT(t, LangJP, im.l->lang);
CT_CHECK(t, keystroke('a', 0, &com));
p = popup;
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
CT_CHECK(t, search.on);
checkstr(t, "modifier preserves search", "a", &search.text);
CT_CHECK(t, keystroke('P', Mctrl|Mshift, &com));
CT_EQ_INT(t, !p, popup);
CT_CHECK(t, search.on);
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
CT_CHECK(t, !search.on);
sclear(&com);
if(typekeys(t, "na", &com)){
shown = shownpre(&im);
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 = &testvi;
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.on);
CT_EQ_PTR(t, &testvi, im.l);
sclear(&com);
if(typekeys(t, "as", &com)){
shown = shownpre(&im);
checkstr(t, "Telex resumed", "á", &shown);
checkstr(t, "Telex raw resumed", "as", &im.raw);
}
}
emojiend(&f);
}
void
engine_emoji_start_and_unknown(struct ct *t)
{
Emojifix f;
Str com;
char want[Maxrunes+1];
int i;
emojibegin(&f, 0);
sclear(&com);
@@ -427,5 +637,67 @@ engine_emoji_start_and_unknown(struct ct *t)
CT_EQ_INT(t, LangKO, im.l->lang);
}
}
sclear(&com);
CT_CHECK(t, keystroke('e', Mctrl, &com));
if(typekeys(t, "xyz", &com)){
CT_CHECK(t, !keystroke(Kspec|0x51, 0, &com));
checkstr(t, "special commits shown query", "xyz", &com);
CT_CHECK(t, !search.on);
CT_EQ_INT(t, LangKO, im.l->lang);
}
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.on);
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.on);
emojiend(&f);
}
void
engine_emoji_dictionary_identity(struct ct *t)
{
Dictres res;
Emojifix f;
emojibegin(&f, 0);
im.pre = mkstr("same");
im.kouho[0] = mkstr("old");
im.nkouho = 1;
im.sel = 0;
memset(&res, 0, sizeof res);
res.key = im.pre;
res.kouho[0] = mkstr("wrong");
res.nkouho = 1;
res.lang = LangJP;
dictresult(&res);
CT_EQ_INT(t, 1, im.nkouho);
checkstr(t, "wrong language ignored", "old", &im.kouho[0]);
res.lang = LangKO;
res.key = mkstr("other");
dictresult(&res);
CT_EQ_INT(t, 1, im.nkouho);
checkstr(t, "wrong preedit ignored", "old", &im.kouho[0]);
res.key = im.pre;
search.on = 1;
dictresult(&res);
CT_EQ_INT(t, 1, im.nkouho);
checkstr(t, "search response ignored", "old", &im.kouho[0]);
search.on = 0;
res.kouho[0] = mkstr("right");
dictresult(&res);
CT_EQ_INT(t, 1, im.nkouho);
CT_EQ_INT(t, -1, im.sel);
checkstr(t, "matching response accepted", "right", &im.kouho[0]);
emojiend(&f);
}