engine: the chosen candidate is what the client shows

The preedit sent back to the owner was always impre(), the reading, so
かく stayed underlined in the application while the popup showed 書く and
Enter inserted 書く.  Nothing was hidden -- the popup had it -- but the
text did not stand where it was going to land, and mozc, fcitx5 and kime
all put the candidate inline.

Only the reply changes.  impre() still answers the reading everywhere it
is the reading that is wanted: commitim, the okurigana mark, the seed
Ctrl+H takes, and snapshot, so a popup that draws the preedit itself
keeps the reading above the list where it says something the highlighted
row does not.  Consulting im.sel here is safe where consulting it inside
impre() would not be: transstr memsets a local Im, whose sel is 0 and not
-1, and this reply only ever reads the engine's own.

Nothing in the suite pinned the old answer, so the test is new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 16:25:55 +09:00
parent 761428dbc4
commit 0f77e0e9ac
3 changed files with 34 additions and 10 deletions

View File

@@ -33,16 +33,17 @@ Japanese composes a whole reading and then offers its Kanji with none
chosen. `Space` and `Tab` step through the candidates and wrap (`Shift` chosen. `Space` and `Tab` step through the candidates and wrap (`Shift`
reverses), the reading in Katakana last, so a word the dictionary lacks reverses), the reading in Katakana last, so a word the dictionary lacks
converts with one `Space`; `Up`/`Down` and `PageUp`/`PageDown` move without converts with one `Space`; `Up`/`Down` and `PageUp`/`PageDown` move without
wrapping. `Enter` commits the chosen candidate, or the reading, as `0` and wrapping. A chosen candidate takes the reading's place in the preedit, so
typing on always do. Once a candidate is chosen `1`-`9` take that row of it stands where it will land, and `Enter` commits it, or the reading, as
the page shown; before that the digits type. `Backspace` deletes the last `0` and typing on always do. Once a candidate is chosen `1`-`9` take that
kana shown, and romaji that made no kana stays as typed until it is mended; row of the page shown; before that the digits type. `Backspace` deletes
`Esc` cancels the reading, and on a chosen candidate both go back to it. the last kana shown, and romaji that made no kana stays as typed until it
The dictionary keys verbs and adjectives by stem and okurigana, so `kaku` is mended; `Esc` cancels the reading, and on a chosen candidate both go
offers かく's readings and then 書く and its kin — typing the okurigana back to it. The dictionary keys verbs and adjectives by stem and
with `Shift`, as SKK does, `kaKu`, asks for that split first. Katakana okurigana, so `kaku` offers かく's readings and then 書く and its kin —
mode does not convert, so there `Space` and `Tab` commit the reading and typing the okurigana with `Shift`, as SKK does, `kaKu`, asks for that
go on to the application. split first. Katakana mode does not convert, so there `Space` and `Tab`
commit the reading and go on to the application.
Korean composes one syllable at a time. `Enter`, `Tab`, `Esc` and any key Korean composes one syllable at a time. `Enter`, `Tab`, `Esc` and any key
that is not a jamo commit the syllable and go on to the application, so that is not a jamo commit the syllable and go on to the application, so

View File

@@ -1097,6 +1097,8 @@ imhandlekey(Keyreq *kr)
if(kr->owner == activeowner){ if(kr->owner == activeowner){
if(search.lang) if(search.lang)
searchpre(&res.preedit); searchpre(&res.preedit);
else if(im.sel >= 0)
res.preedit = im.kouho[im.sel]; /* what Enter would commit */
else else
impre(&im, &res.preedit); impre(&im, &res.preedit);
} }

View File

@@ -411,6 +411,7 @@ engine_commit_contract(struct ct *t)
{ 'd', "", "ㅇㅇㅇ" }, { 'd', "", "ㅇㅇㅇ" },
{ 'k', "", "ㅏㅏㅏ" }, { 'k', "", "ㅏㅏㅏ" },
}; };
Drawcmd dc;
Keyres res; Keyres res;
Str all, com; Str all, com;
char owner; char owner;
@@ -495,6 +496,26 @@ engine_commit_contract(struct ct *t)
CT_CHECK(t, !keystroke(0xf008, 0, &com)); CT_CHECK(t, !keystroke(0xf008, 0, &com));
checkstr(t, "non-ASCII key commit", "", &com); checkstr(t, "non-ASCII key commit", "", &com);
CT_EQ_INT(t, 0, im.pre.n); CT_EQ_INT(t, 0, im.pre.n);
/* A chosen candidate is what the client shows and what Enter
* commits; the popup keeps the reading above the list. */
init();
im.l = getlang(LangJP);
draindraw(nil);
ownerrequestcap(&owner, 1, Keypress, 'k', 0);
res = ownerrequestcap(&owner, 1, Keypress, 'a', 0);
checkstr(t, "the reading before a candidate", "", &res.preedit);
im.kouho[0] = mkstr("");
im.nkouho = 1;
res = ownerrequestcap(&owner, 1, Keypress, Kdown, 0);
checkstr(t, "the candidate the client shows", "", &res.preedit);
clientpre = 0;
redraw();
if(CT_CHECK(t, draindraw(&dc) > 0))
checkstr(t, "the popup keeps the reading", "", &dc.pre);
res = ownerrequestcap(&owner, 1, Keypress, Kret, 0);
checkstr(t, "Enter commits what was shown", "", &res.commit);
CT_EQ_INT(t, 0, res.preedit.n);
} }
void void