From ae7dbae993f9eb7aaf07222a8734a0a5331458a2 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 17 Aug 2026 12:53:12 +0900 Subject: [PATCH] engine: Shift on the okurigana asks for that reading, as SKK does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 4 ++++ strans.c | 36 +++++++++++++++++++++++++++--------- tests/engine_test.c | 12 ++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7ad9818..08c9fd0 100644 --- a/README.md +++ b/README.md @@ -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 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. +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 `Esc` on the reading cancels it. Katakana mode does not perform Kanji conversion. diff --git a/strans.c b/strans.c index e059481..ad2b4c6 100644 --- a/strans.c +++ b/strans.c @@ -8,6 +8,7 @@ static Str lost; static int clientpre; /* the owner draws its own preedit */ static Caret caret; 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 Emit transjp(Im*, Rune); 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 - * okurigana that follows (かk: 書, 描, ...). Every split of the reading - * is tried, longest stem first, and the okurigana is put back; a っ - * ending the stem (いっt: 言, 行) is written in kana too. + * okurigana that follows (かk: 書, 描, ...). The split the user marked + * is tried alone; otherwise every split is, longest stem first. The + * okurigana is put back, and a っ ending the stem (いっt: 言, 行) is + * written in kana too. */ static void -dictqokuri(Str *reading) +dictqokuri(Str *reading, int split) { Str key, okuri, kouho[Maxkouho]; Rune c; 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]); if(c == 0) - continue; + break; key = *reading; key.n = i; sputr(&key, c); @@ -305,6 +307,8 @@ dictqokuri(Str *reading) sappend(&kouho[j], &okuri); addkouho(&kouho[j]); } + if(split >= 0) + break; } } @@ -312,14 +316,20 @@ dictqokuri(Str *reading) static void dictqjp(void) { - Str reading; + Str reading, exact[Maxkouho]; + int i, n; clearkouho(); if(!isjp(&im) || !jpreading(im.l->map, &im.pre, &im.raw, &reading) || reading.n == 0) return; - im.nkouho = dictlookup(im.l->dict, &reading, im.kouho, Maxkouho); - dictqokuri(&reading); + /* What Shift marked comes first; the dictionary's own readings next. */ + 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. */ @@ -663,6 +673,7 @@ reset(void) { sclear(&im.pre); sclear(&im.raw); + okuriat = -1; endsearch(); } @@ -798,10 +809,16 @@ searchkey(u32int ks, u32int mod, Str *com) static int transition(u32int ks, u32int mod, Str *com) { + Str mark; int n; Rune c; 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) ks = kokey(ks, mod); else if(isjp(&im)) @@ -917,6 +934,7 @@ init(void) memset(&search, 0, sizeof search); memset(&caret, 0, sizeof caret); modemark = 0; + okuriat = -1; activeowner = nil; lostowner = nil; clientpre = 0; diff --git a/tests/engine_test.c b/tests/engine_test.c index a376153..662176e 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -1223,6 +1223,18 @@ engine_japanese_candidates(struct ct *t) if(CT_EQ_INT(t, 1, im.nkouho)) checkstr(t, "two-kana okurigana", "大きい", &im.kouho[0]); 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", "言 行"); if(!typekeys(t, "itta", &com))