Files
strans/strans.c

795 lines
12 KiB
C

#include "dat.h"
#include "fn.h"
static Im im;
static int popup = 0;
static int visible = 0;
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 on;
Str raw;
Str text;
};
static Search search;
static void
backmap(Im *im)
{
spopr(&im->pre);
}
Lang langs[] = {
{LangEN, "english", nil, transmap, backmap, dictqmap, nil, nil},
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil},
{LangJPK, "kata", nil, transmap, backjp, dictqjp, nil, nil},
{LangKO, "hangul", nil, transko, backko, dictqmap, 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)
{
im.nkouho = 0;
im.sel = -1;
}
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);
}
static void
impre(Str *s)
{
if(isjp(&im))
jpreading(&im, s);
else
*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++;
}
}
static void
show(void)
{
static Drawcmd old;
Drawcmd dc;
Str pre;
int i, first, n;
sclear(&dc.pre);
if(search.on)
pre = search.text;
else
impre(&pre);
if(popup){
if(search.on || isjp(&im) || im.l->map == nil ||
!mapget(im.l->map, &pre, &dc.pre))
dc.pre = pre;
}
first = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
n = im.nkouho - first;
if(n > Maxdisp) n = Maxdisp;
dc.nkouho = n;
dc.sel = im.sel >= 0 ? im.sel - first : -1;
for(i = 0; i < n; i++)
dc.kouho[i] = im.kouho[first + i];
if(dc.pre.n == 0 && n == 0 && !visible)
return;
visible = dc.pre.n != 0 || n != 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);
clearkouho();
show();
}
void
dictsend(Im *im, Str *key)
{
Dictreq req;
req.key = *key;
req.lang = im->l->lang;
if(isjp(im))
jpreading(im, &req.pre);
else
req.pre = im->pre;
channbsend(dictreqc, &req);
}
static void
dictqmap(Im *im)
{
Str dict;
clearkouho();
show();
if(!mapget(im->l->map, &im->pre, &dict))
return;
dictsend(im, &dict);
}
static void
dictqjp(Im *im)
{
Str reading;
clearkouho();
show();
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
commit(Str *com)
{
Str val;
if(isjp(&im)){
jpreading(&im, &val);
sappend(com, &val);
}else if(im.l->map != nil && mapget(im.l->map, &im.pre, &val))
sappend(com, &val);
else
sappend(com, &im.pre);
sclear(&im.pre);
sclear(&im.raw);
}
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
dotransjp(Rune c, Str *com)
{
Str key, mapped;
if(c == '\'' && im.raw.n == 1 && im.raw.r[0] == 'n'){
if(mapget(im.l->map, &im.raw, &mapped))
sappend(&im.pre, &mapped);
sclear(&im.raw);
dictqjp(&im);
return 1;
}
key = im.raw;
if(key.n < Maxrunes)
sputr(&key, c);
sclear(&mapped);
if(maplookup(im.l->map, &key, &mapped)){
im.raw = key;
if(issmalltsu(&mapped)){
sappend(&im.pre, &mapped);
keeptail(&im.raw);
}
dictqjp(&im);
return 1;
}
if(im.raw.n != 0 && mapget(im.l->map, &im.raw, &mapped)){
sappend(&im.pre, &mapped);
sclear(&im.raw);
sclear(&key);
sputr(&key, c);
if(maplookup(im.l->map, &key, &mapped)){
im.raw = key;
dictqjp(&im);
return 1;
}
}
commit(com);
clearkouho();
return 0;
}
static int
dotrans(Rune c, Str *com)
{
Emit e;
Dictreq req;
Str mapped;
if(isjp(&im))
return dotransjp(c, com);
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){
req.key = e.dict;
req.lang = im.l->lang;
req.pre = im.pre;
channbsend(dictreqc, &req);
}
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 buf[Maxutf], *v;
int klen, vlen;
if(key->n == 0)
return 0;
v = nil;
vlen = 0;
klen = stoutf(key, buf, sizeof(buf));
if(!trielookup(t, buf, klen, &v, &vlen))
return 0;
if(out != nil && v != nil)
sinit(out, v, vlen);
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 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)
{
static 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;
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
emojilookup(Str *key, Dictres *res)
{
Dictreq req;
req.key = *key;
req.pre = req.key;
req.lang = LangEMOJI;
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();
emojilookup(&raw, &res);
rawhit = res.nkouho != 0;
for(i = 0; i < res.nkouho; i++)
addkouho(&res.kouho[i]);
localhit = 0;
if(scmp(&raw, &local) != 0){
emojilookup(&local, &res);
localhit = res.nkouho != 0;
for(i = 0; i < res.nkouho; i++)
addkouho(&res.kouho[i]);
}
search.text = rawhit || !localhit ? raw : local;
show();
}
static void
endsearch(void)
{
search.on = 0;
sclear(&search.raw);
sclear(&search.text);
clearkouho();
show();
}
static void
startsearch(Str *com)
{
if(haspre(&im))
commit(com);
sclear(&im.pre);
sclear(&im.raw);
clearkouho();
search.on = 1;
sclear(&search.raw);
sclear(&search.text);
show();
}
static void
commitsearch(Str *com)
{
sappend(com, &search.text);
endsearch();
}
static int
pickemoji(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, off;
Rune c;
if(ismodkey(ks))
return 0;
if(ks == Kdown || ks == Kup){
if(im.nkouho == 0)
return 1;
if(ks == Kdown && im.sel < im.nkouho - 1)
im.sel++;
if(ks == Kup && im.sel > 0)
im.sel--;
show();
return 1;
}
n = ks - '1';
off = im.sel >= Maxdisp ? im.sel - Maxdisp + 1 : 0;
if(n >= 0 && n < Maxdisp && off + n < im.nkouho && mod == 0){
pickemoji(off + 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++;
show();
return 1;
}
if(ks == Kret){
if(im.nkouho > 0)
return pickemoji(im.sel >= 0 ? im.sel : 0, com);
commitsearch(com);
return 1;
}
if(ks == Kback){
if(search.raw.n == 0)
return 1;
spopr(&search.raw);
emojiquery();
return 1;
}
if(ks == Kesc){
endsearch();
return 1;
}
if(mod & Mctrl){
c = ctrlkey(ks);
if(c == LangEMOJI){
endsearch();
return 1;
}
if(c == 0x10){
popup = !popup;
show();
return 1;
}
endsearch();
if(setlang(c))
return 1;
return 0;
}
if(ks >= Kspec || ks < ' ' || (mod & (Malt|Msuper))){
commitsearch(com);
return 0;
}
if(search.raw.n >= Maxrunes){
commitsearch(com);
return 0;
}
sputr(&search.raw, ks);
emojiquery();
return 1;
}
static int
keystroke(u32int ks, u32int mod, Str *com)
{
int n, off;
Rune c;
if(ismodkey(ks))
return 0;
if(search.on)
return searchkey(ks, mod, com);
if(ks == Kdown || ks == Kup){
if(im.nkouho == 0)
return 0;
if(ks == Kdown && im.sel < im.nkouho - 1)
im.sel++;
if(ks == Kup && im.sel >= 0)
im.sel--;
show();
return 1;
}
n = ks - '1';
off = 0;
if(im.sel >= Maxdisp)
off = im.sel - Maxdisp + 1;
if(n >= 0 && n < Maxdisp && off + n < im.nkouho){
sappend(com, &im.kouho[off + n]);
reset();
return 1;
}
if(ks == Ktab || ks == Kret){
if(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;
}
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);
if(c == LangEMOJI){
startsearch(com);
return 1;
}
if(c == 0x10){
popup = !popup;
show();
return 1;
}
if(getlang(c) != nil){
if(isjp(&im) && haspre(&im))
commit(com);
reset();
setlang(c);
return 1;
}
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);
sputr(com, ks);
reset();
return 1;
}
n = dotrans(ks, com);
show();
return n;
}
static void
init(void)
{
memset(&im, 0, sizeof(im));
im.l = getlang(LangEN);
memset(&search, 0, sizeof search);
visible = 0;
}
static void
imhandlekey(Keyreq *kr)
{
Keyres res;
sclear(&res.commit);
sclear(&res.preedit);
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
if(kr->want){
if(search.on)
res.preedit = search.text;
else
impre(&res.preedit);
}
chansend(kr->reply, &res);
}
static void
dictresult(Dictres *res)
{
Str pre;
impre(&pre);
if(search.on || res->lang != im.l->lang ||
scmp(&res->key, &pre) != 0)
return;
setkouho(res);
show();
}
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 buf[Maxutf], *v;
int klen, vlen;
if(key->n == 0)
return 0;
klen = stoutf(key, buf, sizeof(buf));
v = trieget(t, buf, klen, &vlen);
if(v == nil)
return 0;
sinit(out, v, vlen);
return 1;
}
void
mapinit(char *dir)
{
char path[1024];
int i;
for(i = 0; i < nelem(langs); i++){
if(langs[i].mapname == nil)
continue;
snprint(path, sizeof(path), "%s/%s.map", dir, langs[i].mapname);
langs[i].map = trieopen(path);
}
}