diff --git a/strans.c b/strans.c index 60c45e6..510b911 100644 --- a/strans.c +++ b/strans.c @@ -7,6 +7,7 @@ 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*); @@ -38,6 +39,7 @@ clearkouho(void) dictseq++; im.nkouho = 0; im.sel = -1; + candidatechosen = 0; } static void @@ -83,6 +85,7 @@ movekouho(int delta) im.sel = 0; if(im.sel >= im.nkouho) im.sel = im.nkouho - 1; + candidatechosen = 1; } static int @@ -154,36 +157,43 @@ setkouho(Dictres *res) } static void -show(void) +snapshot(Drawcmd *dc) { - static Drawcmd old; - Drawcmd dc; Str pre; int i, first, n; - sclear(&dc.pre); + memset(dc, 0, sizeof *dc); if(search.lang) pre = search.text; else impre(&pre); if(!(activecap & Cclientpreedit)){ if(search.lang || isjp(&im) || im.l->map == nil || - !mapget(im.l->map, &pre, &dc.pre)) - dc.pre = pre; + !mapget(im.l->map, &pre, &dc->pre)) + 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; + 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]; - if(dc.pre.n == 0 && n == 0 && !visible) + 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 || n != 0; + visible = dc.pre.n != 0 || dc.nkouho != 0; /* imthread is the sole producer; keep only the newest pending draw. */ while(channbrecv(drawc, &old) > 0) ; @@ -200,7 +210,6 @@ reset(void) sclear(&search.text); memset(&caret, 0, sizeof caret); clearkouho(); - show(); } static void @@ -227,7 +236,6 @@ dictqmap(Im *im) Str dict; clearkouho(); - show(); if(im->l->map == nil) return; if(!mapget(im->l->map, &im->pre, &dict)) @@ -241,7 +249,6 @@ dictqjp(Im *im) Str reading; clearkouho(); - show(); if(im->l->dict == nil || !jpreading(im, &reading) || reading.n == 0) return; dictsend(im, &reading); @@ -260,19 +267,25 @@ setlang(int c) } static void -commit(Str *com) +commitim(Im *p, Str *com) { Str val; - if(isjp(&im)){ - jpreading(&im, &val); + if(isjp(p)){ + jpreading(p, &val); sappend(com, &val); - }else if(im.l->map != nil && mapget(im.l->map, &im.pre, &val)) + }else if(p->l->map != nil && mapget(p->l->map, &p->pre, &val)) sappend(com, &val); else - sappend(com, &im.pre); - sclear(&im.pre); - sclear(&im.raw); + sappend(com, &p->pre); + sclear(&p->pre); + sclear(&p->raw); +} + +static void +commit(Str *com) +{ + commitim(&im, com); } static void @@ -302,43 +315,39 @@ keeptail(Str *s) } static int -dotransjp(Rune c, Str *com) +transjp(Im *p, 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); + 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 = im.raw; + key = p->raw; if(key.n < Maxrunes) sputr(&key, c); sclear(&mapped); - if(maplookup(im.l->map, &key, &mapped)){ - im.raw = key; + if(maplookup(p->l->map, &key, &mapped)){ + p->raw = key; if(issmalltsu(&mapped)){ - sappend(&im.pre, &mapped); - keeptail(&im.raw); + sappend(&p->pre, &mapped); + keeptail(&p->raw); } - dictqjp(&im); return 1; } - if(im.raw.n != 0 && mapget(im.l->map, &im.raw, &mapped)){ - sappend(&im.pre, &mapped); - sclear(&im.raw); + 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(im.l->map, &key, &mapped)){ - im.raw = key; - dictqjp(&im); + if(maplookup(p->l->map, &key, &mapped)){ + p->raw = key; return 1; } } - commit(com); - clearkouho(); + commitim(p, com); return 0; } @@ -348,8 +357,14 @@ dotrans(Rune c, Str *com) Emit e; Str mapped; - if(isjp(&im)) - return dotransjp(c, com); + 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) @@ -470,7 +485,7 @@ foldascii(Str *s) static void transstr(Lang *l, Str *raw, Str *out) { - static Im q; + Im q; Emit e; Str mapped; int i; @@ -482,6 +497,14 @@ transstr(Lang *l, Str *raw, Str *out) } 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); @@ -546,7 +569,6 @@ emojiquery(void) } search.text = rawhit || !localhit ? raw : local; selectfirst(); - show(); } static void @@ -562,7 +584,6 @@ hanjaquery(void) setkouho(&res); }else clearkouho(); - show(); } static void @@ -591,7 +612,6 @@ endsearch(void) sclear(&search.raw); sclear(&search.text); clearkouho(); - show(); } static void @@ -605,7 +625,6 @@ startsearch(int lang, Str *com) search.lang = lang; sclear(&search.raw); sclear(&search.text); - show(); } static void @@ -638,7 +657,6 @@ searchkey(u32int ks, u32int mod, Str *com) if(im.nkouho == 0) return 1; movekouho(n); - show(); return 1; } n = numberedkouho(ks, mod); @@ -658,7 +676,6 @@ searchkey(u32int ks, u32int mod, Str *com) im.sel = 0; else im.sel++; - show(); return 1; } if(ks == Kret){ @@ -712,7 +729,7 @@ searchkey(u32int ks, u32int mod, Str *com) } static int -keystroke(u32int ks, u32int mod, Str *com) +transition(u32int ks, u32int mod, Str *com) { int n; Rune c; @@ -726,7 +743,6 @@ keystroke(u32int ks, u32int mod, Str *com) if(im.nkouho == 0) return 0; movekouho(n); - show(); return 1; } n = numberedkouho(ks, mod); @@ -736,7 +752,7 @@ keystroke(u32int ks, u32int mod, Str *com) return 1; } if(ks == Ktab || ks == Kret){ - if(im.sel >= 0 && im.sel < im.nkouho){ + if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho){ sappend(com, &im.kouho[im.sel]); reset(); return 1; @@ -779,12 +795,14 @@ keystroke(u32int ks, u32int mod, Str *com) return 1; } if(getlang(c) != nil){ - if(isjp(&im) && haspre(&im)) + if(haspre(&im)) commit(com); reset(); setlang(c); return 1; } + if(haspre(&im)) + commit(com); reset(); return 0; } @@ -814,10 +832,33 @@ keystroke(u32int ks, u32int mod, Str *com) if(im.l->lang == LangKO) ks = kokey(ks, mod); n = dotrans(ks, com); - show(); return n; } +static int +keystrokeforce(u32int ks, u32int mod, Str *com, int force) +{ + Im oldim; + Search oldsearch; + int eaten, ncom; + + oldim = im; + oldsearch = search; + ncom = com->n; + eaten = transition(ks, mod, com); + if(force || eaten || com->n != ncom || + memcmp(&oldim, &im, sizeof im) != 0 || + memcmp(&oldsearch, &search, sizeof search) != 0) + redraw(); + return eaten; +} + +static int +keystroke(u32int ks, u32int mod, Str *com) +{ + return keystrokeforce(ks, mod, com, 0); +} + static void init(void) { @@ -829,6 +870,7 @@ init(void) activeowner = nil; activecap = 0; dictseq = 0; + candidatechosen = 0; visible = 0; } @@ -854,6 +896,7 @@ imhandlekey(Keyreq *kr) { Keyres res; void *oldowner; + int changed; oldowner = activeowner; sclear(&res.commit); @@ -865,16 +908,19 @@ imhandlekey(Keyreq *kr) reset(); activeowner = nil; activecap = 0; + redraw(); } break; case Keyreset: - if(kr->owner == activeowner) + if(kr->owner == activeowner){ reset(); + redraw(); + } break; case Keycaret: if(kr->owner == activeowner){ caret = kr->caret; - show(); + redraw(); } break; case Keycap: @@ -882,24 +928,33 @@ imhandlekey(Keyreq *kr) if(activeowner != nil && kr->owner == activeowner){ res.eaten = 1; if(setactivecap(kr->cap)) - show(); + redraw(); } break; case Keypress: res.eaten = 0; + changed = 0; if(activeowner != nil && kr->owner == activeowner && setactivecap(kr->cap)) - show(); + changed = 1; if(meaningful(kr)){ if(kr->owner != activeowner){ reset(); activeowner = kr->owner; activecap = kr->cap & Cclientpreedit; caret = kr->caret; - }else if(kr->caret.valid) + changed = 1; + }else if(kr->caret.valid){ caret = kr->caret; - res.eaten = keystroke(kr->ks, kr->mod, &res.commit); - } + 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) @@ -923,7 +978,7 @@ dictresult(Dictres *res) scmp(&res->key, &pre) != 0) return; setkouho(res); - show(); + redraw(); } void diff --git a/tests/engine_test.c b/tests/engine_test.c index 8d17391..f2f41da 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -8,6 +8,7 @@ int checkstr(struct ct*, char*, char*, Str*); Str shownpre(Im*); static int typekeys(struct ct*, char*, Str*); +static int draindraw(Drawcmd*); void testengineinit(int lang) @@ -417,7 +418,8 @@ engine_commit_contract(struct ct *t) im.kouho[0] = mkstr("c1"); im.kouho[1] = mkstr("c2"); im.nkouho = 2; - im.sel = 1; + im.sel = 0; + CT_CHECK(t, keystroke(Kdown, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com)); checkstr(t, "selected candidate", "c2", &com); CT_EQ_INT(t, 0, im.pre.n); @@ -484,6 +486,7 @@ engine_korean_modifiers_and_backspace(struct ct *t) { 'w', "ㅈ", "ㅉ" }, { 'o', "ㅐ", "ㅒ" }, { 'p', "ㅔ", "ㅖ" }, }; + Drawcmd dc; Str com; int i; @@ -518,8 +521,11 @@ engine_korean_modifiers_and_backspace(struct ct *t) sclear(&com); if(typekeys(t, "rkr", &com)){ checkstr(t, "Korean engine preedit", "각", &im.pre); + draindraw(nil); CT_CHECK(t, keystroke(Kback, 0, &com)); checkstr(t, "Korean final backspace", "가", &im.pre); + if(CT_CHECK(t, draindraw(&dc) == 1)) + checkstr(t, "Korean backspace redraw", "가", &dc.pre); CT_CHECK(t, keystroke(Kback, 0, &com)); checkstr(t, "Korean vowel backspace", "ㄱ", &im.pre); CT_CHECK(t, keystroke(Kback, 0, &com)); @@ -578,10 +584,10 @@ engine_language_switch_state(struct ct *t) { "select Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 }, { "Vietnamese a", 'a', 0, LangVI, "a", "a", "", 0 }, { "Vietnamese tone", 's', 0, LangVI, "as", "as", "", 0 }, - { "switch to Korean", 's', Mctrl, LangKO, "", "", "", 1 }, + { "switch to Korean", 's', Mctrl, LangKO, "", "", "á", 1 }, { "Korean initial", 'r', 0, LangKO, "ㄱ", "", "", 0 }, { "Korean syllable", 'k', 0, LangKO, "가", "", "", 0 }, - { "switch to Vietnamese", 'v', Mctrl, LangVI, "", "", "", 0 }, + { "switch to Vietnamese", 'v', Mctrl, LangVI, "", "", "가", 0 }, { "Vietnamese a again", 'a', 0, LangVI, "a", "a", "", 0 }, { "Vietnamese tone again", 's', 0, LangVI, "as", "as", "", 0 }, { "commit Vietnamese", Kret, 0, LangVI, "", "", "á", 0 }, @@ -632,6 +638,24 @@ engine_language_switch_state(struct ct *t) break; } } + + init(); + im.l = getlang(LangKO); + sclear(&com); + if(typekeys(t, "rk", &com)){ + CT_CHECK(t, !keystroke('p', Mctrl, &com)); + checkstr(t, "Korean Ctrl passthrough", "가", &com); + CT_EQ_INT(t, 0, im.pre.n); + } + init(); + im.l = &testvi; + sclear(&com); + if(typekeys(t, "as", &com)){ + CT_CHECK(t, !keystroke('p', Mctrl, &com)); + checkstr(t, "Vietnamese Ctrl passthrough", "á", &com); + CT_EQ_INT(t, 0, im.pre.n); + CT_EQ_INT(t, 0, im.raw.n); + } vi->map = saved; } @@ -644,6 +668,7 @@ struct Searchfix Hmap *dict; int activecap; int visible; + int candidatechosen; }; static int @@ -691,7 +716,7 @@ checkcandidatepage(struct ct *t, char *where, int first, int n, int sel) Drawcmd dc; char want[8]; - show(); + redraw(); if(!CT_CHECK(t, draindraw(&dc) > 0)) return 0; if(!CT_EQ_INT(t, n, dc.nkouho) || !CT_EQ_INT(t, sel, dc.sel) || @@ -732,10 +757,10 @@ engine_candidate_page_metadata(struct ct *t) /* A clearing draw is the observable zero-candidate snapshot. */ candidatebegin(1); - show(); + redraw(); draindraw(nil); setcandidates(0); - show(); + redraw(); if(CT_CHECK(t, draindraw(&dc) > 0)){ CT_EQ_INT(t, 0, dc.nkouho); CT_EQ_INT(t, -1, dc.sel); @@ -761,7 +786,7 @@ engine_candidate_page_snapshots(struct ct *t) candidatebegin(totals[i]); CT_EQ_INT(t, totals[i] == 0 ? -1 : 0, im.sel); if(totals[i] == 0){ - show(); + redraw(); CT_EQ_INT(t, 0, draindraw(nil)); continue; } @@ -815,17 +840,39 @@ engine_candidate_page_movement(struct ct *t) void engine_candidate_completion(struct ct *t) { + static Rune keys[] = { Kret, Ktab }; + Dictres res; Str com; + int arrived, i; + + for(i = 0; i < nelem(keys); i++) + for(arrived = 0; arrived < 2; arrived++){ + init(); + im.l = getlang(LangJP); + im.pre = mkstr("reading"); + memset(&res, 0, sizeof res); + res.key = im.pre; + res.lang = LangJP; + res.seq = dictseq; + res.kouho[0] = mkstr("candidate"); + res.nkouho = 1; + if(arrived) + dictresult(&res); + sclear(&com); + CT_CHECK(t, keystroke(keys[i], 0, &com)); + if(!arrived) + dictresult(&res); + checkstr(t, "unselected candidate commits reading", + "reading", &com); + CT_EQ_INT(t, 0, im.nkouho); + } candidatebegin(2); + im.pre = mkstr("reading"); sclear(&com); + CT_CHECK(t, keystroke(Kdown, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com)); - checkstr(t, "default candidate", "c1", &com); - - candidatebegin(2); - sclear(&com); - CT_CHECK(t, keystroke(Ktab, 0, &com)); - checkstr(t, "ordinary Tab candidate", "c1", &com); + checkstr(t, "explicit candidate", "c2", &com); candidatebegin(2); im.pre = mkstr("reading"); @@ -866,6 +913,7 @@ searchsave(Searchfix *f, int lang) f->dict = f->dictlang->dict; f->activecap = activecap; f->visible = visible; + f->candidatechosen = candidatechosen; draindraw(nil); } @@ -908,6 +956,7 @@ searchend(Searchfix *f) search = f->search; activecap = f->activecap; visible = f->visible; + candidatechosen = f->candidatechosen; } void @@ -931,7 +980,7 @@ engine_popup_preedit_capability(struct ct *t) res.nkouho = 1; dictresult(&res); im.sel = 0; - show(); + redraw(); if(CT_CHECK(t, draindraw(&dc) > 0)){ CT_EQ_INT(t, 0, dc.pre.n); CT_EQ_INT(t, 1, dc.nkouho); @@ -1018,7 +1067,15 @@ engine_japanese_readings(struct ct *t) { "nnya", "んにゃ" }, { "kin'youbi", "きんようび" }, }; - Str com, shown; + static const struct { + char *raw; + char *want; + } replay[] = { + { "kitte", "きって" }, + { "kanj", "かんj" }, + { "n'ya", "んや" }, + }; + Str com, raw, shown; int i; for(i = 0; i < nelem(cases); i++){ @@ -1033,6 +1090,11 @@ engine_japanese_readings(struct ct *t) CT_CHECK(t, keystroke(Kret, 0, &com)); checkstr(t, "committed reading", cases[i].want, &com); } + for(i = 0; i < nelem(replay); i++){ + raw = mkstr(replay[i].raw); + transstr(getlang(LangJP), &raw, &shown); + checkstr(t, replay[i].raw, replay[i].want, &shown); + } } static int @@ -1194,7 +1256,7 @@ engine_emoji_single_candidate(struct ct *t) checkstr(t, "explicit candidate commit", "α", &com); CT_CHECK(t, !search.lang); CT_EQ_INT(t, LangKO, im.l->lang); - show(); + redraw(); CT_CHECK(t, draindraw(&dc) > 0); CT_EQ_INT(t, 0, dc.pre.n); CT_EQ_INT(t, 0, dc.nkouho);