界面渲染 only takes effect at Server startup, so the setting already told the
user to restart the IME process but offered no way to do it outside the
Ctrl+Shift+Alt+R developer shortcut. Add a 重启 button in the same section.
Settings runs in its own process, so the request travels the Aux pipe — the
supported cross-integrity control path the config notifications already use —
and the Server exits with the Watchdog restart code. The shortcut and the
button now share RestartServerProcess().
Whether a whole-sentence candidate is produced depends on which
dictionaries and local models are installed, so the CI product data
yields none and the presence requirement failed there. Check the
canonical reading of whatever whole-sentence candidates do appear, and
assert the shuangpin-to-quanpin conversion unconditionally instead --
that conversion is the value the fix attaches to those candidates.
The Google-pinyin whole-sentence candidate is inserted at index 0 in both
quanpin and shuangpin, so it is what Space commits. All four construction
sites built it without a canonical quanpin reading, so when it finished a
word-creation session update_creating_word_progress() had no reading to
join and can_store stayed false: the prefix plus the sentence went to the
host app but were never learned.
Carry the converted quanpin segmentation on those candidates, and extract
the early-return storage condition into a testable predicate.
PR #344 restored the selected prefix in the committed text for Generated
candidates, but the special-candidate early-return branch still skips the
rest of the post-commit work in ProcessSelectionKey.
Two consequences remained:
- The composed phrase was never stored. Word creation persists through
EnqueueStoreUserPhraseTask in the creating-word completion block, which
sits after the early return. Committing "西" + the generated sentence
"你好中国" displayed correctly but was never learned, so the same input
had to be composed again every time.
- g_ai_context was not advanced, so text committed through this branch was
missing from the context sent to the AI suggestion provider.
Store the phrase before returning, restricted to Generated candidates in an
active creating-word state. Both pinyin halves are canonical quanpin
(creating_word.pinyin is accumulated by append_canonical_pinyin, and the
lattice candidate carries the whole-sentence key in canonical_pinyin), so a
single "'" joins them. Syllable/han-character agreement is validated by
create_word_from_canonical_pinyin, which rejects a mismatch instead of
writing a bad row.
Append to the AI context as well. The prefix was already appended by its own
ProcessSelectionKey call on the normal path, so only the current segment is
added here. The trim logic is factored into AppendAiContext and shared with
the existing call site rather than duplicated.
Refs #261.
Most of them come from logging that is compiled out. The disabled DIAG_LOGF /
FANY_IPC_LOG* macros expanded to `((void)0)`, so every HRESULT and parameter
that exists only to be logged looked unreferenced (C4189/C4100). They now
expand to `((void)sizeof(DiscardLogArgs(__VA_ARGS__)))`: the arguments stay in
an unevaluated context, so nothing is computed and no side effect runs, but the
names count as used.
The rest:
- C4100: commented out the parameter names that are genuinely unused
(WinMain's hPrevInstance/nCmdShow, AdjustWndPosition's hwnd/height, the
WebView2 callbacks' sender, and the no-op logging sinks).
- C4189/C4101: removed the dead locals and the unused catch bindings left over
with the log statements.
- C4244: made the DIP-to-pixel and int-to-float conversions explicit in
globals.h, ime_windows.cpp and the menu placement.
- C4456: renamed the shadowing `hwnd` and `lock` locals.
- C4005: guarded the tests' `#define NOMINMAX` with #ifndef; the test targets
already define it on the command line.
Release builds clean and ctest passes for the server, the tests and msimeui.
Warnings under vendor/ are untouched: those are submodules with their own
repositories.
The Server job's Build step dominates the pipeline (~6m37s of a ~9.5m job;
the dictionary/integration test is only ~43s). sccache caches individual
MSVC compiles across runs so an incremental change rebuilds only what it
touched instead of the whole server + engine tree.
sccache only caches an MSVC compile whose debug info is embedded in the
object, so switch the Server build's Release debug info from /Zi to /Z7.
The linker's /DEBUG still emits the beside-the-executable PDB the release
job validates, so the symbol layout is unchanged.
Its compiler-launcher hook is honoured only by the Ninja and Makefile
generators, so configure the Server build with Ninja Multi-Config (keeping
the --config Release / bin/Release layout the later steps depend on) and
add an MSVC-environment step, since Ninja does not locate cl.exe on its own.
Conflict resolutions:
* windows/src/Utils/FanyUtils.h -- develop deleted GetIMEDataDirPath too
(via #273), so both sides had identical content and the whole-file
conflict was only develop's .gitattributes LF normalisation. Took
develop's version; the symbol is now gone repo-wide.
* server/src/skin/candidate_skin_catalog.cpp -- #273 landed the same
wide-path manifest read on develop. Took develop's code verbatim so
this hunk is a no-op against develop, keeping only the comment that
explains why toml::parse_file(manifest.string()) is unusable here.
* server/tests/src/test_quanpin_scheme.cpp (x2) -- union: this branch's
test::Utf8(db_path) plus develop's `both` autocorrect-flag local.
Verified: server Release build clean, 282 tests pass.
The portable test target builds on a bare Linux runner with no vcpkg tree, so
the UTF-8 path helper cannot live in test_framework.h -- including the engine's
data_path.h there broke the sanitizer build. Move it to test_utf8_path.h, which
only the Windows tests that need it include.
Narrow strings in this repo are UTF-8, but std::filesystem converts narrow
strings through the system ANSI code page. The two agree only on ASCII, so any
path crossing that boundary breaks for users whose profile directory is not
ASCII -- and on a code page that cannot represent the characters at all,
path::string() throws rather than mangling.
The user dictionary replay tool hit exactly that. It handed the journal API
paths converted with path::string(); on a Chinese user name the conversion
threw, the exception escaped main(), and MSVC fastfailed the process with
0xC0000409. The installer treats any non-zero exit from replay as fatal, by
design, so those users could not upgrade at all. Build the paths from the wide
form via path_from_utf8/path_to_utf8 instead, and wrap main() so an unexpected
failure reports a diagnosable message and exit 1 rather than a fastfail code.
The skin catalog had the same defect: toml::parse_file(manifest.string())
routed the manifest path through the ANSI code page, and skins live under the
user profile. The throw escaped past the toml handlers around it. Read the file
through the wide path and parse the text instead.
Also drop FanyUtils::GetIMEDataDirPath, which built a path from the ANSI
environment block and dereferenced getenv without a null check. It had no
callers.
Tests cover the three layers this spans: the conversion contract itself, the
journal API in-process, the replay tool as the installer actually launches it,
and the skin catalog. Each non-ASCII case is paired with an ASCII control so a
failure says whether the path encoding or the data caused it. The replay and
skin tests were both confirmed to fail with their fix reverted.
Test fixtures pass paths through the new test::Utf8 helper rather than
path::string(), since the temp directory sits inside the user profile too.
Two independent failures kept the v0.6.5 installer from being built after
the pipe probe was fixed.
The package job died in "Verify the release commit is on main" with git
exiting 128 on dubious ownership: runner instance 2 leaves _work owned by
SYSTEM while the job runs as `runner`. actions/checkout is unaffected, so
the first plain git call is what fails, and only jobs scheduled onto that
instance see it. Mark the workspace as safe in the three self-hosted jobs.
Neither vcpkg manifest carried a builtin-baseline, so vcpkg resolved ports
against the registry at HEAD and every build depended on a live fetch of
microsoft/vcpkg. When that fetch degraded, the ports simply vanished:
Fetching registry information from https://github.com/microsoft/vcpkg (HEAD)...
fmt does not exist
utfcpp does not exist
Build TSF x64 failed this way twice and passed twice on the same runner,
which is what an unpinned registry looks like. Pin both manifests to
04a9d8e5, the commit vcpkg-lock.json already records as resolved.
PrepareCandidateList runs on the shared task thread, so a stall inside it
delays the hide/show messages queued behind it and reaches the screen as
candidate-window flicker. The diagnostic log showed an identical workload
take 8 ms once and 688 ms moments later, but not which segment paid for it.
Split the function into measured segments and emit one "candidate build"
line per keystroke, separating the engine candidate lookup from the
user-dictionary fixed-position pass -- the latter opens a SQLite
connection and runs its schema DDL on every call.
Measurement only; no behaviour change.
The unconditional 50ms hide grace cost typing its snap: every commit left the
candidate window up for an extra frame or three, and when the next word started
inside the window the pending hide was cancelled outright, so the box stopped
disappearing between words and just morphed instead.
Only a hide that waited behind a stalled task thread can describe state the user
has already typed past, and only that one has a show queued behind it worth
waiting for. Measure the queue wait where the task is dispatched and mark the
posted message; normal typing never reaches the threshold, so it hides
immediately exactly as before.
The wait is now logged per hide task, so the threshold can be checked against a
real capture rather than assumed.
Diagnostic logs from a busy machine show the candidate host repainting far
more often than the input changes. Three separate causes, all in the UI
thread's handling of the worker's posted messages:
- A HideCandidate for one keystroke of a burst (backspacing the composition
empty, then typing again) cloaks a visible window and un-cloaks it ~30ms
later. Defer the hide on a short grace timer and drop it when a show
arrives first; a real commit or focus loss still hides, 50ms later.
- Each keystroke posts two WM_SHOW_MAIN_WINDOW messages, since the candidate
rebuild and the English/cloud merge both request a show. Skip a repaint
whose preedit, page, selection and caret match the frame already on screen
within a short window.
- When the worker stalls it drains several keystrokes at once and posts a
show for each. The handler always renders the newest published state, so an
older queued show can only repaint what the newer one is about to; drop it
when a newer show is already queued.
The stall itself (PrepareCandidateList blocking the shared task thread, worst
case 688ms against a median of 11ms) is untouched here.
Elevated setup left high-IL config.toml that medium-IL Settings never
rewrote on machines hit by the ANSI path bug. Make the data directory
writable, migrate ACP-mangled leftovers, rebuild unparseable files, and
keep remaining asset paths on wchar_t.
Fixes#242