engine: look dictionaries up synchronously

The dictionary thread ran in imthread's own proc, so a lookup could
only start once the engine blocked, and it was a trie probe anyway; the
emoji and Hanja searches already called dictlookup directly. The
request/result channels, sequence numbers, staleness checks and the
second draw per Japanese key are gone; dictqjp fills the candidates
in place. Emit.dict and Lang.dictq only ever triggered lookups for
Vietnamese, which has no dictionary. dictlookup(Lang*, key, out, max)
returns the count. The Hanja lookup no longer pre-checks for a single
syllable; a reading either has an entry or it does not.
This commit is contained in:
2026-08-16 16:02:09 +09:00
parent ebcec3af6b
commit abaea77248
10 changed files with 123 additions and 473 deletions

23
dat.h
View File

@@ -54,7 +54,6 @@ struct Emit
int eat; int eat;
Str s; Str s;
Str next; Str next;
Str dict;
}; };
typedef struct Tnode Tnode; typedef struct Tnode Tnode;
@@ -91,7 +90,6 @@ struct Lang
char *dictname; char *dictname;
Emit (*trans)(Im*, Rune); Emit (*trans)(Im*, Rune);
void (*back)(Im*); void (*back)(Im*);
void (*dictq)(Im*);
Trie *map; Trie *map;
Trie *dict; Trie *dict;
}; };
@@ -190,28 +188,7 @@ struct Keyreq
Channel *reply; Channel *reply;
}; };
typedef struct Dictreq Dictreq;
struct Dictreq
{
Str key;
Str pre;
int lang;
u32int seq;
};
typedef struct Dictres Dictres;
struct Dictres
{
Str key;
Str kouho[Maxkouho];
int nkouho;
int lang;
u32int seq;
};
extern Lang langs[]; extern Lang langs[];
extern int nlang; extern int nlang;
extern Channel *drawc; extern Channel *drawc;
extern Channel *keyc; extern Channel *keyc;
extern Channel *dictreqc;
extern Channel *dictresc;

50
dict.c
View File

@@ -1,52 +1,34 @@
#include "dat.h" #include "dat.h"
#include "fn.h" #include "fn.h"
/* Candidates are the space-separated words of the value; the reading itself /*
* is a candidate only for the emoji dictionary. */ * Fills out[] with up to max candidates for key: the space-separated words
void * of the entry, minus the key itself except in the emoji dictionary, where
dictlookup(Dictreq *req, Dictres *res) * a query may name its own answer.
*/
int
dictlookup(Lang *l, Str *key, Str *out, int max)
{ {
Lang *l;
char *p, *e, *sp; char *p, *e, *sp;
Str tmp; Str tmp;
int vlen; int n, vlen;
res->key = req->pre; if(l == nil || key->n == 0 ||
res->nkouho = 0; trielookup(l->dict, key, &p, &vlen) != TrieExact)
res->lang = req->lang; return 0;
res->seq = req->seq; n = 0;
l = getlang(req->lang);
if(req->key.n == 0 || l == nil ||
trielookup(l->dict, &req->key, &p, &vlen) != TrieExact)
return;
e = p + vlen; e = p + vlen;
while(res->nkouho < Maxkouho && p < e){ while(n < max && p < e){
while(p < e && *p == ' ') while(p < e && *p == ' ')
p++; p++;
sp = p; sp = p;
while(p < e && *p != ' ') while(p < e && *p != ' ')
p++; p++;
if(sinit(&tmp, sp, p - sp) && tmp.n > 0 && if(sinit(&tmp, sp, p - sp) && tmp.n > 0 &&
(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)) (l->lang == LangEMOJI || scmp(&tmp, key) != 0))
res->kouho[res->nkouho++] = tmp; out[n++] = tmp;
}
}
void
dictthread(void*)
{
Dictreq req;
static Dictres res;
threadsetname("dict");
for(;;){
if(chanrecv(dictreqc, &req) < 0)
break;
while(channbrecv(dictreqc, &req) > 0)
;
dictlookup(&req, &res);
chansend(dictresc, &res);
} }
return n;
} }
static Trie* static Trie*

3
fn.h
View File

@@ -20,8 +20,7 @@ int trielookup(Trie*, Str*, char**, int*);
Lang* getlang(int); Lang* getlang(int);
void langinit(char*); void langinit(char*);
void dictthread(void*); int dictlookup(Lang*, Str*, Str*, int);
void dictlookup(Dictreq*, Dictres*);
void drawthread(void*); void drawthread(void*);
void popuparea(Area*, int, Area*, int, int, Area*); void popuparea(Area*, int, Area*, int, int, Area*);

5
main.c
View File

@@ -3,8 +3,6 @@
Channel *drawc; Channel *drawc;
Channel *keyc; Channel *keyc;
Channel *dictreqc;
Channel *dictresc;
void void
usage(void) usage(void)
@@ -56,8 +54,6 @@ threadmain(int argc, char **argv)
drawc = chancreate(sizeof(Drawcmd), 4); drawc = chancreate(sizeof(Drawcmd), 4);
keyc = chancreate(sizeof(Keyreq), 0); keyc = chancreate(sizeof(Keyreq), 0);
dictreqc = chancreate(sizeof(Dictreq), 4);
dictresc = chancreate(sizeof(Dictres), 0);
langinit(argv[1]); langinit(argv[1]);
srvinit(); srvinit();
proccreate(drawthread, nil, 16384); proccreate(drawthread, nil, 16384);
@@ -66,6 +62,5 @@ threadmain(int argc, char **argv)
display = getenv("DISPLAY"); display = getenv("DISPLAY");
if(display != nil && display[0] != '\0') if(display != nil && display[0] != '\0')
proccreate(ximthread, nil, 32768); proccreate(ximthread, nil, 32768);
threadcreate(dictthread, nil, 16384);
imthread(nil); imthread(nil);
} }

153
strans.c
View File

@@ -6,10 +6,7 @@ static int visible = 0;
static void *activeowner; static void *activeowner;
static int activecap; static int activecap;
static Caret caret; static Caret caret;
static u32int dictseq;
static int candidatechosen; static int candidatechosen;
static void dictqmap(Im*);
static void dictqjp(Im*);
static void backjp(Im*); static void backjp(Im*);
static int maplookup(Trie*, Str*, Str*); static int maplookup(Trie*, Str*, Str*);
@@ -23,20 +20,19 @@ struct Search
static Search search; static Search search;
Lang langs[] = { Lang langs[] = {
{LangEN, nil, nil, nil, nil, nil, nil, nil}, {LangEN, nil, nil, nil, nil, nil, nil},
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil}, {LangJP, "hira", "kanji", transmap, backjp, nil, nil},
{LangJPK, "kata", nil, transmap, backjp, dictqjp, nil, nil}, {LangJPK, "kata", nil, transmap, backjp, nil, nil},
{LangKO, nil, nil, transko, backko, nil, nil, nil}, {LangKO, nil, nil, transko, backko, nil, nil},
{LangHANJA, nil, "hanja", nil, nil, nil, nil, nil}, {LangHANJA, nil, "hanja", nil, nil, nil, nil},
{LangEMOJI, nil, "emoji", nil, nil, nil, nil, nil}, {LangEMOJI, nil, "emoji", nil, nil, nil, nil},
{LangVI, "telex", nil, transvi, backvi, dictqmap, nil, nil}, {LangVI, "telex", nil, transvi, backvi, nil, nil},
}; };
int nlang = nelem(langs); int nlang = nelem(langs);
static void static void
clearkouho(void) clearkouho(void)
{ {
dictseq++;
im.nkouho = 0; im.nkouho = 0;
im.sel = -1; im.sel = -1;
candidatechosen = 0; candidatechosen = 0;
@@ -144,19 +140,6 @@ impre(Str *s)
*s = im.pre; *s = im.pre;
} }
static void
setkouho(Dictres *res)
{
int i;
clearkouho();
for(i = 0; i < res->nkouho; i++){
im.kouho[i] = res->kouho[i];
im.nkouho++;
}
selectfirst();
}
static void static void
snapshot(Drawcmd *dc) snapshot(Drawcmd *dc)
{ {
@@ -210,43 +193,17 @@ reset(void)
clearkouho(); clearkouho();
} }
/* Kanji candidates for a complete reading. */
static void static void
dictsend(Im *im, Str *key) dictqjp(void)
{
Dictreq old, req;
req.key = *key;
req.lang = im->l->lang;
req.seq = dictseq;
impre(&req.pre);
/* imthread is the sole producer; discard every obsolete queued lookup. */
while(channbrecv(dictreqc, &old) > 0)
;
channbsend(dictreqc, &req);
}
static void
dictqmap(Im *im)
{
Str dict;
clearkouho();
if(im->l->map == nil)
return;
if(!mapget(im->l->map, &im->pre, &dict))
return;
dictsend(im, &dict);
}
static void
dictqjp(Im *im)
{ {
Str reading; Str reading;
clearkouho(); clearkouho();
if(im->l->dict == nil || !jpreading(im, &reading) || reading.n == 0) if(!jpreading(&im, &reading) || reading.n == 0)
return; return;
dictsend(im, &reading); im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho);
selectfirst();
} }
static int static int
@@ -355,7 +312,7 @@ dotrans(Rune c, Str *com)
if(isjp(&im)){ if(isjp(&im)){
e.eat = transjp(&im, c, com); e.eat = transjp(&im, c, com);
if(e.eat) if(e.eat)
dictqjp(&im); dictqjp();
else else
clearkouho(); clearkouho();
return e.eat; return e.eat;
@@ -380,9 +337,6 @@ dotrans(Rune c, Str *com)
sappend(com, &e.s); sappend(com, &e.s);
sclear(&im.pre); sclear(&im.pre);
sappend(&im.pre, &e.next); sappend(&im.pre, &e.next);
if(e.eat && e.dict.n > 0){
dictsend(&im, &e.dict);
}
return e.eat; return e.eat;
} }
@@ -418,13 +372,13 @@ Emit
transmap(Im *im, Rune c) transmap(Im *im, Rune c)
{ {
Emit e = {0}; Emit e = {0};
Str key; Str key, mapped;
Trie *t; Trie *t;
t = im->l->map; t = im->l->map;
key = im->pre; key = im->pre;
sputr(&key, c); sputr(&key, c);
if(maplookup(t, &key, &e.dict)){ if(maplookup(t, &key, &mapped)){
e.eat = 1; e.eat = 1;
e.next = key; e.next = key;
return e; return e;
@@ -433,7 +387,7 @@ transmap(Im *im, Rune c)
e.s = im->pre; e.s = im->pre;
sclear(&key); sclear(&key);
sputr(&key, c); sputr(&key, c);
if(!maplookup(t, &key, &e.dict)) if(!maplookup(t, &key, &mapped))
return e; return e;
e.eat = 1; e.eat = 1;
sputr(&e.next, c); sputr(&e.next, c);
@@ -526,40 +480,28 @@ addkouho(Str *s)
im.kouho[im.nkouho++] = *s; im.kouho[im.nkouho++] = *s;
} }
static void /* The typed keys and their transliteration are both emoji queries. */
searchlookup(int lang, Str *key, Dictres *res)
{
Dictreq req;
req.key = *key;
req.pre = req.key;
req.lang = lang;
req.seq = 0;
dictlookup(&req, res);
}
static void static void
emojiquery(void) emojiquery(void)
{ {
static Dictres res; Str kouho[Maxkouho], raw, local;
Str raw, local; int i, n, rawhit, localhit;
int i, rawhit, localhit;
raw = search.raw; raw = search.raw;
foldascii(&raw); foldascii(&raw);
transstr(im.l, &search.raw, &local); transstr(im.l, &search.raw, &local);
foldascii(&local); foldascii(&local);
clearkouho(); clearkouho();
searchlookup(LangEMOJI, &raw, &res); n = dictlookup(getlang(LangEMOJI), &raw, kouho, Maxkouho);
rawhit = res.nkouho != 0; rawhit = n != 0;
for(i = 0; i < res.nkouho; i++) for(i = 0; i < n; i++)
addkouho(&res.kouho[i]); addkouho(&kouho[i]);
localhit = 0; localhit = 0;
if(scmp(&raw, &local) != 0){ if(scmp(&raw, &local) != 0){
searchlookup(LangEMOJI, &local, &res); n = dictlookup(getlang(LangEMOJI), &local, kouho, Maxkouho);
localhit = res.nkouho != 0; localhit = n != 0;
for(i = 0; i < res.nkouho; i++) for(i = 0; i < n; i++)
addkouho(&res.kouho[i]); addkouho(&kouho[i]);
} }
search.text = rawhit || !localhit ? raw : local; search.text = rawhit || !localhit ? raw : local;
selectfirst(); selectfirst();
@@ -568,16 +510,13 @@ emojiquery(void)
static void static void
hanjaquery(void) hanjaquery(void)
{ {
static Dictres res;
Str key; Str key;
transstr(im.l, &search.raw, &key); transstr(im.l, &search.raw, &key);
search.text = key; search.text = key;
if(key.n == 1 && key.r[0] >= 0xac00 && key.r[0] <= 0xd7a3){
searchlookup(LangHANJA, &key, &res);
setkouho(&res);
}else
clearkouho(); clearkouho();
im.nkouho = dictlookup(getlang(LangHANJA), &key, im.kouho, Maxkouho);
selectfirst();
} }
static void static void
@@ -766,8 +705,10 @@ transition(u32int ks, u32int mod, Str *com)
reset(); reset();
return 1; return 1;
} }
if(im.l->dictq != nil) if(isjp(&im))
im.l->dictq(&im); dictqjp();
else
clearkouho();
return 1; return 1;
} }
if(ks == Kesc){ if(ks == Kesc){
@@ -896,7 +837,6 @@ init(void)
memset(&caret, 0, sizeof caret); memset(&caret, 0, sizeof caret);
activeowner = nil; activeowner = nil;
activecap = 0; activecap = 0;
dictseq = 0;
candidatechosen = 0; candidatechosen = 0;
visible = 0; visible = 0;
} }
@@ -1004,41 +944,16 @@ imhandlekey(Keyreq *kr)
chansend(kr->reply, &res); chansend(kr->reply, &res);
} }
static void
dictresult(Dictres *res)
{
Str pre;
impre(&pre);
if(search.lang || res->seq != dictseq || res->lang != im.l->lang ||
scmp(&res->key, &pre) != 0)
return;
setkouho(res);
redraw();
}
void void
imthread(void*) imthread(void*)
{ {
static Dictres res;
Keyreq kr; Keyreq kr;
Alt alts[] = {
{keyc, &kr, CHANRCV, nil},
{dictresc, &res, CHANRCV, nil},
{nil, nil, CHANEND, nil},
};
threadsetname("im"); threadsetname("im");
init(); init();
for(;;){ for(;;){
switch(alt(alts)){ chanrecv(keyc, &kr);
case 0:
imhandlekey(&kr); imhandlekey(&kr);
break;
case 1:
dictresult(&res);
break;
}
} }
} }

View File

@@ -5,33 +5,22 @@ dictionary_candidates(struct ct *t)
{ {
char many[512], item[8]; char many[512], item[8];
char *p; char *p;
Dictreq req; Str kouho[Maxkouho], key;
Dictres res;
Trie *saved; Trie *saved;
Lang *lang; Lang *lang;
Str key; int i, n;
int i;
lang = getlang(LangJP); lang = getlang(LangJP);
saved = lang->dict; saved = lang->dict;
lang->dict = trienew(); lang->dict = trienew();
key = mkstr("かな");
trieput(lang->dict, "かな", strlen("かな"), " 候補1 かな 候補2 ", trieput(lang->dict, "かな", strlen("かな"), " 候補1 かな 候補2 ",
strlen(" 候補1 かな 候補2 ")); strlen(" 候補1 かな 候補2 "));
memset(&req, 0, sizeof req); key = mkstr("かな");
req.key = key; n = dictlookup(lang, &key, kouho, Maxkouho);
req.pre = mkstr("preedit-one"); if(CT_EQ_INT(t, 2, n)){
req.lang = LangJP; checkstr(t, "candidate 1", "候補1", &kouho[0]);
req.seq = 17; checkstr(t, "candidate 2", "候補2", &kouho[1]);
dictlookup(&req, &res); }
if(!CT_EQ_INT(t, 2, res.nkouho))
goto cleanup;
CT_EQ_INT(t, LangJP, res.lang);
CT_EQ_UINT(t, 17, res.seq);
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
checkstr(t, "candidate 1", "候補1", &res.kouho[0]);
checkstr(t, "candidate 2", "候補2", &res.kouho[1]);
key = mkstr("key");
p = many; p = many;
for(i = 0; i < Maxkouho+1; i++){ for(i = 0; i < Maxkouho+1; i++){
snprint(item, sizeof item, "c%02d", i); snprint(item, sizeof item, "c%02d", i);
@@ -42,56 +31,32 @@ dictionary_candidates(struct ct *t)
} }
*p = '\0'; *p = '\0';
trieput(lang->dict, "key", 3, many, strlen(many)); trieput(lang->dict, "key", 3, many, strlen(many));
req.key = key; key = mkstr("key");
req.pre = mkstr("preedit-two"); n = dictlookup(lang, &key, kouho, Maxkouho);
req.seq = 29; if(CT_EQ_INT(t, Maxkouho, n)){
dictlookup(&req, &res); checkstr(t, "first capped candidate", "c00", &kouho[0]);
if(!CT_EQ_INT(t, Maxkouho, res.nkouho)) checkstr(t, "last capped candidate", "c31", &kouho[31]);
goto cleanup; }
CT_EQ_INT(t, LangJP, res.lang); CT_EQ_INT(t, 3, dictlookup(lang, &key, kouho, 3));
CT_EQ_UINT(t, 29, res.seq);
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
cleanup:
trieclose(lang->dict); trieclose(lang->dict);
lang->dict = saved; lang->dict = saved;
} }
void void
dictionary_misses_clear_result(struct ct *t) dictionary_misses(struct ct *t)
{ {
static const struct { Str kouho[Maxkouho], key;
char *name;
char *key;
char *pre;
} cases[] = {
{ "empty key", "", "empty-preedit" },
{ "missing key", "missing", "missing-preedit" },
};
Dictreq req;
Dictres res;
Trie *saved; Trie *saved;
Lang *lang; Lang *lang;
int i;
lang = getlang(LangJP); lang = getlang(LangJP);
saved = lang->dict; saved = lang->dict;
lang->dict = trienew(); lang->dict = trienew();
for(i = 0; i < nelem(cases); i++){ key = mkstr("");
memset(&res, 0xa5, sizeof res); CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
req.key = mkstr(cases[i].key); key = mkstr("missing");
req.pre = mkstr(cases[i].pre); CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
req.lang = LangJP; CT_EQ_INT(t, 0, dictlookup(getlang(LangKO), &key, kouho, Maxkouho));
req.seq = 0xf00d0000U + i;
dictlookup(&req, &res);
if(res.nkouho != 0)
CT_ERRORF(t, "%s: want 0 candidates, got %d",
cases[i].name, res.nkouho);
CT_EQ_INT(t, LangJP, res.lang);
CT_EQ_UINT(t, 0xf00d0000U + i, res.seq);
checkstr(t, cases[i].name, cases[i].pre, &res.key);
}
trieclose(lang->dict); trieclose(lang->dict);
lang->dict = saved; lang->dict = saved;
} }
@@ -99,25 +64,17 @@ dictionary_misses_clear_result(struct ct *t)
void void
dictionary_emoji_identity(struct ct *t) dictionary_emoji_identity(struct ct *t)
{ {
Dictreq req; Str kouho[Maxkouho], key;
Dictres res;
Trie *saved; Trie *saved;
Lang *lang; Lang *lang;
Str key;
lang = getlang(LangEMOJI); lang = getlang(LangEMOJI);
saved = lang->dict; saved = lang->dict;
lang->dict = trienew(); lang->dict = trienew();
key = mkstr("é");
trieput(lang->dict, "é", strlen("é"), "é", strlen("é")); trieput(lang->dict, "é", strlen("é"), "é", strlen("é"));
memset(&req, 0, sizeof req); key = mkstr("é");
req.key = key; if(CT_EQ_INT(t, 1, dictlookup(lang, &key, kouho, Maxkouho)))
req.pre = key; checkstr(t, "emoji identity candidate", "é", &kouho[0]);
req.lang = LangEMOJI;
req.seq = 0;
dictlookup(&req, &res);
if(CT_EQ_INT(t, 1, res.nkouho))
checkstr(t, "emoji identity candidate", "é", &res.kouho[0]);
trieclose(lang->dict); trieclose(lang->dict);
lang->dict = saved; lang->dict = saved;
} }

View File

@@ -165,37 +165,6 @@ engine_candidate_shortcut_modifiers(struct ct *t)
} }
} }
void
engine_dictionary_queue_latest_wins(struct ct *t)
{
Channel *saved;
Dictreq old, got;
Str key;
saved = dictreqc;
dictreqc = chancreate(sizeof(Dictreq), 1);
memset(&old, 0, sizeof old);
old.key = mkstr("old-key");
old.pre = mkstr("old-preedit");
old.lang = LangJP;
old.seq = 0;
CT_CHECK(t, channbsend(dictreqc, &old) > 0);
init();
im.l = getlang(LangJP);
im.pre = mkstr("new-preedit");
key = mkstr("new-key");
dictsend(&im, &key);
if(CT_CHECK(t, channbrecv(dictreqc, &got) > 0)){
checkstr(t, "newest dictionary key", "new-key", &got.key);
checkstr(t, "newest dictionary preedit", "new-preedit", &got.pre);
CT_EQ_INT(t, LangJP, got.lang);
CT_EQ_UINT(t, dictseq, got.seq);
}
CT_CHECK(t, channbrecv(dictreqc, &got) <= 0);
chanfree(dictreqc);
dictreqc = saved;
}
static Keyres static Keyres
ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod, ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
Caret *caret) Caret *caret)
@@ -306,66 +275,6 @@ engine_active_owner_reset(struct ct *t)
checkstr(t, "same owner after reset", "", &res.preedit); checkstr(t, "same owner after reset", "", &res.preedit);
} }
void
engine_rejects_stale_dictionary_results(struct ct *t)
{
Dictreq a, b;
Dictres res;
Trie *saved;
Lang *jp;
char one, two;
int n;
jp = getlang(LangJP);
saved = jp->dict;
jp->dict = trienew();
init();
im.l = jp;
while(channbrecv(dictreqc, &a) > 0)
;
ownerrequest(&one, Keypress, 'k', 0);
ownerrequest(&one, Keypress, 'a', 0);
n = 0;
while(channbrecv(dictreqc, &a) > 0)
n++;
if(!CT_CHECK(t, n > 0))
goto cleanup;
ownerrequest(&one, Keyreset, 0, 0);
ownerrequest(&two, Keypress, 'k', 0);
ownerrequest(&two, Keypress, 'a', 0);
n = 0;
while(channbrecv(dictreqc, &b) > 0)
n++;
if(!CT_CHECK(t, n > 0))
goto cleanup;
CT_EQ_INT(t, 0, scmp(&a.key, &b.key));
CT_EQ_INT(t, 0, scmp(&a.pre, &b.pre));
CT_EQ_INT(t, a.lang, b.lang);
CT_CHECK(t, a.seq != b.seq);
memset(&res, 0, sizeof res);
res.key = a.pre;
res.lang = a.lang;
res.seq = a.seq;
res.kouho[0] = mkstr("stale");
res.nkouho = 1;
dictresult(&res);
CT_EQ_INT(t, 0, im.nkouho);
res.key = b.pre;
res.lang = b.lang;
res.seq = b.seq;
res.kouho[0] = mkstr("current");
dictresult(&res);
if(CT_EQ_INT(t, 1, im.nkouho))
checkstr(t, "current dictionary result", "current", &im.kouho[0]);
cleanup:
while(channbrecv(dictreqc, &a) > 0)
;
trieclose(jp->dict);
jp->dict = saved;
}
void void
engine_active_owner_caret(struct ct *t) engine_active_owner_caret(struct ct *t)
{ {
@@ -660,7 +569,6 @@ engine_direct_language_modes(struct ct *t)
CT_EQ_PTR(t, nil, im.l->mapname); CT_EQ_PTR(t, nil, im.l->mapname);
CT_EQ_PTR(t, nil, im.l->map); CT_EQ_PTR(t, nil, im.l->map);
CT_EQ_PTR(t, nil, im.l->dictname); CT_EQ_PTR(t, nil, im.l->dictname);
CT_EQ_PTR(t, nil, im.l->dictq);
CT_CHECK(t, strcmp(getlang(LangHANJA)->dictname, "hanja") == 0); CT_CHECK(t, strcmp(getlang(LangHANJA)->dictname, "hanja") == 0);
CT_CHECK(t, keystroke('r', 0, &com)); CT_CHECK(t, keystroke('r', 0, &com));
checkstr(t, "Korean without map", "", &im.pre); checkstr(t, "Korean without map", "", &im.pre);
@@ -789,17 +697,16 @@ draindraw(Drawcmd *last)
static void static void
setcandidates(int n) setcandidates(int n)
{ {
Dictres res;
char name[8]; char name[8];
int i; int i;
memset(&res, 0, sizeof res); clearkouho();
for(i = 0; i < n; i++){ for(i = 0; i < n; i++){
snprint(name, sizeof name, "c%d", i+1); snprint(name, sizeof name, "c%d", i+1);
res.kouho[i] = mkstr(name); im.kouho[i] = mkstr(name);
} }
res.nkouho = n; im.nkouho = n;
setkouho(&res); selectfirst();
} }
static void static void
@@ -929,27 +836,16 @@ void
engine_candidate_completion(struct ct *t) engine_candidate_completion(struct ct *t)
{ {
static Rune keys[] = { Kret, Ktab }; static Rune keys[] = { Kret, Ktab };
Dictres res;
Str com; Str com;
int arrived, i; int i;
for(i = 0; i < nelem(keys); i++) for(i = 0; i < nelem(keys); i++){
for(arrived = 0; arrived < 2; arrived++){
init(); init();
im.l = getlang(LangJP); im.l = getlang(LangJP);
im.pre = mkstr("reading"); im.pre = mkstr("reading");
memset(&res, 0, sizeof res); setcandidates(1);
res.key = im.pre;
res.lang = LangJP;
res.seq = dictseq;
res.kouho[0] = mkstr("candidate");
res.nkouho = 1;
if(arrived)
dictresult(&res);
sclear(&com); sclear(&com);
CT_CHECK(t, keystroke(keys[i], 0, &com)); CT_CHECK(t, keystroke(keys[i], 0, &com));
if(!arrived)
dictresult(&res);
checkstr(t, "unselected candidate commits reading", checkstr(t, "unselected candidate commits reading",
"reading", &com); "reading", &com);
CT_EQ_INT(t, 0, im.nkouho); CT_EQ_INT(t, 0, im.nkouho);
@@ -1047,24 +943,22 @@ searchend(Searchfix *f)
void void
engine_popup_preedit_capability(struct ct *t) engine_popup_preedit_capability(struct ct *t)
{ {
Dictres res;
Drawcmd dc; Drawcmd dc;
Keyres kres; Keyres kres;
Lang *jp;
Trie *saved;
char client, inactive; char client, inactive;
jp = getlang(LangJP);
saved = jp->dict;
jp->dict = trienew();
setdict(jp->dict, "", "");
init(); init();
im.l = getlang(LangJP); im.l = jp;
draindraw(nil); draindraw(nil);
ownerrequestcap(&client, Cclientpreedit, Keypress, 'k', 0); ownerrequestcap(&client, Cclientpreedit, Keypress, 'k', 0);
ownerrequestcap(&client, Cclientpreedit, Keypress, 'a', 0); ownerrequestcap(&client, Cclientpreedit, Keypress, 'a', 0);
memset(&res, 0, sizeof res); CT_EQ_INT(t, 1, im.nkouho);
res.lang = LangJP;
res.key = mkstr("");
res.seq = dictseq;
res.kouho[0] = mkstr("");
res.nkouho = 1;
dictresult(&res);
im.sel = 0;
redraw(); 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);
@@ -1115,6 +1009,8 @@ engine_popup_preedit_capability(struct ct *t)
ownerrequest(&client, Keyrelease, 0, 0); ownerrequest(&client, Keyrelease, 0, 0);
draindraw(nil); draindraw(nil);
trieclose(jp->dict);
jp->dict = saved;
} }
static void static void
@@ -1124,9 +1020,6 @@ hanjabegin(Searchfix *f, int lang)
f->dictlang->dict = trienew(); f->dictlang->dict = trienew();
setdict(f->dictlang->dict, "", "漢 韓"); setdict(f->dictlang->dict, "", "漢 韓");
setdict(f->dictlang->dict, "", ""); setdict(f->dictlang->dict, "", "");
setdict(f->dictlang->dict, "", "");
setdict(f->dictlang->dict, "한글", "");
setdict(f->dictlang->dict, "", "");
init(); init();
im.l = getlang(lang); im.l = getlang(lang);
} }
@@ -1182,22 +1075,9 @@ engine_japanese_readings(struct ct *t)
} }
} }
static int
newestrequest(Dictreq *req)
{
int n;
n = 0;
while(channbrecv(dictreqc, req) > 0)
n++;
return n;
}
void void
engine_japanese_candidates(struct ct *t) engine_japanese_candidates(struct ct *t)
{ {
Dictreq req;
Dictres res;
Trie *saved; Trie *saved;
Lang *jp; Lang *jp;
Str com, shown; Str com, shown;
@@ -1213,12 +1093,6 @@ engine_japanese_candidates(struct ct *t)
goto cleanup; goto cleanup;
shown = shownpre(&im); shown = shownpre(&im);
checkstr(t, "complete Japanese reading", "かんじ", &shown); checkstr(t, "complete Japanese reading", "かんじ", &shown);
if(!CT_CHECK(t, newestrequest(&req) > 0))
goto cleanup;
checkstr(t, "dictionary lookup key", "かんじ", &req.key);
checkstr(t, "dictionary request identity", "かんじ", &req.pre);
dictlookup(&req, &res);
dictresult(&res);
if(!CT_EQ_INT(t, 2, im.nkouho)) if(!CT_EQ_INT(t, 2, im.nkouho))
goto cleanup; goto cleanup;
CT_EQ_INT(t, 0, im.sel); CT_EQ_INT(t, 0, im.sel);
@@ -1228,8 +1102,17 @@ engine_japanese_candidates(struct ct *t)
CT_EQ_INT(t, 1, im.sel); CT_EQ_INT(t, 1, im.sel);
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "selected Kanji", "幹事", &com); checkstr(t, "selected Kanji", "幹事", &com);
CT_EQ_INT(t, 0, im.nkouho);
/* 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));
CT_EQ_INT(t, 0, im.nkouho);
CT_CHECK(t, keystroke(Kback, 0, &com));
if(CT_EQ_INT(t, 1, im.nkouho))
checkstr(t, "shorter reading candidate", "", &im.kouho[0]);
cleanup: cleanup:
newestrequest(&req);
trieclose(jp->dict); trieclose(jp->dict);
jp->dict = saved; jp->dict = saved;
} }
@@ -1294,11 +1177,9 @@ engine_katakana_sequences(struct ct *t)
{ "xu", "" }, { "xu", "" },
{ "vu", "" }, { "vu", "" },
}; };
Dictreq req;
Str com, shown; Str com, shown;
int i; int i;
newestrequest(&req);
for(i = 0; i < nelem(cases); i++){ for(i = 0; i < nelem(cases); i++){
init(); init();
im.l = getlang(LangJPK); im.l = getlang(LangJPK);
@@ -1308,10 +1189,10 @@ engine_katakana_sequences(struct ct *t)
checkstr(t, cases[i].keys, "", &com); checkstr(t, cases[i].keys, "", &com);
shown = shownpre(&im); shown = shownpre(&im);
checkstr(t, cases[i].keys, cases[i].want, &shown); checkstr(t, cases[i].keys, cases[i].want, &shown);
CT_EQ_INT(t, 0, im.nkouho);
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "committed Katakana", cases[i].want, &com); checkstr(t, "committed Katakana", cases[i].want, &com);
} }
CT_EQ_INT(t, 0, newestrequest(&req));
CT_EQ_PTR(t, nil, getlang(LangJPK)->dictname); CT_EQ_PTR(t, nil, getlang(LangJPK)->dictname);
} }
@@ -1692,48 +1573,6 @@ engine_emoji_start_and_unknown(struct ct *t)
searchend(&f); searchend(&f);
} }
void
engine_emoji_dictionary_identity(struct ct *t)
{
Dictres res;
Searchfix 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.seq = dictseq;
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.lang = LangEMOJI;
dictresult(&res);
CT_EQ_INT(t, 1, im.nkouho);
checkstr(t, "search response ignored", "old", &im.kouho[0]);
search.lang = 0;
res.kouho[0] = mkstr("right");
dictresult(&res);
CT_EQ_INT(t, 1, im.nkouho);
CT_EQ_INT(t, 0, im.sel);
checkstr(t, "matching response accepted", "right", &im.kouho[0]);
searchend(&f);
}
void void
engine_hanja_search(struct ct *t) engine_hanja_search(struct ct *t)
{ {

View File

@@ -33,10 +33,8 @@ void engine_candidate_page_metadata(struct ct*);
void engine_candidate_page_snapshots(struct ct*); void engine_candidate_page_snapshots(struct ct*);
void engine_candidate_page_movement(struct ct*); void engine_candidate_page_movement(struct ct*);
void engine_candidate_completion(struct ct*); void engine_candidate_completion(struct ct*);
void engine_dictionary_queue_latest_wins(struct ct*);
void engine_active_owner_lifecycle(struct ct*); void engine_active_owner_lifecycle(struct ct*);
void engine_active_owner_reset(struct ct*); void engine_active_owner_reset(struct ct*);
void engine_rejects_stale_dictionary_results(struct ct*);
void engine_active_owner_caret(struct ct*); void engine_active_owner_caret(struct ct*);
void engine_popup_preedit_capability(struct ct*); void engine_popup_preedit_capability(struct ct*);
void engine_vietnamese_client_preedit(struct ct*); void engine_vietnamese_client_preedit(struct ct*);
@@ -57,7 +55,6 @@ void engine_emoji_navigation(struct ct*);
void engine_search_candidate_keys(struct ct*); void engine_search_candidate_keys(struct ct*);
void engine_emoji_preedit_languages(struct ct*); void engine_emoji_preedit_languages(struct ct*);
void engine_emoji_start_and_unknown(struct ct*); void engine_emoji_start_and_unknown(struct ct*);
void engine_emoji_dictionary_identity(struct ct*);
void engine_hanja_search(struct ct*); void engine_hanja_search(struct ct*);
void engine_hanja_unknown_and_cancel(struct ct*); void engine_hanja_unknown_and_cancel(struct ct*);
void engine_hanja_korean_keys(struct ct*); void engine_hanja_korean_keys(struct ct*);
@@ -66,7 +63,7 @@ void engine_hanja_input_languages(struct ct*);
void engine_randomized_stress(struct ct*); void engine_randomized_stress(struct ct*);
void engine_full_boundary_passthrough(struct ct*); void engine_full_boundary_passthrough(struct ct*);
void dictionary_candidates(struct ct*); void dictionary_candidates(struct ct*);
void dictionary_misses_clear_result(struct ct*); void dictionary_misses(struct ct*);
void dictionary_emoji_identity(struct ct*); void dictionary_emoji_identity(struct ct*);
void ipc_masks_modifiers(struct ct*); void ipc_masks_modifiers(struct ct*);
void ipc_control_and_caret_frames(struct ct*); void ipc_control_and_caret_frames(struct ct*);

View File

@@ -99,13 +99,12 @@ transmap_states(struct ct *t)
int eat; int eat;
char *emit; char *emit;
char *next; char *next;
char *mapped;
} cases[] = { } cases[] = {
{ "", 'k', 1, "", "k", "" }, { "", 'k', 1, "", "k" },
{ "k", 'a', 1, "", "ka", "" }, { "k", 'a', 1, "", "ka" },
{ "ka", 's', 1, "", "s", "" }, { "ka", 's', 1, "", "s" },
{ "ka", 'q', 0, "", "", "" }, { "ka", 'q', 0, "", "" },
{ "k", 'q', 0, "k", "", "" }, { "k", 'q', 0, "k", "" },
}; };
Emit e; Emit e;
Im state; Im state;
@@ -125,7 +124,6 @@ transmap_states(struct ct *t)
i, cases[i].eat, e.eat); i, cases[i].eat, e.eat);
checkstr(t, "emit", cases[i].emit, &e.s); checkstr(t, "emit", cases[i].emit, &e.s);
checkstr(t, "next", cases[i].next, &e.next); checkstr(t, "next", cases[i].next, &e.next);
checkstr(t, "mapped", cases[i].mapped, &e.dict);
} }
trieclose(fixture); trieclose(fixture);
state.l->map = saved; state.l->map = saved;

View File

@@ -5,8 +5,6 @@
Channel *drawc; Channel *drawc;
Channel *keyc; Channel *keyc;
Channel *dictreqc;
Channel *dictresc;
Lang testvi; Lang testvi;
void void
@@ -88,10 +86,8 @@ static const struct ct_test tests[] = {
{ "engine/candidate-page-snapshots", engine_candidate_page_snapshots }, { "engine/candidate-page-snapshots", engine_candidate_page_snapshots },
{ "engine/candidate-page-movement", engine_candidate_page_movement }, { "engine/candidate-page-movement", engine_candidate_page_movement },
{ "engine/candidate-completion", engine_candidate_completion }, { "engine/candidate-completion", engine_candidate_completion },
{ "engine/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle }, { "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
{ "engine/active-owner-reset", engine_active_owner_reset }, { "engine/active-owner-reset", engine_active_owner_reset },
{ "engine/rejects-stale-dictionary", engine_rejects_stale_dictionary_results },
{ "engine/active-owner-caret", engine_active_owner_caret }, { "engine/active-owner-caret", engine_active_owner_caret },
{ "engine/popup-preedit-capability", engine_popup_preedit_capability }, { "engine/popup-preedit-capability", engine_popup_preedit_capability },
{ "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit }, { "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit },
@@ -112,7 +108,6 @@ static const struct ct_test tests[] = {
{ "engine/search-candidate-keys", engine_search_candidate_keys }, { "engine/search-candidate-keys", engine_search_candidate_keys },
{ "engine/emoji-preedit-languages", engine_emoji_preedit_languages }, { "engine/emoji-preedit-languages", engine_emoji_preedit_languages },
{ "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown }, { "engine/emoji-start-and-unknown", engine_emoji_start_and_unknown },
{ "engine/emoji-dictionary-identity", engine_emoji_dictionary_identity },
{ "engine/hanja-search", engine_hanja_search }, { "engine/hanja-search", engine_hanja_search },
{ "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel }, { "engine/hanja-unknown-cancel", engine_hanja_unknown_and_cancel },
{ "engine/hanja-korean-keys", engine_hanja_korean_keys }, { "engine/hanja-korean-keys", engine_hanja_korean_keys },
@@ -120,7 +115,7 @@ static const struct ct_test tests[] = {
{ "engine/hanja-input-languages", engine_hanja_input_languages }, { "engine/hanja-input-languages", engine_hanja_input_languages },
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough }, { "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
{ "dict/candidates", dictionary_candidates }, { "dict/candidates", dictionary_candidates },
{ "dict/misses-clear-result", dictionary_misses_clear_result }, { "dict/misses", dictionary_misses },
{ "dict/emoji-identity", dictionary_emoji_identity }, { "dict/emoji-identity", dictionary_emoji_identity },
{ "ipc/masks-modifiers", ipc_masks_modifiers }, { "ipc/masks-modifiers", ipc_masks_modifiers },
{ "ipc/control-caret-frames", ipc_control_and_caret_frames }, { "ipc/control-caret-frames", ipc_control_and_caret_frames },
@@ -166,8 +161,6 @@ threadmain(int argc, char **argv)
drawc = chancreate(sizeof(Drawcmd), 4); drawc = chancreate(sizeof(Drawcmd), 4);
keyc = chancreate(sizeof(Keyreq), 0); keyc = chancreate(sizeof(Keyreq), 0);
dictreqc = chancreate(sizeof(Dictreq), 64);
dictresc = chancreate(sizeof(Dictres), 0);
testmapinit(); testmapinit();
status = CT_RUN_ARGS(tests, argc, argv); status = CT_RUN_ARGS(tests, argc, argv);
for(i = 0; i < nlang; i++){ for(i = 0; i < nlang; i++){
@@ -178,7 +171,5 @@ threadmain(int argc, char **argv)
testvi.map = nil; testvi.map = nil;
chanfree(drawc); chanfree(drawc);
chanfree(keyc); chanfree(keyc);
chanfree(dictreqc);
chanfree(dictresc);
threadexitsall(status == 0 ? nil : "tests failed"); threadexitsall(status == 0 ? nil : "tests failed");
} }