Commit Graph

67 Commits

Author SHA1 Message Date
a0e83f98c6 engine: a takeover keeps the text it took for the context it took it from
Two applications' focus events cross — ibus-daemon documents the case —
so the first key of the new one can arrive before the old one says it
lost focus.  The engine dropped whatever that context was composing; it
now keeps it and hands it back with the reset or release that follows,
which every frontend already commits.  Normal ordering is unchanged.
2026-08-17 12:07:19 +09:00
88ad5f7630 engine: the one capability is a flag
Cclientpreedit was a one-bit mask that every producer set as "want ?
Cclientpreedit : 0" and every consumer masked out again; nothing else
was ever going to join it.  Keyreq carries clientpre, an int that says
whether the client draws the preedit, and the engine and the XIM
context keep it under that name.
2026-08-17 10:57:15 +09:00
33023d7f51 ipc: a modifier keysym is no key
Shift, Control, and their kin were mapped to special keys that the
engine then had a range and a check to ignore, and the GTK module made a
round trip to the daemon for each press.  ipckeysym maps them to key 0,
which was already the "no key" every frontend and the engine skip;
Kmodfirst, Kmodlast, and ismodkey go.
2026-08-17 10:56:03 +09:00
e32cea3296 engine: Backspace deletes the last kana shown; Tab converts like Space
Backspace undid a keystroke: な became ん, かんじ became かんj, きゃ
became ky.  Like every Japanese IME it now deletes the last kana as
shown — な goes, かんじ becomes かん, きゃ becomes き — while a romaji
letter that never became kana still goes one at a time.  Tab stepped
through candidates in a search and committed in Japanese; it steps
through them there too, with Space, and Shift+Tab steps back.
2026-08-17 01:55:25 +09:00
47f9801ad8 engine: a search just begun shows itself
Ctrl+E and Ctrl+H gave no sign until a key produced candidates; with
nothing pending the popup even went away.  An empty search now shows
☺ or 漢 in the popup, so the user knows the next keys are a query.
2026-08-17 01:45:27 +09:00
0c4271b8b5 engine: an emoji query shows as typed and Space picks
The emoji search folded the keys for its lookup and then showed and
committed the folded copy: SMILE became smile in the text.  And it
showed the transliteration only while that alone matched, so a Korean
query flipped between 웃 and key soup as it grew.  Now the query shown
is the keys while they match anything, else what they type in the
current language, both as typed; only the lookups fold.  Space in a
search picks the highlighted result as Enter does, instead of adding a
space no alias needs.
2026-08-17 01:44:29 +09:00
3d862bdc0d engine: keys outside a map pass on like any other; one candidate query; the caret rides on every key
Space and non-ASCII keys with a Korean or Telex syllable pending were
committed as text along with the syllable, while '.', ',' and digits
committed the syllable and passed on: one path for both now, through the
language's own trans, which commits and passes on.  Japanese Space is
its own key already.

dictqjp() knows which languages have candidates; its two callers no
longer choose between it and clearkouho().  Every request carries its
owner's caret as the frontend knows it, so a Keypress copies it whether
valid or not, and the XIM frontend need not send a Keycaret to say that
its spot went away.
2026-08-17 01:30:56 +09:00
97a838eda1 engine: the Japanese keyboard's own keys; keypad arrows move through candidates
半角/全角, ひらがな/カタカナ, 変換, and 無変換 committed the reading and did
nothing, like any function key.  They now stand for what a Japanese
keyboard means by them: toggling Japanese and English, switching kana
modes, converting (or turning Japanese on), and turning it off; the
Korean keys became a switch of the same shape.  The keypad's arrows
and page keys, with NumLock off, fold onto the plain ones as its Enter
and Tab already did.
2026-08-17 01:29:07 +09:00
1c8d9fac07 engine: Ctrl+Shift chords belong to the application
Ctrl+Shift+V switched to Vietnamese and ate the key, in every terminal
where it pastes; Ctrl+Shift+T and N opened no tab.  Only a plain
Ctrl+letter is a strans command now; a chord with Shift, Alt, or Super
commits what is pending and passes, as Alt and Super chords already did.
Caps Lock still works: the keysym's case never mattered.
2026-08-17 01:27:47 +09:00
97e6f2cf1a engine: one ASCII fold, commitim through impre, no second modifier check
Four places lower-cased ASCII by hand; commitim repeated impre's three
lines to compute the text it commits; and transition() began by
rejecting modifier keys that imhandlekey's keymeaningful() had already
turned away, kept alive only by tests calling transition() directly.
The test helper now goes through the same gate.
2026-08-17 01:24:56 +09:00
852e129f62 engine: the Hanja search composes from its seed, and Escape gives it back
Ctrl+H took the pending syllable as the query, but the keys typed after
it started a composition of their own: gk, Ctrl+H, s showed 하ㄴ, and r,
Ctrl+H, k showed ㄱㅏ.  transstr now composes from a pending text, so
the search goes on where the syllable left off.  Escape ended the search
and dropped the syllable it had taken; it puts it back as pending text.
2026-08-17 01:24:14 +09:00
f0498868eb 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.
2026-08-17 01:18:33 +09:00
ab0366b7f7 engine: verbs and adjectives through SKK's okuri-ari entries
A sixth of kanji.dict is SKK's okuri-ari entries, keyed by a stem and
the letter of the okurigana that follows it — かk: 書 描 掛 —, and no
reading ever ends in a letter, so かく offered 確 and 各 but never 書く,
and no verb or adjective could be converted at all.  Now every split of a
complete reading is tried, longest stem first, with the okurigana put
back: かく adds 書く and 描く after the exact candidates, よむ gives
読む, あかい 赤い, おおきい 大きい, いった 言った, かいた 書いた.  A stem
that ends in っ writes it in kana too, so いt and いっt agree.
2026-08-17 01:18:09 +09:00
f61f7ab5f8 engine: a romaji typo stays in the reading
A key that could not go on with the pending romaji, when that was not a
syllable either, committed the whole reading to the application and
passed the key on: one slip dumped かんj into the text with no way back.
The letters typed now stay in the reading as they are, like any other
IME shows them, and Backspace mends them; only a key that starts no
syllable at all still ends the composition.
2026-08-17 01:17:12 +09:00
abff5ed122 emoji: search the dictionary by prefix in the engine, not in the data
mkemoji wrote a row for every prefix of every alias, so that a query
matched as it was typed; the trie is a prefix index already, and with a
real emoji list those rows would be four times the aliases themselves.
Now emoji.dict has one row per alias, trienode() names a key's node, and
dictlookup walks the entries at and below it, the key's own first, up to
Maxkouho.  Trie children are appended rather than pushed, so the walk
keeps the file's order and a bare digit still picks the superscript or
subscript it always did.

The hand-written symbol rows move to symbol.src; emoji.src is left to
the emoji.  mkemoji reads both by default, or the files it is given.
2026-08-17 01:07:10 +09:00
221117f93d data(kana): Mozc's romaji table
hira.map and kata.map knew 152 keys of the 320 every Japanese IME
accepts: no nn, no small kana by x or l (xtu, xya, la, ...), no sya, tya,
zya, jya, dya, cya, tsa, thi, dhi, twu, kye, ye, fya, wha, and zi gave ぢ.
Both are now Mozc's default table in full, kata.map in Katakana, so that
they agree (kata.map alone had di → ディ and a lone v).  With Mozc, wi
and we are うぃ and うぇ (wyi and wye the old kana), the v row is ゔ, nn
is ん, and [ ] ~ and the z-prefixed symbols type 「 」 〜 ・ … ← ↓ ↑ →.

n' is a row now, not a case in transjp.  A row whose value is っ keeps
its consonant pending only when it is a doubled consonant or tch: xtu
and ltu are っ itself, so a lone っ can finally be typed.
2026-08-17 00:58:36 +09:00
b77400fa86 engine: the Korean keyboard's 한/영 and 한자 keys
A Korean keyboard sends Hangul and Hangul_Hanja for its two extra keys;
they did nothing but end the composition.  Now 한/영 toggles Korean and
English and 한자 opens the Hanja search, by standing in for Ctrl+S,
Ctrl+T, and Ctrl+H, as fcitx5-hangul binds them.
2026-08-17 00:54:36 +09:00
63fc60dc06 engine: Korean and Telex let Escape through after committing
A pending Hangul syllable is real text, and Escape is what vi users
press to leave insert mode; eating it and dropping the syllable served
nobody.  fcitx5-hangul commits and passes any key it does not consume,
Escape included; do the same for Korean and Telex.  A Japanese reading
is still cancelled: there Escape is the IME's own key.
2026-08-17 00:53:43 +09:00
e8519b4e06 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.
2026-08-17 00:53:32 +09:00
693a1210a6 engine: romaji is case-blind
Shift or Caps Lock made a Japanese key pass through as a Latin capital,
so nothing composed with Caps Lock on; Korean already folds case. Latin
text still has Ctrl+T.
2026-08-16 21:30:59 +09:00
91b3e538f1 engine: Ctrl+H converts the pending syllable; Backspace on an empty search stays put
Ctrl+H committed the syllable being composed and opened an empty search,
so converting 한 meant typing it twice; the pending syllable now seeds the
query. Backspace on an already empty search ended it and then reached the
application, deleting a character; it now only ends the search.
2026-08-16 21:30:16 +09:00
5e72a48c2c engine: Korean and Telex let Enter and Tab through after committing
Enter with a pending syllable committed it and stopped there, so a chat
message needed Enter twice; Korean input methods commit and pass the key
on. Japanese keeps consuming the key that confirms a reading or a chosen
candidate, as Japanese input methods do.
2026-08-16 21:28:11 +09:00
11bf379ad3 fix(engine): a reset or a lost focus commits the pending text
Clicking elsewhere, changing focus, or any client reset dropped the
composition in the GTK module and IBus (only XIM ResetIC handed it
back), so typing 안녕 and clicking Send lost 녕. The engine now returns
the pending text — a moved-to candidate first, as Enter would — on
Keyreset and Keyrelease; the GTK module asks for it before closing on
focus-out and commits it, IBus commits it on FocusOut, Reset and a
switch to a password field, and XIM commits it on focus loss and hands
it back on ResetIC without a separate capability probe.
2026-08-16 21:05:31 +09:00
ae1bc51256 data(kana): one sokuon rule for every doubled consonant, and the missing hira rows
Hiragana spelled out 42 doubled syllables (kka, ssha, ttsu, ...) and so
knew none it did not list: matcha, baggu and beddo came out as ma-tc-ha,
ba-gg-u. Katakana already used the engine's small-tsu rule for a few
doubles. Both maps now list every doubled consonant but n, plus tch, and
hiragana gains di, che and fa/fi/fe/fo like katakana.
2026-08-16 20:31:48 +09:00
32a9723043 fix(engine): the search keys commit the query they end or replace
Ctrl+E during an emoji search, Ctrl+H during a hanja search, or either
during the other search dropped the typed query, while every other
Ctrl key committed it as the README says. Escape is the way to cancel;
the search keys now commit the shown text like a language switch.
2026-08-16 20:27:31 +09:00
6402c26830 fix(engine): movement keys without candidates commit the pending text
Up, Down, PageUp and PageDown with a composition pending and nothing to
move through returned the key uneaten but left the text pending, so the
client moved its cursor and reset the context: the pending Korean
syllable vanished, where Left or Home would have committed it. Let them
fall through to the special-key path that commits and passes the key.
2026-08-16 20:26:28 +09:00
9853d65d4c fix(engine): keep composing when a reading reaches Maxrunes
At the limit the key that did not fit was handed back to the client as
plain ASCII, so a long Japanese reading ended in a Latin letter, and a
pending romaji consonant was flushed as ASCII with it (…あka for か).
Commit the completed kana, keep the pending syllable, and transliterate
the key as usual; other languages commit and go on the same way.
2026-08-16 20:25:38 +09:00
795f11be22 test: one engine pump and one preedit check for the white-box suites
server_test, ibus_test and xim_adapter_test each ran their own copy of
the same alt loop that stands in for imthread, and two of them had
private checkstr/checkenginepreedit variants. test_util.c now provides
Pump (trace, optional hold on a chosen op, stop) in its own proc so a
test may block in socket I/O while the engine runs, and test.h declares
the engine hooks once; every .c includes dat.h and fn.h itself as the
rest of the tree does. server_test's three copies of fixture setup and
teardown became serverbegin/serverend.
2026-08-16 16:30:32 +09:00
826da31486 test: drive Korean and Vietnamese through the engine's own transstr
ko_test and vi_test replayed keys with private copies of transstr's
loop, and test_util reimplemented the shown preedit; the engine exports
impre(Im*, Str*) and transstr() instead, so a change to composition
rules is one edit.
2026-08-16 16:25:40 +09:00
9080bb833c engine: trim the key dispatch
mapget and maplookup were one trie probe with two predicates; mapmatch
returns the match kind and mapget is its exact case. movedelta is a
switch. searchkey's modifier test was unreachable behind transition's,
searchlang's Alt/Super test behind both callers' earlier returns, and
picksearch's range check behind numberedkouho. Korean key folding runs
once at the top of transition. commit() on an empty preedit is a no-op,
so the Ctrl branch and startsearch lose their haspre guards and repeated
commit/reset pairs. Ctrl+language inside a search now commits the shown
query as it does elsewhere instead of dropping it. Language ids are
documented as the Ctrl codes they are; Im parameters no longer shadow
the file's Im.
2026-08-16 16:09:05 +09:00
68869e2589 engine: redraw when the picture changes, not when the state might have
Every key copied the whole Im and Search rune by rune, compared them
after the transition, and combined the answer with 'eaten', a commit
count that is always zero, and a force flag for caret and owner changes,
while redraw() kept a separate 'visible' bit to know when to send an
empty popup. redraw() now compares the snapshot with the last one sent
and treats two empty popups as equal, so imhandlekey just ends in
redraw(). keystroke() was only a test entry point and moves there.
2026-08-16 16:06:42 +09:00
abaea77248 engine: look dictionaries up synchronously
The dictionary thread ran in imthread's own proc, so a lookup could
only start once the engine blocked, and it was a trie probe anyway; the
emoji and Hanja searches already called dictlookup directly. The
request/result channels, sequence numbers, staleness checks and the
second draw per Japanese key are gone; dictqjp fills the candidates
in place. Emit.dict and Lang.dictq only ever triggered lookups for
Vietnamese, which has no dictionary. dictlookup(Lang*, key, out, max)
returns the count. The Hanja lookup no longer pre-checks for a single
syllable; a reading either has an entry or it does not.
2026-08-16 16:02:09 +09:00
ebcec3af6b fix(engine): send clients the composed Telex text, not the keys
For Vietnamese the preedit is the map key ('as', 'oong'); only the popup
and the commit path looked it up. Clients with inline preedit therefore
showed 'as' while Enter committed 'á'. impre() now maps once for every
reader, and snapshot() no longer maps a second time.
2026-08-16 15:57:55 +09:00
c7718ece52 data: one trie for maps and dictionaries, one loader
The hash map served a single exact-match lookup that the trie already
answers, at the price of a second container, a second file loader with
its own drift, and a Str-to-UTF-8 conversion on every chain probe. The
files are small (kanji.dict is 7.5k lines), so the trie holds both.
trieopen validates keys and each space-separated value word against Str
and reports path:line; trielookup takes the Str every caller holds and
treats a nil trie as an unloaded map; trienew/trieput exist for tests.
The overflow guards on growth, Trie.root (always 0) and the per-language
init loop written twice are gone.
2026-08-16 15:56:32 +09:00
a1ef8a4744 fix(frontends): restart preedit around commits 2026-08-15 15:42:35 +09:00
67ed2ef478 fix(engine): redraw only for visible caret changes 2026-08-15 15:07:13 +09:00
d29d43f443 fix(frontends): preserve preedit lifecycle 2026-08-15 15:05:49 +09:00
59482d00f3 fix(engine): compare logical transition state
Avoid reading inactive Str storage when deciding whether to publish a redraw. Keep the focused engine suite compact by retaining one case for each page, navigation, and empty-result invariant.
2026-08-14 22:46:42 +09:00
5dc2530951 fix(engine): make composition transitions deterministic 2026-08-14 22:13:16 +09:00
e4fc327ffd popup: show the candidate page 2026-08-14 20:06:53 +09:00
4cac6f1a15 engine: use stable candidate pages 2026-08-14 19:27:22 +09:00
b536573622 engine: update active preedit capability 2026-08-14 18:44:52 +09:00
9a3a92170e engine: reject stale dictionary results 2026-08-14 18:43:12 +09:00
2376035524 engine: route popup preedit by client capability 2026-08-14 17:02:46 +09:00
f5dc552c08 build: use configured include paths 2026-08-14 16:17:07 +09:00
9f4f9ceb14 ibus: cover context ownership through the engine 2026-08-13 23:21:12 +09:00
bb2a970db1 tests: cover ownership across ipc connections 2026-08-13 23:07:25 +09:00
b6a9ccd9d9 cleanup: simplify frontend request ownership 2026-08-13 21:57:32 +09:00
125e9349c0 fix: pass empty search backspace through 2026-08-12 21:35:33 +09:00
136695a900 hanja: separate temporary search mode 2026-08-12 21:23:10 +09:00