engine: Space offers the reading in Katakana last; 0 always commits the reading

Every Japanese IME converts a reading to Katakana on demand; strans made
the user switch to the Katakana mode and type again.  Space now adds
the reading in Katakana after the dictionary candidates, so a loanword
the dictionary lacks converts with one Space and Enter, and 漢字 users
still get 漢字 first.

0 committed the reading only while candidates showed; without them it
typed a 0 as well.  It now commits any Japanese reading, the counterpart
of 1-9 picking candidates, as the README always said.
This commit is contained in:
2026-08-17 01:18:33 +09:00
parent ab0366b7f7
commit f0498868eb
3 changed files with 81 additions and 42 deletions

View File

@@ -26,8 +26,9 @@ unmodified `1`-`9` commits that row of the current page. `Enter` and `Tab`
commit the chosen candidate, or the reading when none is chosen, as `0` commit the chosen candidate, or the reading when none is chosen, as `0`
always does; so does typing on, or any key that ends the composition. always does; so does typing on, or any key that ends the composition.
Japanese consumes the confirming key; Korean and Vietnamese pass it on to Japanese consumes the confirming key; Korean and Vietnamese pass it on to
the application. `Space` on a reading without candidates commits it and the application. `Space` adds the reading in Katakana as the last
types nothing. `Backspace` and `Esc` on a chosen candidate go back to the candidate, so a word the dictionary lacks converts to Katakana with one
`Space`. `Backspace` and `Esc` on a chosen candidate go back to the
reading. In a temporary Emoji or Hanja search, `Enter` commits the reading. In a temporary Emoji or Hanja search, `Enter` commits the
highlighted result and `Tab`/`Shift-Tab` wrap through the results. Leaving highlighted result and `Tab`/`Shift-Tab` wrap through the results. Leaving
the field or clicking elsewhere commits what is pending. `Esc` cancels a the field or clicking elsewhere commits what is pending. `Esc` cancels a

View File

@@ -313,6 +313,23 @@ dictqjp(void)
dictqokuri(&reading); dictqokuri(&reading);
} }
/* The reading in Katakana is the last candidate, as in every IME. */
static void
katakouho(void)
{
Str reading, k;
int i;
if(!jpreading(im.l->map, &im.pre, &im.raw, &reading))
return;
k = reading;
for(i = 0; i < k.n; i++)
if(k.r[i] >= L'' && k.r[i] <= L'')
k.r[i] += L'' - L'';
if(scmp(&k, &reading) != 0)
addkouho(&k);
}
static int static int
setlang(int c) setlang(int c)
{ {
@@ -755,8 +772,9 @@ transition(u32int ks, u32int mod, Str *com)
return 1; return 1;
} }
if(ks == ' ' && isjp(&im) && haspre(&im) && !(mod & ~Mshift)){ if(ks == ' ' && isjp(&im) && haspre(&im) && !(mod & ~Mshift)){
/* Space converts: it steps through the candidates, or /* Space converts: it steps through the candidates, Katakana
* commits a reading that has none. */ * last; a reading with none is committed. */
katakouho();
if(im.nkouho != 0) if(im.nkouho != 0)
cyclekouho(mod & Mshift ? -1 : 1); cyclekouho(mod & Mshift ? -1 : 1);
else else
@@ -811,8 +829,8 @@ transition(u32int ks, u32int mod, Str *com)
flush(com); flush(com);
return setlang(c); return setlang(c);
} }
if(ks == '0' && im.nkouho > 0){ if(ks == '0' && isjp(&im) && haspre(&im)){
im.sel = -1; im.sel = -1; /* 0 commits the reading, as 1-9 pick candidates */
flush(com); flush(com);
return 1; return 1;
} }

View File

@@ -1146,12 +1146,14 @@ engine_japanese_candidates(struct ct *t)
CT_CHECK(t, keystroke(' ', 0, &com)); CT_CHECK(t, keystroke(' ', 0, &com));
CT_EQ_INT(t, 1, im.sel); CT_EQ_INT(t, 1, im.sel);
CT_CHECK(t, keystroke(' ', 0, &com)); CT_CHECK(t, keystroke(' ', 0, &com));
CT_EQ_INT(t, 2, im.sel);
CT_CHECK(t, keystroke(' ', 0, &com));
CT_EQ_INT(t, 0, im.sel); CT_EQ_INT(t, 0, im.sel);
CT_CHECK(t, keystroke(' ', Mshift, &com)); CT_CHECK(t, keystroke(' ', Mshift, &com));
CT_EQ_INT(t, 1, im.sel); CT_EQ_INT(t, 2, im.sel);
CT_CHECK(t, keystroke(Kback, 0, &com)); CT_CHECK(t, keystroke(Kback, 0, &com));
CT_EQ_INT(t, -1, im.sel); CT_EQ_INT(t, -1, im.sel);
CT_EQ_INT(t, 2, im.nkouho); CT_EQ_INT(t, 3, im.nkouho);
CT_CHECK(t, keystroke(' ', 0, &com)); CT_CHECK(t, keystroke(' ', 0, &com));
CT_CHECK(t, keystroke('w', 0, &com)); CT_CHECK(t, keystroke('w', 0, &com));
CT_CHECK(t, keystroke('o', 0, &com)); CT_CHECK(t, keystroke('o', 0, &com));
@@ -1160,45 +1162,22 @@ engine_japanese_candidates(struct ct *t)
checkstr(t, "reading after the candidate", "", &shown); checkstr(t, "reading after the candidate", "", &shown);
sclear(&com); sclear(&com);
CT_CHECK(t, keystroke(' ', 0, &com)); CT_CHECK(t, keystroke(' ', 0, &com));
checkstr(t, "Space commits a reading without candidates", "", &com); if(CT_EQ_INT(t, 1, im.nkouho))
CT_EQ_INT(t, 0, im.pre.n); checkstr(t, "Space offers Katakana without candidates", "",
CT_EQ_INT(t, 0, im.raw.n); &im.kouho[0]);
CT_EQ_INT(t, 0, im.sel);
CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "Katakana committed", "", &com);
CT_CHECK(t, !keystroke(' ', 0, &com)); CT_CHECK(t, !keystroke(' ', 0, &com));
sclear(&com); sclear(&com);
if(!typekeys(t, "kanji", &com)) if(!typekeys(t, "kanji", &com))
goto cleanup; goto cleanup;
CT_CHECK(t, keystroke(' ', 0, &com));
CT_CHECK(t, keystroke(Kesc, 0, &com));
CT_EQ_INT(t, -1, im.sel);
impre(&im, &shown);
checkstr(t, "Escape goes back to the reading", "かんじ", &shown);
CT_CHECK(t, keystroke(Kesc, 0, &com));
CT_EQ_INT(t, 0, im.pre.n);
CT_EQ_INT(t, 0, im.raw.n);
CT_EQ_INT(t, 0, im.nkouho);
sclear(&com);
if(!typekeys(t, "kanji", &com))
goto cleanup;
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_CHECK(t, keystroke('s', Mctrl, &com));
checkstr(t, "language switch takes the candidate", "漢字", &com);
CT_EQ_INT(t, LangKO, im.l->lang);
im.l = jp;
/* A romaji typo stays in the reading for Backspace to mend. */
sclear(&com);
if(!typekeys(t, "kanjr", &com))
goto cleanup;
impre(&im, &shown);
checkstr(t, "typo kept", "かんjr", &shown);
CT_CHECK(t, keystroke(Kback, 0, &com));
impre(&im, &shown);
checkstr(t, "typo half mended", "かんj", &shown);
CT_CHECK(t, keystroke(Kback, 0, &com));
if(!typekeys(t, "ji", &com))
goto cleanup;
impre(&im, &shown);
checkstr(t, "typo mended", "かんじ", &shown);
CT_EQ_INT(t, 2, im.nkouho); CT_EQ_INT(t, 2, im.nkouho);
CT_CHECK(t, keystroke(' ', 0, &com));
if(CT_EQ_INT(t, 3, im.nkouho))
checkstr(t, "Katakana after the candidates", "カンジ",
&im.kouho[2]);
CT_CHECK(t, keystroke(Kesc, 0, &com));
CT_CHECK(t, keystroke(Kesc, 0, &com)); CT_CHECK(t, keystroke(Kesc, 0, &com));
/* SKK's okuri-ari entries convert verbs: stem plus okurigana. */ /* SKK's okuri-ari entries convert verbs: stem plus okurigana. */
setdict(jp->dict, "かk", "書 描"); setdict(jp->dict, "かk", "書 描");
@@ -1226,6 +1205,47 @@ engine_japanese_candidates(struct ct *t)
checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]); checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]);
} }
CT_CHECK(t, keystroke(Kesc, 0, &com)); CT_CHECK(t, keystroke(Kesc, 0, &com));
/* A romaji typo stays in the reading for Backspace to mend. */
if(!typekeys(t, "kanjr", &com))
goto cleanup;
impre(&im, &shown);
checkstr(t, "typo kept", "かんjr", &shown);
CT_CHECK(t, keystroke(Kback, 0, &com));
impre(&im, &shown);
checkstr(t, "typo half mended", "かんj", &shown);
CT_CHECK(t, keystroke(Kback, 0, &com));
if(!typekeys(t, "ji", &com))
goto cleanup;
impre(&im, &shown);
checkstr(t, "typo mended", "かんじ", &shown);
CT_EQ_INT(t, 2, im.nkouho);
CT_CHECK(t, keystroke('0', 0, &com));
checkstr(t, "0 commits the reading", "かんじ", &com);
sclear(&com);
if(!typekeys(t, "wo", &com))
goto cleanup;
CT_CHECK(t, keystroke('0', 0, &com));
checkstr(t, "0 commits a reading without candidates", "", &com);
sclear(&com);
if(!typekeys(t, "kanji", &com))
goto cleanup;
CT_CHECK(t, keystroke(' ', 0, &com));
CT_CHECK(t, keystroke(Kesc, 0, &com));
CT_EQ_INT(t, -1, im.sel);
impre(&im, &shown);
checkstr(t, "Escape goes back to the reading", "かんじ", &shown);
CT_CHECK(t, keystroke(Kesc, 0, &com));
CT_EQ_INT(t, 0, im.pre.n);
CT_EQ_INT(t, 0, im.raw.n);
CT_EQ_INT(t, 0, im.nkouho);
sclear(&com);
if(!typekeys(t, "kanji", &com))
goto cleanup;
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_CHECK(t, keystroke('s', Mctrl, &com));
checkstr(t, "language switch takes the candidate", "漢字", &com);
CT_EQ_INT(t, LangKO, im.l->lang);
im.l = jp;
/* Backspace looks a complete shorter reading up again. */ /* Backspace looks a complete shorter reading up again. */
setdict(jp->dict, "かん", ""); setdict(jp->dict, "かん", "");
if(!typekeys(t, "kanji", &com)) if(!typekeys(t, "kanji", &com))