#include "dat.h" #include "fn.h" static Im im; static int popup = 0; static int visible = 0; static void dictqmap(Im*); 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, backmap, dictqmap, nil, nil}, {LangJPK, "kata", "kanji", transmap, backmap, dictqmap, 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 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); pre = search.on ? &search.text : &im.pre; if(popup){ if(search.on || 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; 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 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(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 int dotrans(Rune c, Str *com) { Emit e; Dictreq req; Str mapped; 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(im.pre.n > 0) 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(im.pre.n > 0){ commit(com); reset(); return 1; } return 0; } if(ks == Kback){ if(im.pre.n == 0) return 0; im.l->back(&im); if(im.pre.n == 0){ reset(); return 1; } im.l->dictq(&im); return 1; } if(ks == Kesc){ if(im.pre.n == 0) 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; } reset(); if(setlang(c)) return 1; return 0; } if(ks == '0' && im.nkouho > 0){ commit(com); reset(); return 1; } if(ks > 0x7f || ks == ' '){ if(im.pre.n == 0) 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) res.preedit = search.on ? search.text : im.pre; chansend(kr->reply, &res); } static void dictresult(Dictres *res) { if(search.on || res->lang != im.l->lang || scmp(&res->key, &im.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); } }