fix vietnamese telex: qu-/gi- onset tone placement and dd+tone

transvi fixes:

1. qu-/gi- onset tone placement. The u after q, and the i after g when a
   vowel follows, are onset glides rather than the rime nucleus, so the
   tone must skip them: qua -> quá (was qúa), gia -> giá. The onset was
   previously passed straight through to the app, so transvi never saw it
   and toned the glide. Keep the onset in the preedit by adding qu-/gi-
   clusters to telex.map (mktelex.py onsets(), appended additively to the
   curated map), and add onsetglide() so transvi skips the glide. gi- with
   no following vowel keeps i as the nucleus (gì, gìn).

2. A tone key on a vowel-less preedit (e.g. "đ" from dd) now commits the
   preedit and lets the tone key pass through (eat=0), matching the engine
   commit-on-passthrough invariant, instead of eating it into the commit.

Verified against the running engine: qua/quan/quay/quê/quên/quyển,
gia/già/giàu/giữ/giúp/giống, gì/gìn, dd+s; unchanged mua->mùa, của, lúa;
all non-qu/gi words byte-identical to before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-19 13:14:31 +09:00
parent aefe9bc618
commit a94d55c541
3 changed files with 951 additions and 3 deletions

32
vi.c
View File

@@ -89,12 +89,37 @@ applytone(Rune c, int tidx)
return c;
}
/*
* In qu- and gi- onsets the u/i is a glide that belongs to the onset,
* not the rime, so it must not bear the tone (qua -> quá, gia -> giá).
* The i of gi- is only a glide when another vowel follows; otherwise it
* is the nucleus itself (gì, gìn). Returns the rune index to skip, or -1.
*/
static int
onsetglide(Str *m)
{
Rune a, b;
int i;
if(m->n < 2)
return -1;
a = m->r[0];
b = removetone(m->r[1]);
if((a == 'q' || a == 'Q') && (b == 'u' || b == 'U'))
return 1;
if((a == 'g' || a == 'G') && (b == 'i' || b == 'I'))
for(i = 2; i < m->n; i++)
if(isvowel(removetone(m->r[i])))
return 1;
return -1;
}
Emit
transvi(Im *im, Rune c)
{
Emit e;
Str mapped, pre;
int i, tidx, vi, last, penult;
int i, tidx, vi, last, penult, glide;
Rune v, b1, b2;
if(!istone(c) && c != 'z')
@@ -115,9 +140,12 @@ transvi(Im *im, Rune c)
}
if(!mapget(im->l->map, &im->pre, &mapped))
mapped = im->pre;
glide = onsetglide(&mapped);
last = -1;
penult = -1;
for(i = 0; i < mapped.n; i++){
if(i == glide)
continue;
v = removetone(mapped.r[i]);
if(isvowel(v)){
penult = last;
@@ -140,9 +168,7 @@ transvi(Im *im, Rune c)
}
}
if(vi < 0){
e.eat = 1;
e.s = mapped;
sputr(&e.s, c);
return e;
}