Skip to content

Custom-candidate overrides via UI; async dictionary load - #11

Merged
Kenny2github merged 6 commits into
mainfrom
feat/candidate-overrides-async-dict
Aug 11, 2026
Merged

Kenny2github merged 6 commits into
mainfrom
feat/candidate-overrides-async-dict

Conversation

@ArnauvGilotra

@ArnauvGilotra ArnauvGilotra commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

Two things from the last round, both in here.

1. Overrides, but with real UI (not a text file)

Reinstates the "associate a key with extra candidates" idea (the old + behaviour), driven from the options dialog instead of hand-editing a .txt.

  • New Custom candidates section in the options dialog: a list of existing overrides, a Key field, a Candidates field (space-separated), and Add / Update / Remove. Select a row to load it into the fields for editing.
  • Stored per-user under HKCU\Software\CantoIME\Overrides, one REG_SZ value per key (candidates space-joined). Cancel discards; OK persists.
  • Merged on top of the selected dictionary at load via Dictionary::ApplyOverrides: the extra candidates are appended after the stock ones for that key (deduped), and a key with no stock entry is added outright.

One judgement call for you: I append overrides after the stock candidates (so "additional" means additional, stock ordering unchanged). If you'd rather a user's own candidate jump to position 1 (selectable with the 1 key), say so and I'll flip the merge order.

2. Activation lag

The lag switching to CantoIME was the whole dictionary being parsed synchronously on the UI thread inside ActivateEx (ApplySettings -> Dictionary::Load). Now:

  • The parse runs on a detached background thread; the finished Dictionary is posted back to the UI thread (WM_CANTO_DICT_READY) and move-assigned into m_dict. Activation returns immediately.
  • The message window is now created before ApplySettings so the handoff works during activation.
  • Until the load lands (a fraction of a second) typing just yields no candidates yet, then it's live.
  • Stale loads (dictionary switched again mid-parse) are dropped by comparing the loaded path against the latest requested one.
  • The DLL is pinned (DllAddRef/DllRelease) for the worker's lifetime so a fast deactivate can't unload the code out from under the thread.

Override changes funnel through the same background reload path, so a removed override rebuilds cleanly from the base file rather than needing an incremental un-merge.

Notes

  • Everything is CI-verified compiling only; the runtime behaviours (dialog, the list, the lag actually being gone) need a look on your VM.
  • No dictionary/LFS files touched.

Two changes:

1. Async dictionary load. Switching to CantoIME parsed the whole dictionary
   synchronously inside ActivateEx (ApplySettings -> Dictionary::Load), which
   blocked the UI thread and caused the noticeable activation lag. The parse now
   runs on a detached background thread and the finished Dictionary is handed
   back to the UI thread via WM_CANTO_DICT_READY, where it is move-assigned into
   m_dict. The message window is created before ApplySettings so the handoff
   works during activation. Until the load lands (a fraction of a second) typing
   just produces no candidates. Stale loads (dictionary switched again mid-parse)
   are dropped by comparing the loaded path to the latest requested one, and the
   DLL is pinned (DllAddRef/DllRelease) for the worker's lifetime so a fast
   deactivate cannot unload it out from under the thread.

2. Custom candidates in the options dialog. Instead of hand-editing a text file,
   the dialog now has a "Custom candidates" section: a list plus key/candidates
   fields and Add-Update / Remove buttons. Overrides are stored per-user under
   HKCU\Software\CantoIME\Overrides (one REG_SZ value per key) and merged on top
   of the selected dictionary at load via Dictionary::ApplyOverrides — extra
   candidates are appended after the stock ones for that key (deduped); a key
   with no stock entry is added. Override changes funnel through the same
   background reload path, so removals rebuild cleanly from the base file.
Prepend each key's override candidates ahead of the dictionary
candidates so the user's first extra is pickable with the 1 key.
Dedup keeps a stock character that an override repeats at the front
only, never twice.
actions/checkout defaults to lfs=false, so the build was bundling the
LFS pointer stubs instead of the real dictionaries. Enable lfs on
checkout and fail fast if either dictionary is still a pointer.
It cost a build step on every run; lfs: true on checkout is enough.
Resize: add a sizing border and an anchor-based reflow so the overrides
list grows/shrinks with the window (fields and buttons track it). The
dialog was too tall on some resolutions; it can now shrink until the
list is ~3 rows and widen freely.

Add-multiple: after Add / Update, clear the Key/Candidates fields and
drop the list selection so the next Add starts a fresh entry instead of
being stuck updating the row just committed.
Overrides now belong to the dictionary they were made for: one set for
stock Traditional, one for stock Simplified, and one per distinct custom
dictionary path. Storage moves to HKCU\...\Overrides\<dictId>, where
dictId is trad/simp or custom-<hash-of-path> (DictionaryIdentity).

Runtime loads the active dictionary's set. The options dialog stages a
set per dictionary it touches and swaps the editor when you change the
dictionary selection (mode change, Browse, or leaving the path field),
writing every touched set back under its own dictionary on OK.
@ArnauvGilotra
ArnauvGilotra force-pushed the feat/candidate-overrides-async-dict branch from b175f09 to 2571952 Compare August 11, 2026 08:08
@Kenny2github
Kenny2github merged commit 17fdd5f into main Aug 11, 2026
2 checks passed
@Kenny2github
Kenny2github deleted the feat/candidate-overrides-async-dict branch August 11, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants