From ebcec3af6b9f3509d05ff23ac59605d82896d6db Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 16 Aug 2026 15:57:55 +0900 Subject: [PATCH] fix(engine): send clients the composed Telex text, not the keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For Vietnamese the preedit is the map key ('as', 'oong'); only the popup and the commit path looked it up. Clients with inline preedit therefore showed 'as' while Enter committed 'á'. impre() now maps once for every reader, and snapshot() no longer maps a second time. --- strans.c | 15 +++++---------- tests/engine_test.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/test.h | 1 + tests/unit_test.c | 1 + 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/strans.c b/strans.c index 40ac3d0..04bfcfc 100644 --- a/strans.c +++ b/strans.c @@ -134,12 +134,13 @@ 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 + else if(!mapget(im.l->map, &im.pre, s)) *s = im.pre; } @@ -167,11 +168,8 @@ snapshot(Drawcmd *dc) 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; - } + if(!(activecap & Cclientpreedit)) + dc->pre = pre; first = pagefirst(); n = im.nkouho - first; if(n > Maxdisp) n = Maxdisp; @@ -220,10 +218,7 @@ dictsend(Im *im, Str *key) req.key = *key; req.lang = im->l->lang; req.seq = dictseq; - if(isjp(im)) - jpreading(im, &req.pre); - else - req.pre = im->pre; + impre(&req.pre); /* imthread is the sole producer; discard every obsolete queued lookup. */ while(channbrecv(dictreqc, &old) > 0) ; diff --git a/tests/engine_test.c b/tests/engine_test.c index be615ea..eb0b6eb 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -417,6 +417,51 @@ engine_active_owner_caret(struct ct *t) CT_CHECK(t, !caret.valid); } +void +engine_vietnamese_client_preedit(struct ct *t) +{ + static const struct { + char *keys; + char *shown; + } cases[] = { + { "as", "á" }, + { "aa", "â" }, + { "ddoong", "đông" }, + { "hoaf", "hoà" }, + { "quas", "quá" }, + }; + Drawcmd dc; + Keyres res; + Str com, shown; + char *k, owner; + int i; + + for(i = 0; i < nelem(cases); i++){ + init(); + im.l = &testvi; + draindraw(nil); + sclear(&com); + for(k = cases[i].keys; *k != '\0'; k++){ + res = ownerrequestcap(&owner, Cclientpreedit, + Keypress, *k, 0); + sappend(&com, &res.commit); + if(!res.eaten) + sputr(&com, *k); + } + /* What the client shows, what the popup shows, and what + * Enter commits are the same composed text. */ + shown = com; + sappend(&shown, &res.preedit); + checkstr(t, "client preedit", cases[i].shown, &shown); + activecap = 0; + redraw(); + if(CT_CHECK(t, draindraw(&dc) > 0)) + CT_EQ_INT(t, 0, scmp(&dc.pre, &res.preedit)); + CT_CHECK(t, keystroke(Kret, 0, &com)); + checkstr(t, "commit", cases[i].shown, &com); + } +} + void engine_commit_contract(struct ct *t) { diff --git a/tests/test.h b/tests/test.h index c4e54ad..06cc2e8 100644 --- a/tests/test.h +++ b/tests/test.h @@ -39,6 +39,7 @@ void engine_active_owner_reset(struct ct*); void engine_rejects_stale_dictionary_results(struct ct*); void engine_active_owner_caret(struct ct*); void engine_popup_preedit_capability(struct ct*); +void engine_vietnamese_client_preedit(struct ct*); void engine_commit_contract(struct ct*); void engine_language_switch_state(struct ct*); void engine_telex_history_bound(struct ct*); diff --git a/tests/unit_test.c b/tests/unit_test.c index 501e41c..e0555e4 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -94,6 +94,7 @@ static const struct ct_test tests[] = { { "engine/rejects-stale-dictionary", engine_rejects_stale_dictionary_results }, { "engine/active-owner-caret", engine_active_owner_caret }, { "engine/popup-preedit-capability", engine_popup_preedit_capability }, + { "engine/vietnamese-client-preedit", engine_vietnamese_client_preedit }, { "engine/commit-contract", engine_commit_contract }, { "engine/language-switch-state", engine_language_switch_state }, { "engine/telex-history-bound", engine_telex_history_bound },