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.
971 lines
16 KiB
C
971 lines
16 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 int candidatechosen;
|
|
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},
|
|
{LangJP, "hira", "kanji", transmap, backjp, nil, nil},
|
|
{LangJPK, "kata", nil, transmap, backjp, nil, nil},
|
|
{LangKO, nil, nil, transko, backko, nil, nil},
|
|
{LangHANJA, nil, "hanja", nil, nil, nil, nil},
|
|
{LangEMOJI, nil, "emoji", nil, nil, nil, nil},
|
|
{LangVI, "telex", nil, transvi, backvi, nil, nil},
|
|
};
|
|
int nlang = nelem(langs);
|
|
|
|
static void
|
|
clearkouho(void)
|
|
{
|
|
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
|
|
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();
|
|
}
|
|
|
|
/* Kanji candidates for a complete reading. */
|
|
static void
|
|
dictqjp(void)
|
|
{
|
|
Str reading;
|
|
|
|
clearkouho();
|
|
if(!jpreading(&im, &reading) || reading.n == 0)
|
|
return;
|
|
im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho);
|
|
selectfirst();
|
|
}
|
|
|
|
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();
|
|
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);
|
|
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, mapped;
|
|
Trie *t;
|
|
|
|
t = im->l->map;
|
|
key = im->pre;
|
|
sputr(&key, c);
|
|
if(maplookup(t, &key, &mapped)){
|
|
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, &mapped))
|
|
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;
|
|
}
|
|
|
|
/* The typed keys and their transliteration are both emoji queries. */
|
|
static void
|
|
emojiquery(void)
|
|
{
|
|
Str kouho[Maxkouho], raw, local;
|
|
int i, n, rawhit, localhit;
|
|
|
|
raw = search.raw;
|
|
foldascii(&raw);
|
|
transstr(im.l, &search.raw, &local);
|
|
foldascii(&local);
|
|
clearkouho();
|
|
n = dictlookup(getlang(LangEMOJI), &raw, kouho, Maxkouho);
|
|
rawhit = n != 0;
|
|
for(i = 0; i < n; i++)
|
|
addkouho(&kouho[i]);
|
|
localhit = 0;
|
|
if(scmp(&raw, &local) != 0){
|
|
n = dictlookup(getlang(LangEMOJI), &local, kouho, Maxkouho);
|
|
localhit = n != 0;
|
|
for(i = 0; i < n; i++)
|
|
addkouho(&kouho[i]);
|
|
}
|
|
search.text = rawhit || !localhit ? raw : local;
|
|
selectfirst();
|
|
}
|
|
|
|
static void
|
|
hanjaquery(void)
|
|
{
|
|
Str key;
|
|
|
|
transstr(im.l, &search.raw, &key);
|
|
search.text = key;
|
|
clearkouho();
|
|
im.nkouho = dictlookup(getlang(LangHANJA), &key, im.kouho, Maxkouho);
|
|
selectfirst();
|
|
}
|
|
|
|
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(isjp(&im))
|
|
dictqjp();
|
|
else
|
|
clearkouho();
|
|
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;
|
|
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);
|
|
}
|
|
|
|
void
|
|
imthread(void*)
|
|
{
|
|
Keyreq kr;
|
|
|
|
threadsetname("im");
|
|
init();
|
|
for(;;){
|
|
chanrecv(keyc, &kr);
|
|
imhandlekey(&kr);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|