The unit suite built a Lang of its own for Telex and loaded telex.map
into it, so the tests that drive the real table had to swap the map in
and out around themselves. testmapinit loads it where it belongs.
An IBus client throws away a dead key its engine did not take — the GTK
module's own comment says so, and Qt does the same — so é and ü were
lost in every IBus application, while XIM composed them with a table of
its own. That table moves to compose.c, which both frontends now use;
xim.c is the shorter for it.
A finished sequence is text, not a key: the engine is asked to hand back
what it had pending, and the composed text follows it, so the composed
character can no longer land before the syllable typed before it.
ibusinit unwound its server and un-registered its atexit handler on a
failure whose only sequel was ibusthread dying anyway; now each failure
dies with its own message, and the test that existed to walk that
unwind goes with it. Also gone: an empty watch-toggle callback where
libdbus takes nil, Maxconns as a second name for Maxclients, fprintf
and strerror where the rest of the daemon says fprint and %r, USED()
where a parameter can simply be unnamed, a second findcontext() for the
Properties branch, and emitcommit() taking a connection and path apart
from the context that has both. Ownerpoll lives once, in dat.h.
dictlookup took a Lang for two reasons that were not its business: to
know whether to walk below the key (the emoji dictionary) and to drop a
candidate equal to the key except there. No dictionary lists its key
among its candidates — the rule was left from a first version that put
the reading first itself — so it goes, and the walk is dictprefix(),
called by the emoji search alone.
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.
XIM text always went out through imdkit's COMPOUND_TEXT converter,
which already wraps every UTF-8 string in ESC%G; the UTF8_STRING
negotiation, the per-client encoding list, ximtext.c's fallback and its
wrapper never changed a byte on the wire. The spot location is honoured
for every style now (GTK's XIM module sends it with PreeditCallbacks)
and an unset focus window means the client window, as the spec says,
so those clients get the popup at the caret. readattrs/place kept four
transient fields to pass values between them; ximclose tore down state
right before die(); the OOM passthrough context was a third policy for
one small calloc where emalloc dies like everything else. Both frontends
now poll for engine-owner loss only while a preedit shows instead of
XIM waking on a pipe the engine had to know about; ibus stops waking
five times a second when idle. keymeaningful() is the engine's own
predicate. The standalone xim_test moved into the unit suite, so
xim/Makefile is gone.
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.
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.
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.
Add table-driven dictionary miss and language-switch state coverage, regress stale candidates after backspace, and clear them before dictionary refresh. Keep IPC and runtime-created file checks outside the unit runner.