For Vietnamese the preedit is the map key ('as', 'oong'); only the popup
and the commit path looked it up. Clients with inline preedit therefore
showed 'as' while Enter committed 'á'. impre() now maps once for every
reader, and snapshot() no longer maps a second time.
1056 lines
17 KiB
C
1056 lines
17 KiB
C
#include "dat.h"
|
|
#include "fn.h"
|
|
|
|
static Im im;
|
|
static int visible = 0;
|
|
static void *activeowner;
|
|
static int activecap;
|
|
static Caret caret;
|
|
static u32int dictseq;
|
|
static int candidatechosen;
|
|
static void dictqmap(Im*);
|
|
static void dictqjp(Im*);
|
|
static void backjp(Im*);
|
|
static int maplookup(Trie*, Str*, Str*);
|
|
|
|
typedef struct Search Search;
|
|
struct Search
|
|
{
|
|
int lang;
|
|
Str raw;
|
|
Str text;
|
|
};
|
|
static Search search;
|
|
|
|
Lang langs[] = {
|
|
{LangEN, nil, nil, nil, nil, nil, nil, nil},
|
|
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil},
|
|
{LangJPK, "kata", nil, transmap, backjp, dictqjp, nil, nil},
|
|
{LangKO, nil, nil, transko, backko, nil, nil, nil},
|
|
{LangHANJA, nil, "hanja", nil, nil, nil, nil, nil},
|
|
{LangEMOJI, nil, "emoji", nil, nil, nil, nil, nil},
|
|
{LangVI, "telex", nil, transvi, backvi, dictqmap, nil, nil},
|
|
};
|
|
int nlang = nelem(langs);
|
|
|
|
static void
|
|
clearkouho(void)
|
|
{
|
|
dictseq++;
|
|
im.nkouho = 0;
|
|
im.sel = -1;
|
|
candidatechosen = 0;
|
|
}
|
|
|
|
static void
|
|
selectfirst(void)
|
|
{
|
|
im.sel = im.nkouho > 0 ? 0 : -1;
|
|
}
|
|
|
|
static int
|
|
pagefirst(void)
|
|
{
|
|
if(im.sel < 0)
|
|
return 0;
|
|
return im.sel / Maxdisp * Maxdisp;
|
|
}
|
|
|
|
static int
|
|
movedelta(u32int ks)
|
|
{
|
|
static struct {
|
|
u32int key;
|
|
int delta;
|
|
} move[] = {
|
|
{Kup, -1}, {Kdown, 1},
|
|
{Kpgup, -Maxdisp}, {Kpgdown, Maxdisp},
|
|
};
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(move); i++)
|
|
if(move[i].key == ks)
|
|
return move[i].delta;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
movekouho(int delta)
|
|
{
|
|
if(im.sel < 0)
|
|
im.sel = 0;
|
|
else
|
|
im.sel += delta;
|
|
if(im.sel < 0)
|
|
im.sel = 0;
|
|
if(im.sel >= im.nkouho)
|
|
im.sel = im.nkouho - 1;
|
|
candidatechosen = 1;
|
|
}
|
|
|
|
static int
|
|
numberedkouho(u32int ks, u32int mod)
|
|
{
|
|
int n;
|
|
|
|
if(mod != 0 || ks < '1' || ks > '9')
|
|
return -1;
|
|
n = pagefirst() + ks - '1';
|
|
if(n >= im.nkouho)
|
|
return -1;
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
isjp(Im *p)
|
|
{
|
|
return p->l->lang == LangJP || p->l->lang == LangJPK;
|
|
}
|
|
|
|
/*
|
|
* Japanese keeps completed kana in pre and an ambiguous romaji syllable in
|
|
* raw. A mapped raw suffix is part of the reading even while it remains
|
|
* pending (notably n, which may still grow into nya).
|
|
*/
|
|
static int
|
|
jpreading(Im *p, Str *s)
|
|
{
|
|
Str mapped;
|
|
|
|
*s = p->pre;
|
|
if(p->raw.n == 0)
|
|
return 1;
|
|
if(mapget(p->l->map, &p->raw, &mapped)){
|
|
sappend(s, &mapped);
|
|
return 1;
|
|
}
|
|
sappend(s, &p->raw);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
haspre(Im *p)
|
|
{
|
|
return p->pre.n != 0 || (isjp(p) && p->raw.n != 0);
|
|
}
|
|
|
|
/* The preedit as clients see it: Telex keys read back through the map. */
|
|
static void
|
|
impre(Str *s)
|
|
{
|
|
if(isjp(&im))
|
|
jpreading(&im, s);
|
|
else if(!mapget(im.l->map, &im.pre, s))
|
|
*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
|
|
snapshot(Drawcmd *dc)
|
|
{
|
|
Str pre;
|
|
int i, first, n;
|
|
|
|
memset(dc, 0, sizeof *dc);
|
|
if(search.lang)
|
|
pre = search.text;
|
|
else
|
|
impre(&pre);
|
|
if(!(activecap & Cclientpreedit))
|
|
dc->pre = pre;
|
|
first = pagefirst();
|
|
n = im.nkouho - first;
|
|
if(n > Maxdisp) n = Maxdisp;
|
|
dc->nkouho = n;
|
|
dc->sel = im.sel >= 0 ? im.sel - first : -1;
|
|
dc->first = first;
|
|
dc->total = im.nkouho;
|
|
dc->caret = caret;
|
|
for(i = 0; i < n; i++)
|
|
dc->kouho[i] = im.kouho[first + i];
|
|
}
|
|
|
|
static void
|
|
redraw(void)
|
|
{
|
|
static Drawcmd old;
|
|
Drawcmd dc;
|
|
|
|
snapshot(&dc);
|
|
if(dc.pre.n == 0 && dc.nkouho == 0 && !visible)
|
|
return;
|
|
visible = dc.pre.n != 0 || dc.nkouho != 0;
|
|
/* imthread is the sole producer; keep only the newest pending draw. */
|
|
while(channbrecv(drawc, &old) > 0)
|
|
;
|
|
channbsend(drawc, &dc);
|
|
}
|
|
|
|
static void
|
|
reset(void)
|
|
{
|
|
sclear(&im.pre);
|
|
sclear(&im.raw);
|
|
search.lang = 0;
|
|
sclear(&search.raw);
|
|
sclear(&search.text);
|
|
memset(&caret, 0, sizeof caret);
|
|
clearkouho();
|
|
}
|
|
|
|
static void
|
|
dictsend(Im *im, Str *key)
|
|
{
|
|
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;
|
|
|
|
clearkouho();
|
|
if(im->l->dict == nil || !jpreading(im, &reading) || reading.n == 0)
|
|
return;
|
|
dictsend(im, &reading);
|
|
}
|
|
|
|
static int
|
|
setlang(int c)
|
|
{
|
|
Lang *l;
|
|
|
|
l = getlang(c);
|
|
if(l == nil)
|
|
return 0;
|
|
im.l = l;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
commitim(Im *p, Str *com)
|
|
{
|
|
Str val;
|
|
|
|
if(isjp(p)){
|
|
jpreading(p, &val);
|
|
sappend(com, &val);
|
|
}else if(p->l->map != nil && mapget(p->l->map, &p->pre, &val))
|
|
sappend(com, &val);
|
|
else
|
|
sappend(com, &p->pre);
|
|
sclear(&p->pre);
|
|
sclear(&p->raw);
|
|
}
|
|
|
|
static void
|
|
commit(Str *com)
|
|
{
|
|
commitim(&im, com);
|
|
}
|
|
|
|
static void
|
|
backjp(Im *im)
|
|
{
|
|
if(im->raw.n != 0)
|
|
spopr(&im->raw);
|
|
else
|
|
spopr(&im->pre);
|
|
}
|
|
|
|
static int
|
|
issmalltsu(Str *s)
|
|
{
|
|
return s->n == 1 && (s->r[0] == L'っ' || s->r[0] == L'ッ');
|
|
}
|
|
|
|
static void
|
|
keeptail(Str *s)
|
|
{
|
|
int i;
|
|
|
|
for(i = 1; i < s->n; i++)
|
|
s->r[i-1] = s->r[i];
|
|
if(s->n > 0)
|
|
s->n--;
|
|
}
|
|
|
|
static int
|
|
transjp(Im *p, Rune c, Str *com)
|
|
{
|
|
Str key, mapped;
|
|
|
|
if(c == '\'' && p->raw.n == 1 && p->raw.r[0] == 'n'){
|
|
if(mapget(p->l->map, &p->raw, &mapped))
|
|
sappend(&p->pre, &mapped);
|
|
sclear(&p->raw);
|
|
return 1;
|
|
}
|
|
key = p->raw;
|
|
if(key.n < Maxrunes)
|
|
sputr(&key, c);
|
|
sclear(&mapped);
|
|
if(maplookup(p->l->map, &key, &mapped)){
|
|
p->raw = key;
|
|
if(issmalltsu(&mapped)){
|
|
sappend(&p->pre, &mapped);
|
|
keeptail(&p->raw);
|
|
}
|
|
return 1;
|
|
}
|
|
if(p->raw.n != 0 && mapget(p->l->map, &p->raw, &mapped)){
|
|
sappend(&p->pre, &mapped);
|
|
sclear(&p->raw);
|
|
sclear(&key);
|
|
sputr(&key, c);
|
|
if(maplookup(p->l->map, &key, &mapped)){
|
|
p->raw = key;
|
|
return 1;
|
|
}
|
|
}
|
|
commitim(p, com);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dotrans(Rune c, Str *com)
|
|
{
|
|
Emit e;
|
|
Str mapped;
|
|
|
|
if(isjp(&im)){
|
|
e.eat = transjp(&im, c, com);
|
|
if(e.eat)
|
|
dictqjp(&im);
|
|
else
|
|
clearkouho();
|
|
return e.eat;
|
|
}
|
|
e = im.l->trans(&im, c);
|
|
if(im.l->lang == LangVI){
|
|
if(e.s.n > 0 || !e.eat)
|
|
sclear(&im.raw);
|
|
if(e.eat && e.next.n > 0){
|
|
if(im.raw.n >= Maxrunes){
|
|
if(!mapget(im.l->map, &e.next, &mapped))
|
|
mapped = e.next;
|
|
sappend(com, &mapped);
|
|
reset();
|
|
return e.eat;
|
|
}
|
|
sputr(&im.raw, c);
|
|
}
|
|
}
|
|
clearkouho();
|
|
if(e.s.n > 0)
|
|
sappend(com, &e.s);
|
|
sclear(&im.pre);
|
|
sappend(&im.pre, &e.next);
|
|
if(e.eat && e.dict.n > 0){
|
|
dictsend(&im, &e.dict);
|
|
}
|
|
return e.eat;
|
|
}
|
|
|
|
Lang*
|
|
getlang(int lang)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(langs); i++)
|
|
if(langs[i].lang == lang)
|
|
return &langs[i];
|
|
return nil;
|
|
}
|
|
|
|
static int
|
|
maplookup(Trie *t, Str *key, Str *out)
|
|
{
|
|
char *v;
|
|
int match, vlen;
|
|
|
|
sclear(out);
|
|
if(key->n == 0)
|
|
return 0;
|
|
match = trielookup(t, key, &v, &vlen);
|
|
if(match == TrieMiss)
|
|
return 0;
|
|
if(match == TrieExact && !sinit(out, v, vlen))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
Emit
|
|
transmap(Im *im, Rune c)
|
|
{
|
|
Emit e = {0};
|
|
Str key;
|
|
Trie *t;
|
|
|
|
t = im->l->map;
|
|
key = im->pre;
|
|
sputr(&key, c);
|
|
if(maplookup(t, &key, &e.dict)){
|
|
e.eat = 1;
|
|
e.next = key;
|
|
return e;
|
|
}
|
|
if(!mapget(t, &im->pre, &e.s))
|
|
e.s = im->pre;
|
|
sclear(&key);
|
|
sputr(&key, c);
|
|
if(!maplookup(t, &key, &e.dict))
|
|
return e;
|
|
e.eat = 1;
|
|
sputr(&e.next, c);
|
|
return e;
|
|
}
|
|
|
|
static Rune
|
|
ctrlkey(Rune c)
|
|
{
|
|
if(c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
if(c >= 'a' && c <= 'z')
|
|
return c - 'a' + 1;
|
|
return c;
|
|
}
|
|
|
|
static Rune
|
|
kokey(Rune c, u32int mod)
|
|
{
|
|
if(c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
if((mod & Mshift) && c >= 'a' && c <= 'z')
|
|
c -= 'a' - 'A';
|
|
return c;
|
|
}
|
|
|
|
static int
|
|
ismodkey(u32int ks)
|
|
{
|
|
return ks >= Kmodfirst && ks <= Kmodlast;
|
|
}
|
|
|
|
static void
|
|
foldascii(Str *s)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < s->n; i++)
|
|
if(s->r[i] >= 'A' && s->r[i] <= 'Z')
|
|
s->r[i] += 'a' - 'A';
|
|
}
|
|
|
|
static void
|
|
transstr(Lang *l, Str *raw, Str *out)
|
|
{
|
|
Im q;
|
|
Emit e;
|
|
Str mapped;
|
|
int i;
|
|
|
|
sclear(out);
|
|
if(l == nil || l->trans == nil || (l->trans == transmap && l->map == nil)){
|
|
*out = *raw;
|
|
return;
|
|
}
|
|
memset(&q, 0, sizeof q);
|
|
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++){
|
|
e = l->trans(&q, raw->r[i]);
|
|
sappend(out, &e.s);
|
|
q.pre = e.next;
|
|
if(!e.eat && out->n < Maxrunes)
|
|
sputr(out, raw->r[i]);
|
|
}
|
|
if(q.pre.n == 0)
|
|
return;
|
|
if(l->map != nil && mapget(l->map, &q.pre, &mapped))
|
|
sappend(out, &mapped);
|
|
else
|
|
sappend(out, &q.pre);
|
|
}
|
|
|
|
static void
|
|
addkouho(Str *s)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < im.nkouho; i++)
|
|
if(scmp(&im.kouho[i], s) == 0)
|
|
return;
|
|
if(im.nkouho < Maxkouho)
|
|
im.kouho[im.nkouho++] = *s;
|
|
}
|
|
|
|
static void
|
|
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
|
|
emojiquery(void)
|
|
{
|
|
static Dictres res;
|
|
Str raw, local;
|
|
int i, rawhit, localhit;
|
|
|
|
raw = search.raw;
|
|
foldascii(&raw);
|
|
transstr(im.l, &search.raw, &local);
|
|
foldascii(&local);
|
|
clearkouho();
|
|
searchlookup(LangEMOJI, &raw, &res);
|
|
rawhit = res.nkouho != 0;
|
|
for(i = 0; i < res.nkouho; i++)
|
|
addkouho(&res.kouho[i]);
|
|
localhit = 0;
|
|
if(scmp(&raw, &local) != 0){
|
|
searchlookup(LangEMOJI, &local, &res);
|
|
localhit = res.nkouho != 0;
|
|
for(i = 0; i < res.nkouho; i++)
|
|
addkouho(&res.kouho[i]);
|
|
}
|
|
search.text = rawhit || !localhit ? raw : local;
|
|
selectfirst();
|
|
}
|
|
|
|
static void
|
|
hanjaquery(void)
|
|
{
|
|
static Dictres res;
|
|
Str key;
|
|
|
|
transstr(im.l, &search.raw, &key);
|
|
search.text = key;
|
|
if(key.n == 1 && key.r[0] >= 0xac00 && key.r[0] <= 0xd7a3){
|
|
searchlookup(LangHANJA, &key, &res);
|
|
setkouho(&res);
|
|
}else
|
|
clearkouho();
|
|
}
|
|
|
|
static void
|
|
searchquery(void)
|
|
{
|
|
if(search.lang == LangEMOJI)
|
|
emojiquery();
|
|
else if(search.lang == LangHANJA)
|
|
hanjaquery();
|
|
}
|
|
|
|
static int
|
|
searchlang(Rune c, u32int mod)
|
|
{
|
|
if(mod & (Malt|Msuper))
|
|
return 0;
|
|
if(c == LangEMOJI || c == LangHANJA)
|
|
return c;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
endsearch(void)
|
|
{
|
|
search.lang = 0;
|
|
sclear(&search.raw);
|
|
sclear(&search.text);
|
|
clearkouho();
|
|
}
|
|
|
|
static void
|
|
startsearch(int lang, Str *com)
|
|
{
|
|
if(haspre(&im))
|
|
commit(com);
|
|
sclear(&im.pre);
|
|
sclear(&im.raw);
|
|
clearkouho();
|
|
search.lang = lang;
|
|
sclear(&search.raw);
|
|
sclear(&search.text);
|
|
}
|
|
|
|
static void
|
|
commitsearch(Str *com)
|
|
{
|
|
sappend(com, &search.text);
|
|
endsearch();
|
|
}
|
|
|
|
static int
|
|
picksearch(int n, Str *com)
|
|
{
|
|
if(n < 0 || n >= im.nkouho)
|
|
return 0;
|
|
sappend(com, &im.kouho[n]);
|
|
endsearch();
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
searchkey(u32int ks, u32int mod, Str *com)
|
|
{
|
|
int n;
|
|
Rune c;
|
|
|
|
if(ismodkey(ks))
|
|
return 0;
|
|
n = movedelta(ks);
|
|
if(n != 0){
|
|
if(im.nkouho == 0)
|
|
return 1;
|
|
movekouho(n);
|
|
return 1;
|
|
}
|
|
n = numberedkouho(ks, mod);
|
|
if(n >= 0){
|
|
picksearch(n, com);
|
|
return 1;
|
|
}
|
|
if(ks == Ktab){
|
|
if(im.nkouho == 0)
|
|
return 1;
|
|
if(mod & Mshift){
|
|
if(im.sel <= 0)
|
|
im.sel = im.nkouho - 1;
|
|
else
|
|
im.sel--;
|
|
}else if(im.sel >= im.nkouho - 1)
|
|
im.sel = 0;
|
|
else
|
|
im.sel++;
|
|
return 1;
|
|
}
|
|
if(ks == Kret){
|
|
if(im.nkouho > 0)
|
|
return picksearch(im.sel >= 0 ? im.sel : 0, com);
|
|
commitsearch(com);
|
|
return 1;
|
|
}
|
|
if(ks == Kback){
|
|
if(search.raw.n == 0){
|
|
endsearch();
|
|
return 0;
|
|
}
|
|
spopr(&search.raw);
|
|
searchquery();
|
|
return 1;
|
|
}
|
|
if(ks == Kesc){
|
|
endsearch();
|
|
return 1;
|
|
}
|
|
if(ks >= Kspec || (ks < ' ' && !(mod & Mctrl)) ||
|
|
(mod & (Malt|Msuper))){
|
|
commitsearch(com);
|
|
return 0;
|
|
}
|
|
if(mod & Mctrl){
|
|
c = ctrlkey(ks);
|
|
n = searchlang(c, mod);
|
|
if(n != 0){
|
|
if(n == search.lang)
|
|
endsearch();
|
|
else
|
|
startsearch(n, com);
|
|
return 1;
|
|
}
|
|
endsearch();
|
|
if(setlang(c))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
if(search.raw.n >= Maxrunes){
|
|
commitsearch(com);
|
|
return 0;
|
|
}
|
|
if(im.l->lang == LangKO)
|
|
ks = kokey(ks, mod);
|
|
sputr(&search.raw, ks);
|
|
searchquery();
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
transition(u32int ks, u32int mod, Str *com)
|
|
{
|
|
int n;
|
|
Rune c;
|
|
|
|
if(ismodkey(ks))
|
|
return 0;
|
|
if(search.lang)
|
|
return searchkey(ks, mod, com);
|
|
n = movedelta(ks);
|
|
if(n != 0){
|
|
if(im.nkouho == 0)
|
|
return 0;
|
|
movekouho(n);
|
|
return 1;
|
|
}
|
|
n = numberedkouho(ks, mod);
|
|
if(n >= 0){
|
|
sappend(com, &im.kouho[n]);
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(ks == Ktab || ks == Kret){
|
|
if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho){
|
|
sappend(com, &im.kouho[im.sel]);
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(haspre(&im)){
|
|
commit(com);
|
|
reset();
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
if(ks == Kback){
|
|
if(!haspre(&im))
|
|
return 0;
|
|
im.l->back(&im);
|
|
if(!haspre(&im)){
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(im.l->dictq != nil)
|
|
im.l->dictq(&im);
|
|
return 1;
|
|
}
|
|
if(ks == Kesc){
|
|
if(!haspre(&im))
|
|
return 0;
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(ks >= Kspec || (mod & (Malt|Msuper))){
|
|
commit(com);
|
|
reset();
|
|
return 0;
|
|
}
|
|
if(mod & Mctrl){
|
|
c = ctrlkey(ks);
|
|
n = searchlang(c, mod);
|
|
if(n != 0){
|
|
startsearch(n, com);
|
|
return 1;
|
|
}
|
|
if(getlang(c) != nil){
|
|
if(haspre(&im))
|
|
commit(com);
|
|
reset();
|
|
setlang(c);
|
|
return 1;
|
|
}
|
|
if(haspre(&im))
|
|
commit(com);
|
|
reset();
|
|
return 0;
|
|
}
|
|
if(ks == '0' && im.nkouho > 0){
|
|
commit(com);
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(ks > 0x7f || ks == ' '){
|
|
if(!haspre(&im))
|
|
return 0;
|
|
commit(com);
|
|
reset();
|
|
if(com->n >= Maxrunes)
|
|
return 0;
|
|
sputr(com, ks);
|
|
return 1;
|
|
}
|
|
if(im.l->trans == nil)
|
|
return 0;
|
|
if(im.pre.n >= Maxrunes ||
|
|
(isjp(&im) && im.pre.n + im.raw.n >= Maxrunes)){
|
|
commit(com);
|
|
reset();
|
|
return 0;
|
|
}
|
|
if(im.l->lang == LangKO)
|
|
ks = kokey(ks, mod);
|
|
n = dotrans(ks, com);
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
samestate(Im *a, Search *as, Im *b, Search *bs)
|
|
{
|
|
int i;
|
|
|
|
if(a->l != b->l || scmp(&a->pre, &b->pre) != 0 ||
|
|
scmp(&a->raw, &b->raw) != 0 || a->nkouho != b->nkouho ||
|
|
a->sel != b->sel || as->lang != bs->lang ||
|
|
scmp(&as->raw, &bs->raw) != 0 || scmp(&as->text, &bs->text) != 0)
|
|
return 0;
|
|
for(i = 0; i < a->nkouho; i++)
|
|
if(scmp(&a->kouho[i], &b->kouho[i]) != 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
savestate(Im *p, Search *s)
|
|
{
|
|
int i;
|
|
|
|
memset(p, 0, sizeof *p);
|
|
memset(s, 0, sizeof *s);
|
|
p->l = im.l;
|
|
sappend(&p->pre, &im.pre);
|
|
sappend(&p->raw, &im.raw);
|
|
p->nkouho = im.nkouho;
|
|
p->sel = im.sel;
|
|
for(i = 0; i < im.nkouho; i++)
|
|
sappend(&p->kouho[i], &im.kouho[i]);
|
|
s->lang = search.lang;
|
|
sappend(&s->raw, &search.raw);
|
|
sappend(&s->text, &search.text);
|
|
}
|
|
|
|
static int
|
|
keystrokeforce(u32int ks, u32int mod, Str *com, int force)
|
|
{
|
|
Im oldim;
|
|
Search oldsearch;
|
|
int eaten, ncom;
|
|
|
|
savestate(&oldim, &oldsearch);
|
|
ncom = com->n;
|
|
eaten = transition(ks, mod, com);
|
|
if(force || eaten || com->n != ncom ||
|
|
!samestate(&oldim, &oldsearch, &im, &search))
|
|
redraw();
|
|
return eaten;
|
|
}
|
|
|
|
static int
|
|
keystroke(u32int ks, u32int mod, Str *com)
|
|
{
|
|
return keystrokeforce(ks, mod, com, 0);
|
|
}
|
|
|
|
static void
|
|
init(void)
|
|
{
|
|
memset(&im, 0, sizeof(im));
|
|
im.l = getlang(LangEN);
|
|
im.sel = -1;
|
|
memset(&search, 0, sizeof search);
|
|
memset(&caret, 0, sizeof caret);
|
|
activeowner = nil;
|
|
activecap = 0;
|
|
dictseq = 0;
|
|
candidatechosen = 0;
|
|
visible = 0;
|
|
}
|
|
|
|
static int
|
|
meaningful(Keyreq *kr)
|
|
{
|
|
return kr->ks != 0 && !ismodkey(kr->ks);
|
|
}
|
|
|
|
static int
|
|
setactivecap(int cap)
|
|
{
|
|
int old;
|
|
|
|
old = activecap & Cclientpreedit;
|
|
activecap &= ~Cclientpreedit;
|
|
activecap |= cap & Cclientpreedit;
|
|
return old != (activecap & Cclientpreedit);
|
|
}
|
|
|
|
static int
|
|
careteq(Caret *a, Caret *b)
|
|
{
|
|
return a->valid == b->valid && a->x == b->x && a->y == b->y &&
|
|
a->h == b->h;
|
|
}
|
|
|
|
static void
|
|
imhandlekey(Keyreq *kr)
|
|
{
|
|
Keyres res;
|
|
void *oldowner;
|
|
int changed;
|
|
|
|
oldowner = activeowner;
|
|
sclear(&res.commit);
|
|
sclear(&res.preedit);
|
|
res.eaten = 1;
|
|
switch(kr->op){
|
|
case Keyrelease:
|
|
if(kr->owner == activeowner){
|
|
reset();
|
|
activeowner = nil;
|
|
activecap = 0;
|
|
redraw();
|
|
}
|
|
break;
|
|
case Keyreset:
|
|
if(kr->owner == activeowner){
|
|
reset();
|
|
redraw();
|
|
}
|
|
break;
|
|
case Keycaret:
|
|
if(kr->owner == activeowner && !careteq(&caret, &kr->caret)){
|
|
caret = kr->caret;
|
|
if(visible)
|
|
redraw();
|
|
}
|
|
break;
|
|
case Keycap:
|
|
res.eaten = 0;
|
|
if(activeowner != nil && kr->owner == activeowner){
|
|
res.eaten = 1;
|
|
if(setactivecap(kr->cap))
|
|
redraw();
|
|
}
|
|
break;
|
|
case Keypress:
|
|
res.eaten = 0;
|
|
changed = 0;
|
|
if(activeowner != nil && kr->owner == activeowner &&
|
|
setactivecap(kr->cap))
|
|
changed = 1;
|
|
if(meaningful(kr)){
|
|
if(kr->owner != activeowner){
|
|
reset();
|
|
activeowner = kr->owner;
|
|
activecap = kr->cap & Cclientpreedit;
|
|
caret = kr->caret;
|
|
changed = 1;
|
|
}else if(kr->caret.valid &&
|
|
!careteq(&caret, &kr->caret)){
|
|
caret = kr->caret;
|
|
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;
|
|
}
|
|
if(activeowner != oldowner)
|
|
ximownernotify();
|
|
if(kr->owner == activeowner){
|
|
if(search.lang)
|
|
res.preedit = search.text;
|
|
else
|
|
impre(&res.preedit);
|
|
}
|
|
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
|
|
imthread(void*)
|
|
{
|
|
static Dictres res;
|
|
Keyreq kr;
|
|
Alt alts[] = {
|
|
{keyc, &kr, CHANRCV, nil},
|
|
{dictresc, &res, CHANRCV, nil},
|
|
{nil, nil, CHANEND, nil},
|
|
};
|
|
|
|
threadsetname("im");
|
|
init();
|
|
for(;;){
|
|
switch(alt(alts)){
|
|
case 0:
|
|
imhandlekey(&kr);
|
|
break;
|
|
case 1:
|
|
dictresult(&res);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
mapget(Trie *t, Str *key, Str *out)
|
|
{
|
|
char *v;
|
|
int vlen;
|
|
|
|
sclear(out);
|
|
if(key->n == 0 || trielookup(t, key, &v, &vlen) != TrieExact)
|
|
return 0;
|
|
return sinit(out, v, vlen);
|
|
}
|