engine: Shift on the okurigana asks for that reading, as SKK does

kaku offers the twenty-two readings of かく before 書く, because the SKK
dictionary keys a verb by its stem and gives no frequency to rank the two
lists by.  SKK's own answer is the shift key: kaKu says where the
okurigana starts, and that split now comes first — the reading's own
candidates still follow it.  Caps Lock sends no Shift, so it marks
nothing, and a word typed without Shift is unchanged.
This commit is contained in:
2026-08-17 12:53:12 +09:00
parent 2e53627b7d
commit ae7dbae993
3 changed files with 43 additions and 9 deletions

View File

@@ -39,6 +39,10 @@ current page; before that the digits type. `Enter` commits the chosen candidate,
reading when none is chosen, as `0` always does; so does typing on, or any reading when none is chosen, as `0` always does; so does typing on, or any
key that ends the composition. `Backspace` deletes the last kana shown; key that ends the composition. `Backspace` deletes the last kana shown;
romaji that made no kana stays in the reading as typed until it is mended. romaji that made no kana stays in the reading as typed until it is mended.
The dictionary keys a verb or adjective by its stem, so `kaku` offers the
readings of かく and then 書く and its kin; typing the okurigana with
`Shift`, as SKK does — `kaKu` — asks for that split first. Caps Lock is
not `Shift` and marks nothing.
`Backspace` and `Esc` on a chosen candidate go back to the reading, and `Backspace` and `Esc` on a chosen candidate go back to the reading, and
`Esc` on the reading cancels it. Katakana mode does not perform Kanji `Esc` on the reading cancels it. Katakana mode does not perform Kanji
conversion. conversion.

View File

@@ -8,6 +8,7 @@ static Str lost;
static int clientpre; /* the owner draws its own preedit */ static int clientpre; /* the owner draws its own preedit */
static Caret caret; static Caret caret;
static Rune modemark; /* the mode the last key switched to */ static Rune modemark; /* the mode the last key switched to */
static int okuriat = -1; /* where Shift marked the okurigana, or -1 */
static Drawcmd lastdraw; static Drawcmd lastdraw;
static Emit transjp(Im*, Rune); static Emit transjp(Im*, Rune);
static void backjp(Im*); static void backjp(Im*);
@@ -279,21 +280,22 @@ okuriletter(Rune r)
/* /*
* SKK keys a verb or adjective by its stem and the letter of the * SKK keys a verb or adjective by its stem and the letter of the
* okurigana that follows (かk: 書, 描, ...). Every split of the reading * okurigana that follows (かk: 書, 描, ...). The split the user marked
* is tried, longest stem first, and the okurigana is put back; a っ * is tried alone; otherwise every split is, longest stem first. The
* ending the stem (いっt: 言, 行) is written in kana too. * okurigana is put back, and a っ ending the stem (いっt: 言, 行) is
* written in kana too.
*/ */
static void static void
dictqokuri(Str *reading) dictqokuri(Str *reading, int split)
{ {
Str key, okuri, kouho[Maxkouho]; Str key, okuri, kouho[Maxkouho];
Rune c; Rune c;
int i, j, n; int i, j, n;
for(i = reading->n - 1; i > 0; i--){ for(i = split >= 0 ? split : reading->n - 1; i > 0; i--){
c = okuriletter(reading->r[i]); c = okuriletter(reading->r[i]);
if(c == 0) if(c == 0)
continue; break;
key = *reading; key = *reading;
key.n = i; key.n = i;
sputr(&key, c); sputr(&key, c);
@@ -305,6 +307,8 @@ dictqokuri(Str *reading)
sappend(&kouho[j], &okuri); sappend(&kouho[j], &okuri);
addkouho(&kouho[j]); addkouho(&kouho[j]);
} }
if(split >= 0)
break;
} }
} }
@@ -312,14 +316,20 @@ dictqokuri(Str *reading)
static void static void
dictqjp(void) dictqjp(void)
{ {
Str reading; Str reading, exact[Maxkouho];
int i, n;
clearkouho(); clearkouho();
if(!isjp(&im) || !jpreading(im.l->map, &im.pre, &im.raw, &reading) || if(!isjp(&im) || !jpreading(im.l->map, &im.pre, &im.raw, &reading) ||
reading.n == 0) reading.n == 0)
return; return;
im.nkouho = dictlookup(im.l->dict, &reading, im.kouho, Maxkouho); /* What Shift marked comes first; the dictionary's own readings next. */
dictqokuri(&reading); if(okuriat > 0 && okuriat < reading.n)
dictqokuri(&reading, okuriat);
n = dictlookup(im.l->dict, &reading, exact, Maxkouho);
for(i = 0; i < n; i++)
addkouho(&exact[i]);
dictqokuri(&reading, -1);
} }
/* The reading in Katakana is the last candidate, as in every IME. */ /* The reading in Katakana is the last candidate, as in every IME. */
@@ -663,6 +673,7 @@ reset(void)
{ {
sclear(&im.pre); sclear(&im.pre);
sclear(&im.raw); sclear(&im.raw);
okuriat = -1;
endsearch(); endsearch();
} }
@@ -798,10 +809,16 @@ searchkey(u32int ks, u32int mod, Str *com)
static int static int
transition(u32int ks, u32int mod, Str *com) transition(u32int ks, u32int mod, Str *com)
{ {
Str mark;
int n; int n;
Rune c; Rune c;
modemark = 0; /* any key after the switch takes its mark away */ modemark = 0; /* any key after the switch takes its mark away */
/* Shift on a romaji letter marks the okurigana, as SKK's capital does. */
if(isjp(&im) && (mod & Mshift) && ks >= 'A' && ks <= 'Z' && haspre(&im)){
impre(&im, &mark);
okuriat = mark.n;
}
if(im.l->lang == LangKO) if(im.l->lang == LangKO)
ks = kokey(ks, mod); ks = kokey(ks, mod);
else if(isjp(&im)) else if(isjp(&im))
@@ -917,6 +934,7 @@ init(void)
memset(&search, 0, sizeof search); memset(&search, 0, sizeof search);
memset(&caret, 0, sizeof caret); memset(&caret, 0, sizeof caret);
modemark = 0; modemark = 0;
okuriat = -1;
activeowner = nil; activeowner = nil;
lostowner = nil; lostowner = nil;
clientpre = 0; clientpre = 0;

View File

@@ -1223,6 +1223,18 @@ engine_japanese_candidates(struct ct *t)
if(CT_EQ_INT(t, 1, im.nkouho)) if(CT_EQ_INT(t, 1, im.nkouho))
checkstr(t, "two-kana okurigana", "大きい", &im.kouho[0]); checkstr(t, "two-kana okurigana", "大きい", &im.kouho[0]);
CT_CHECK(t, keystroke(Kesc, 0, &com)); CT_CHECK(t, keystroke(Kesc, 0, &com));
/* Shift on the okurigana asks for that split alone, first. */
sclear(&com);
if(!typekeys(t, "ka", &com))
goto cleanup;
CT_CHECK(t, keystroke('K', Mshift, &com));
CT_CHECK(t, keystroke('u', 0, &com));
if(CT_EQ_INT(t, 3, im.nkouho)){
checkstr(t, "marked okurigana first", "書く", &im.kouho[0]);
checkstr(t, "then the second", "描く", &im.kouho[1]);
checkstr(t, "then the reading's own", "", &im.kouho[2]);
}
CT_CHECK(t, keystroke(Kesc, 0, &com));
setdict(jp->dict, "いt", "言 行"); setdict(jp->dict, "いt", "言 行");
setdict(jp->dict, "いっt", "言 行"); setdict(jp->dict, "いっt", "言 行");
if(!typekeys(t, "itta", &com)) if(!typekeys(t, "itta", &com))