#include "dat.h" #include "fn.h" static Im im; static int popup = 0; static int visible = 0; static Lock ownerlock; static uvlong ownerctr; static uvlong activeowner; static Caret caret; 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; uvlong ownernew(void) { uvlong n; lock(&ownerlock); n = ++ownerctr; if(n == 0) n = ++ownerctr; unlock(&ownerlock); return n; } 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) { 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.lang) pre = search.text; else impre(&pre); if(popup){ if(search.lang || 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; dc.caret = caret; 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); search.lang = 0; sclear(&search.raw); sclear(&search.text); memset(&caret, 0, sizeof caret); clearkouho(); show(); } void dictsend(Im *im, Str *key) { Dictreq old, req; req.key = *key; req.lang = im->l->lang; if(isjp(im)) jpreading(im, &req.pre); else req.pre = im->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(); show(); 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(); 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; 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){ 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 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 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) { 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 searchlookup(int lang, Str *key, Dictres *res) { Dictreq req; req.key = *key; req.pre = req.key; req.lang = lang; 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; show(); } 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(); show(); } 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(); show(); } 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); show(); } 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, 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){ picksearch(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 picksearch(im.sel >= 0 ? im.sel : 0, com); commitsearch(com); return 1; } if(ks == Kback){ if(search.raw.n == 0) return 1; 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; } if(c == 0x10){ popup = !popup; show(); 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 keystroke(u32int ks, u32int mod, Str *com) { int n, off; Rune c; if(ismodkey(ks)) return 0; if(search.lang) 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 && mod == 0){ 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; } 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(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); 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); show(); return n; } static void init(void) { memset(&im, 0, sizeof(im)); im.l = getlang(LangEN); memset(&search, 0, sizeof search); memset(&caret, 0, sizeof caret); activeowner = 0; visible = 0; } static int meaningful(Keyreq *kr) { return kr->ks != 0 && !ismodkey(kr->ks); } static void imhandlekey(Keyreq *kr) { Keyres res; sclear(&res.commit); sclear(&res.preedit); res.eaten = 1; switch(kr->op){ case Keyrelease: if(kr->owner == activeowner){ reset(); activeowner = 0; } break; case Keyreset: if(kr->owner == activeowner) reset(); break; case Keycaret: if(kr->owner == activeowner){ caret = kr->caret; show(); } break; default: res.eaten = 0; if(meaningful(kr)){ if(kr->owner != activeowner){ reset(); activeowner = kr->owner; caret = kr->caret; }else if(kr->caret.valid) caret = kr->caret; res.eaten = keystroke(kr->ks, kr->mod, &res.commit); } break; } if(kr->want && 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->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; int i; for(i = 0; i < nelem(langs); i++){ if(langs[i].mapname == nil) continue; path = smprint("%s/%s.map", dir, langs[i].mapname); if(path == nil) die("out of memory"); langs[i].map = trieopen(path); free(path); } }