engine: no candidate is chosen until the user moves to one; Space converts

A complete Japanese reading showed its candidates with the first row
highlighted, yet Enter committed the reading unless the user had moved
to a candidate: the highlight lied.  Now sel is -1 while nothing is
chosen, and it alone says what Enter commits; candidatechosen goes.

Every key that ends a composition — Enter, Tab, a language switch, a
special key, a modifier chord, typing on — now commits the chosen
candidate through one flush; before, only Enter and Tab did, and Ctrl+S
after choosing 漢字 committed かんじ.

Space in a Japanese mode is the conversion key, as in every other
Japanese IME: it steps through the candidates (Shift+Space backwards,
both wrapping) and commits a reading that has none, without typing a
space.  Backspace and Escape on a chosen candidate go back to the
reading first.  Tab in a search wraps through the same cyclekouho.

reset() no longer forgets the caret: a chosen candidate confirmed by
typing on would otherwise redraw the next reading at the pointer.
This commit is contained in:
2026-08-17 00:52:50 +09:00
parent ea363ca792
commit e8519b4e06
3 changed files with 148 additions and 100 deletions

View File

@@ -16,14 +16,18 @@ Vietnamese Telex is also available as a compatibility mode.
| `Ctrl+E` | Emoji and symbol search | | `Ctrl+E` | Emoji and symbol search |
| `Ctrl+H` | One-shot Hanja search | | `Ctrl+H` | One-shot Hanja search |
Use `Up`/`Down` to move through candidates and `PageUp`/`PageDown` to move by A complete Japanese reading shows its Kanji candidates with none chosen.
nine without wrapping. An unmodified `1`-`9` selects that row on the current `Space` chooses the first and then steps on, wrapping (`Shift+Space` steps
page. `Enter` and `Tab` commit a candidate you have moved to; otherwise they back); `Up`/`Down` and `PageUp`/`PageDown` move without wrapping. An
commit the reading, as `0` always does. Japanese consumes the confirming key; unmodified `1`-`9` commits that row of the current page. `Enter` and `Tab`
Korean and Vietnamese pass it on to the application. In a temporary Emoji or commit the chosen candidate, or the reading when none is chosen, as `0`
Hanja search, `Enter` commits the highlighted result and `Tab`/`Shift-Tab` always does; so does typing on, or any key that ends the composition.
wrap through the results. Leaving the field or clicking elsewhere commits Japanese consumes the confirming key; Korean and Vietnamese pass it on to
what is pending; `Esc` cancels it. the application. `Space` on a reading without candidates commits it and
types nothing. `Backspace` and `Esc` on a chosen candidate go back to the
reading. In a temporary Emoji or Hanja search, `Enter` commits the
highlighted result and `Tab`/`Shift-Tab` wrap through the results. Leaving
the field or clicking elsewhere commits what is pending; `Esc` cancels it.
Hiragana mode composes a complete reading before offering Kanji candidates. Hiragana mode composes a complete reading before offering Kanji candidates.
Katakana mode does not perform Kanji conversion. Emoji and Hanja searches Katakana mode does not perform Kanji conversion. Emoji and Hanja searches

138
strans.c
View File

@@ -5,7 +5,6 @@ static Im im;
static void *activeowner; static void *activeowner;
static int activecap; static int activecap;
static Caret caret; static Caret caret;
static int candidatechosen;
static Drawcmd lastdraw; static Drawcmd lastdraw;
static Emit transjp(Im*, Rune); static Emit transjp(Im*, Rune);
static void backjp(Im*); static void backjp(Im*);
@@ -54,12 +53,12 @@ mapget(Trie *t, Str *key, Str *out)
return mapmatch(t, key, out) == TrieExact; return mapmatch(t, key, out) == TrieExact;
} }
/* sel is the chosen candidate; -1 while the user has not moved to one. */
static void static void
clearkouho(void) clearkouho(void)
{ {
im.nkouho = 0; im.nkouho = 0;
im.sel = -1; im.sel = -1;
candidatechosen = 0;
} }
static void static void
@@ -99,7 +98,16 @@ movekouho(int delta)
im.sel = 0; im.sel = 0;
if(im.sel >= im.nkouho) if(im.sel >= im.nkouho)
im.sel = im.nkouho - 1; im.sel = im.nkouho - 1;
candidatechosen = 1; }
/* Space and Tab step through the candidates and wrap around. */
static void
cyclekouho(int delta)
{
if(im.sel < 0)
im.sel = delta > 0 ? 0 : im.nkouho - 1;
else
im.sel = (im.sel + delta + im.nkouho) % im.nkouho;
} }
static int static int
@@ -230,7 +238,7 @@ redraw(void)
channbsend(drawc, &dc); channbsend(drawc, &dc);
} }
/* Kanji candidates for a complete reading. */ /* Kanji candidates for a complete reading; none is chosen yet. */
static void static void
dictqjp(void) dictqjp(void)
{ {
@@ -240,7 +248,6 @@ dictqjp(void)
if(!jpreading(im.l->map, &im.pre, &im.raw, &reading) || reading.n == 0) if(!jpreading(im.l->map, &im.pre, &im.raw, &reading) || reading.n == 0)
return; return;
im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho); im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho);
selectfirst();
} }
static int static int
@@ -269,12 +276,6 @@ commitim(Im *p, Str *com)
sclear(&p->raw); sclear(&p->raw);
} }
static void
commit(Str *com)
{
commitim(&im, com);
}
static void static void
backjp(Im *p) backjp(Im *p)
{ {
@@ -542,10 +543,7 @@ searchlang(Rune c)
static void static void
endsearch(void) endsearch(void)
{ {
search.lang = 0; memset(&search, 0, sizeof search);
sclear(&search.seed);
sclear(&search.raw);
sclear(&search.text);
clearkouho(); clearkouho();
} }
@@ -555,14 +553,23 @@ reset(void)
sclear(&im.pre); sclear(&im.pre);
sclear(&im.raw); sclear(&im.raw);
endsearch(); endsearch();
memset(&caret, 0, sizeof caret);
} }
/*
* Whatever is pending becomes committed text: the search query, a chosen
* candidate, else the reading. Enter does this, and so do a reset and a
* lost focus.
*/
static void static void
commitsearch(Str *com) flush(Str *com)
{ {
sappend(com, &search.text); if(search.lang)
endsearch(); sappend(com, &search.text);
else if(im.sel >= 0)
sappend(com, &im.kouho[im.sel]);
else
commitim(&im, com);
reset();
} }
/* /*
@@ -572,36 +579,18 @@ commitsearch(Str *com)
static void static void
startsearch(int lang, Str *com) startsearch(int lang, Str *com)
{ {
Str pending; Str seed;
impre(&im, &pending); sclear(&seed);
if(lang == LangHANJA){ if(lang == LangHANJA && !search.lang && im.sel < 0)
sclear(&im.pre); impre(&im, &seed);
sclear(&im.raw);
}else
commit(com);
commitsearch(com);
search.lang = lang;
if(lang == LangHANJA){
search.seed = pending;
searchquery();
}
}
/*
* Whatever is pending becomes committed text, a moved-to candidate
* first: Enter does this, and so do a reset and a lost focus.
*/
static void
flush(Str *com)
{
if(search.lang)
commitsearch(com);
else if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho)
sappend(com, &im.kouho[im.sel]);
else else
commit(com); flush(com);
reset(); reset();
search.lang = lang;
search.seed = seed;
if(lang == LangHANJA)
searchquery();
} }
static void static void
@@ -630,24 +619,15 @@ searchkey(u32int ks, u32int mod, Str *com)
return 1; return 1;
} }
if(ks == Ktab){ if(ks == Ktab){
if(im.nkouho == 0) if(im.nkouho != 0)
return 1; cyclekouho(mod & Mshift ? -1 : 1);
if(mod & Mshift){
if(im.sel <= 0)
im.sel = im.nkouho - 1;
else
im.sel--;
}else if(im.sel >= im.nkouho - 1)
im.sel = 0;
else
im.sel++;
return 1; return 1;
} }
if(ks == Kret){ if(ks == Kret){
if(im.nkouho > 0) if(im.nkouho > 0)
picksearch(max(im.sel, 0), com); picksearch(max(im.sel, 0), com);
else else
commitsearch(com); flush(com);
return 1; return 1;
} }
if(ks == Kback){ if(ks == Kback){
@@ -668,7 +648,7 @@ searchkey(u32int ks, u32int mod, Str *com)
} }
if(ks >= Kspec || (ks < ' ' && !(mod & Mctrl)) || if(ks >= Kspec || (ks < ' ' && !(mod & Mctrl)) ||
(mod & (Malt|Msuper))){ (mod & (Malt|Msuper))){
commitsearch(com); flush(com);
return 0; return 0;
} }
if(mod & Mctrl){ if(mod & Mctrl){
@@ -678,11 +658,11 @@ searchkey(u32int ks, u32int mod, Str *com)
startsearch(n, com); startsearch(n, com);
return 1; return 1;
} }
commitsearch(com); flush(com);
return n != 0 || setlang(c); return n != 0 || setlang(c);
} }
if(search.raw.n >= Maxrunes){ if(search.raw.n >= Maxrunes){
commitsearch(com); flush(com);
return 0; return 0;
} }
sputr(&search.raw, ks); sputr(&search.raw, ks);
@@ -715,6 +695,15 @@ transition(u32int ks, u32int mod, Str *com)
reset(); reset();
return 1; return 1;
} }
if(ks == ' ' && isjp(&im) && haspre(&im) && !(mod & ~Mshift)){
/* Space converts: it steps through the candidates, or
* commits a reading that has none. */
if(im.nkouho != 0)
cyclekouho(mod & Mshift ? -1 : 1);
else
flush(com);
return 1;
}
if(ks == Ktab || ks == Kret){ if(ks == Ktab || ks == Kret){
if(!haspre(&im)) if(!haspre(&im))
return 0; return 0;
@@ -725,6 +714,10 @@ transition(u32int ks, u32int mod, Str *com)
if(ks == Kback){ if(ks == Kback){
if(!haspre(&im)) if(!haspre(&im))
return 0; return 0;
if(im.sel >= 0){
im.sel = -1; /* back from the candidate to the reading */
return 1;
}
im.l->back(&im); im.l->back(&im);
if(!haspre(&im)){ if(!haspre(&im)){
reset(); reset();
@@ -739,12 +732,14 @@ transition(u32int ks, u32int mod, Str *com)
if(ks == Kesc){ if(ks == Kesc){
if(!haspre(&im)) if(!haspre(&im))
return 0; return 0;
reset(); if(im.sel >= 0)
im.sel = -1;
else
reset();
return 1; return 1;
} }
if(ks >= Kspec || (mod & (Malt|Msuper))){ if(ks >= Kspec || (mod & (Malt|Msuper))){
commit(com); flush(com);
reset();
return 0; return 0;
} }
if(mod & Mctrl){ if(mod & Mctrl){
@@ -754,20 +749,18 @@ transition(u32int ks, u32int mod, Str *com)
startsearch(n, com); startsearch(n, com);
return 1; return 1;
} }
commit(com); flush(com);
reset();
return setlang(c); return setlang(c);
} }
if(ks == '0' && im.nkouho > 0){ if(ks == '0' && im.nkouho > 0){
commit(com); im.sel = -1;
reset(); flush(com);
return 1; return 1;
} }
if(ks > 0x7f || ks == ' '){ if(ks > 0x7f || ks == ' '){
if(!haspre(&im)) if(!haspre(&im))
return 0; return 0;
commit(com); flush(com);
reset();
if(com->n >= Maxrunes) if(com->n >= Maxrunes)
return 0; return 0;
sputr(com, ks); sputr(com, ks);
@@ -775,13 +768,15 @@ transition(u32int ks, u32int mod, Str *com)
} }
if(im.l->trans == nil) if(im.l->trans == nil)
return 0; return 0;
if(im.sel >= 0)
flush(com); /* typing on takes the chosen candidate */
if(im.pre.n >= Maxrunes || if(im.pre.n >= Maxrunes ||
(isjp(&im) && im.pre.n + im.raw.n >= Maxrunes)){ (isjp(&im) && im.pre.n + im.raw.n >= Maxrunes)){
/* A full reading commits; a pending romaji syllable stays. */ /* A full reading commits; a pending romaji syllable stays. */
if(isjp(&im)) if(isjp(&im))
sappend(com, &im.pre); sappend(com, &im.pre);
else else
commit(com); commitim(&im, com);
sclear(&im.pre); sclear(&im.pre);
clearkouho(); clearkouho();
} }
@@ -798,7 +793,6 @@ init(void)
memset(&caret, 0, sizeof caret); memset(&caret, 0, sizeof caret);
activeowner = nil; activeowner = nil;
activecap = 0; activecap = 0;
candidatechosen = 0;
memset(&lastdraw, 0, sizeof lastdraw); memset(&lastdraw, 0, sizeof lastdraw);
} }

View File

@@ -116,6 +116,9 @@ engine_backspace_clears_candidates(struct ct *t)
im.nkouho = 1; im.nkouho = 1;
im.sel = 0; im.sel = 0;
CT_CHECK(t, keystroke(Kback, 0, &com)); CT_CHECK(t, keystroke(Kback, 0, &com));
CT_EQ_INT(t, -1, im.sel);
checkstr(t, "chosen candidate backspace", "na", &im.raw);
CT_CHECK(t, keystroke(Kback, 0, &com));
checkstr(t, "backspace commit", "", &com); checkstr(t, "backspace commit", "", &com);
checkstr(t, "pending romaji after backspace", "n", &im.raw); checkstr(t, "pending romaji after backspace", "n", &im.raw);
impre(&im, &shown); impre(&im, &shown);
@@ -162,7 +165,7 @@ engine_candidate_shortcut_modifiers(struct ct *t)
init(); init();
im.kouho[9] = mkstr("must-not-select"); im.kouho[9] = mkstr("must-not-select");
im.nkouho = 10; im.nkouho = 10;
im.sel = 9; im.sel = -1;
selected = im.kouho[9]; selected = im.kouho[9];
sclear(&com); sclear(&com);
CT_CHECK(t, !keystroke('1', mods[i], &com)); CT_CHECK(t, !keystroke('1', mods[i], &com));
@@ -277,7 +280,6 @@ engine_active_owner_reset(struct ct *t)
CT_EQ_PTR(t, &owner, activeowner); CT_EQ_PTR(t, &owner, activeowner);
checkstr(t, "reset hands the reading back", "", &res.commit); checkstr(t, "reset hands the reading back", "", &res.commit);
CT_EQ_INT(t, 0, res.preedit.n); CT_EQ_INT(t, 0, res.preedit.n);
CT_CHECK(t, !caret.valid);
res = ownerrequest(&owner, Keypress, 'n', 0); res = ownerrequest(&owner, Keypress, 'n', 0);
checkstr(t, "same owner after reset", "", &res.preedit); checkstr(t, "same owner after reset", "", &res.preedit);
} }
@@ -633,7 +635,7 @@ engine_language_switch_state(struct ct *t)
if(steps[i].stale){ if(steps[i].stale){
im.kouho[0] = mkstr("stale"); im.kouho[0] = mkstr("stale");
im.nkouho = 1; im.nkouho = 1;
im.sel = 0; im.sel = -1;
} }
sclear(&com); sclear(&com);
eaten = keystroke(steps[i].key, steps[i].mod, &com); eaten = keystroke(steps[i].key, steps[i].mod, &com);
@@ -693,7 +695,6 @@ struct Searchfix
Trie *dict; Trie *dict;
int activecap; int activecap;
Drawcmd lastdraw; Drawcmd lastdraw;
int candidatechosen;
}; };
static int static int
@@ -723,7 +724,6 @@ setcandidates(int n)
im.kouho[i] = mkstr(name); im.kouho[i] = mkstr(name);
} }
im.nkouho = n; im.nkouho = n;
selectfirst();
} }
static void static void
@@ -873,6 +873,7 @@ engine_candidate_completion(struct ct *t)
im.pre = mkstr("reading"); im.pre = mkstr("reading");
sclear(&com); sclear(&com);
CT_CHECK(t, keystroke(Kdown, 0, &com)); CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "explicit candidate", "c2", &com); checkstr(t, "explicit candidate", "c2", &com);
@@ -888,13 +889,14 @@ engine_candidate_completion(struct ct *t)
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "Enter without candidates", "reading", &com); checkstr(t, "Enter without candidates", "reading", &com);
/* A digit with no such row on the page is an ordinary key. */ /* A digit with no such row on the page is an ordinary key: it
* confirms the candidate paged to and passes on. */
candidatebegin(32); candidatebegin(32);
im.pre = mkstr("reading"); im.pre = mkstr("reading");
im.sel = 27; im.sel = 27;
sclear(&com); sclear(&com);
CT_CHECK(t, !keystroke('9', 0, &com)); CT_CHECK(t, !keystroke('9', 0, &com));
checkstr(t, "unavailable short-page digit", "reading", &com); checkstr(t, "unavailable short-page digit", "c28", &com);
CT_EQ_INT(t, 0, im.nkouho); CT_EQ_INT(t, 0, im.nkouho);
} }
@@ -913,7 +915,6 @@ searchsave(Searchfix *f, int lang)
f->dict = f->dictlang->dict; f->dict = f->dictlang->dict;
f->activecap = activecap; f->activecap = activecap;
f->lastdraw = lastdraw; f->lastdraw = lastdraw;
f->candidatechosen = candidatechosen;
draindraw(nil); draindraw(nil);
} }
@@ -956,7 +957,6 @@ searchend(Searchfix *f)
search = f->search; search = f->search;
activecap = f->activecap; activecap = f->activecap;
lastdraw = f->lastdraw; lastdraw = f->lastdraw;
candidatechosen = f->candidatechosen;
} }
void void
@@ -982,7 +982,7 @@ engine_popup_preedit_capability(struct ct *t)
if(CT_CHECK(t, draindraw(&dc) > 0)){ if(CT_CHECK(t, draindraw(&dc) > 0)){
CT_EQ_INT(t, 0, dc.pre.n); CT_EQ_INT(t, 0, dc.pre.n);
CT_EQ_INT(t, 1, dc.nkouho); CT_EQ_INT(t, 1, dc.nkouho);
CT_EQ_INT(t, 0, dc.sel); CT_EQ_INT(t, -1, dc.sel);
checkstr(t, "client-preedit candidate", "", &dc.kouho[0]); checkstr(t, "client-preedit candidate", "", &dc.kouho[0]);
} }
@@ -992,11 +992,11 @@ engine_popup_preedit_capability(struct ct *t)
if(CT_CHECK(t, draindraw(&dc) > 0)){ if(CT_CHECK(t, draindraw(&dc) > 0)){
checkstr(t, "popup preedit", "", &dc.pre); checkstr(t, "popup preedit", "", &dc.pre);
CT_EQ_INT(t, 1, dc.nkouho); CT_EQ_INT(t, 1, dc.nkouho);
CT_EQ_INT(t, 0, dc.sel); CT_EQ_INT(t, -1, dc.sel);
checkstr(t, "popup candidate", "", &dc.kouho[0]); checkstr(t, "popup candidate", "", &dc.kouho[0]);
} }
CT_EQ_INT(t, 1, im.nkouho); CT_EQ_INT(t, 1, im.nkouho);
CT_EQ_INT(t, 0, im.sel); CT_EQ_INT(t, -1, im.sel);
checkstr(t, "retained popup candidate", "", &im.kouho[0]); checkstr(t, "retained popup candidate", "", &im.kouho[0]);
kres = ownerrequestcap(&client, 0, Keycap, 0, 0); kres = ownerrequestcap(&client, 0, Keycap, 0, 0);
@@ -1016,11 +1016,11 @@ engine_popup_preedit_capability(struct ct *t)
if(CT_CHECK(t, draindraw(&dc) > 0)){ if(CT_CHECK(t, draindraw(&dc) > 0)){
CT_EQ_INT(t, 0, dc.pre.n); CT_EQ_INT(t, 0, dc.pre.n);
CT_EQ_INT(t, 1, dc.nkouho); CT_EQ_INT(t, 1, dc.nkouho);
CT_EQ_INT(t, 0, dc.sel); CT_EQ_INT(t, -1, dc.sel);
checkstr(t, "restored client candidate", "", &dc.kouho[0]); checkstr(t, "restored client candidate", "", &dc.kouho[0]);
} }
CT_EQ_INT(t, 1, im.nkouho); CT_EQ_INT(t, 1, im.nkouho);
CT_EQ_INT(t, 0, im.sel); CT_EQ_INT(t, -1, im.sel);
checkstr(t, "retained client candidate", "", &im.kouho[0]); checkstr(t, "retained client candidate", "", &im.kouho[0]);
kres = ownerrequestcap(&client, Cclientpreedit, Keycap, 0, 0); kres = ownerrequestcap(&client, Cclientpreedit, Keycap, 0, 0);
CT_CHECK(t, kres.eaten); CT_CHECK(t, kres.eaten);
@@ -1116,14 +1116,64 @@ engine_japanese_candidates(struct ct *t)
checkstr(t, "complete Japanese reading", "かんじ", &shown); checkstr(t, "complete Japanese reading", "かんじ", &shown);
if(!CT_EQ_INT(t, 2, im.nkouho)) if(!CT_EQ_INT(t, 2, im.nkouho))
goto cleanup; goto cleanup;
CT_EQ_INT(t, 0, im.sel); CT_EQ_INT(t, -1, im.sel);
checkstr(t, "candidate one", "漢字", &im.kouho[0]); checkstr(t, "candidate one", "漢字", &im.kouho[0]);
checkstr(t, "candidate two", "幹事", &im.kouho[1]); checkstr(t, "candidate two", "幹事", &im.kouho[1]);
CT_CHECK(t, keystroke(Kdown, 0, &com)); CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_EQ_INT(t, 0, im.sel);
CT_CHECK(t, keystroke(Kdown, 0, &com));
CT_EQ_INT(t, 1, im.sel); CT_EQ_INT(t, 1, im.sel);
CT_CHECK(t, keystroke(Kret, 0, &com)); CT_CHECK(t, keystroke(Kret, 0, &com));
checkstr(t, "selected Kanji", "幹事", &com); checkstr(t, "selected Kanji", "幹事", &com);
CT_EQ_INT(t, 0, im.nkouho); CT_EQ_INT(t, 0, im.nkouho);
/* Space converts: it steps through the candidates and wraps;
* typing on takes the chosen one; Backspace goes back. */
if(!typekeys(t, "kanji", &com))
goto cleanup;
sclear(&com);
CT_CHECK(t, keystroke(' ', 0, &com));
CT_EQ_INT(t, 0, im.sel);
CT_CHECK(t, keystroke(' ', 0, &com));
CT_EQ_INT(t, 1, im.sel);
CT_CHECK(t, keystroke(' ', 0, &com));
CT_EQ_INT(t, 0, im.sel);
CT_CHECK(t, keystroke(' ', Mshift, &com));
CT_EQ_INT(t, 1, im.sel);
CT_CHECK(t, keystroke(Kback, 0, &com));
CT_EQ_INT(t, -1, im.sel);
CT_EQ_INT(t, 2, im.nkouho);
CT_CHECK(t, keystroke(' ', 0, &com));
CT_CHECK(t, keystroke('w', 0, &com));
CT_CHECK(t, keystroke('o', 0, &com));
checkstr(t, "typing on takes the candidate", "漢字", &com);
impre(&im, &shown);
checkstr(t, "reading after the candidate", "", &shown);
sclear(&com);
CT_CHECK(t, keystroke(' ', 0, &com));
checkstr(t, "Space commits a reading without candidates", "", &com);
CT_EQ_INT(t, 0, im.pre.n);
CT_EQ_INT(t, 0, im.raw.n);
CT_CHECK(t, !keystroke(' ', 0, &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))
@@ -1179,8 +1229,8 @@ engine_japanese_backspace_and_boundaries(struct ct *t)
im.l = getlang(LangJP); im.l = getlang(LangJP);
sclear(&com); sclear(&com);
if(typekeys(t, "kanji", &com)){ if(typekeys(t, "kanji", &com)){
CT_CHECK(t, keystroke(' ', 0, &com)); CT_CHECK(t, keystroke(L'', 0, &com));
checkstr(t, "real input boundary", "かんじ ", &com); checkstr(t, "real input boundary", "かんじ", &com);
} }
init(); init();
@@ -1960,7 +2010,7 @@ engine_full_boundary_passthrough(struct ct *t)
im.pre.r[i] = L''; im.pre.r[i] = L'';
im.pre.n = Maxrunes; im.pre.n = Maxrunes;
sclear(&com); sclear(&com);
CT_CHECK(t, !keystroke(' ', 0, &com)); CT_CHECK(t, !keystroke(L'', 0, &com));
CT_EQ_INT(t, Maxrunes, com.n); CT_EQ_INT(t, Maxrunes, com.n);
for(i = 0; i < Maxrunes; i++) for(i = 0; i < Maxrunes; i++)
CT_EQ_UINT(t, L'', com.r[i]); CT_EQ_UINT(t, L'', com.r[i]);
@@ -1973,9 +2023,9 @@ engine_full_boundary_passthrough(struct ct *t)
im.pre.r[i] = L''; im.pre.r[i] = L'';
im.pre.n = Maxrunes-1; im.pre.n = Maxrunes-1;
sclear(&com); sclear(&com);
CT_CHECK(t, keystroke(' ', 0, &com)); CT_CHECK(t, keystroke(L'', 0, &com));
CT_EQ_INT(t, Maxrunes, com.n); CT_EQ_INT(t, Maxrunes, com.n);
CT_EQ_UINT(t, ' ', com.r[Maxrunes-1]); CT_EQ_UINT(t, L'', com.r[Maxrunes-1]);
/* Typing on at the limit commits the kana and keeps the syllable. */ /* Typing on at the limit commits the kana and keeps the syllable. */
init(); init();