fix(windows-ime): 避免 TSF 预检导致文本重复插入 (#1021)
Co-authored-by: tonyzaca7755963 <tonyzaca7755963@users.noreply.github.com>
This commit is contained in:
1 parent
823a6a14cd
commit
82aa33ffd6
3 files changed
+36
-32
No files matched your search
@@ -11,6 +11,7 @@ const launcherPath = join(scriptsDir, "windows-package-msvc.cmd");
|
||||
const ciWorkflowPath = join(repoRoot, ".github", "workflows", "release-tauri.yml");
|
||||
const imeBuildPath = join(scriptsDir, "windows-ime-build.ps1");
|
||||
const imeInstallSmokePath = join(scriptsDir, "windows-ime-install-smoke.ps1");
|
||||
const realAsrInsertionSmokePath = join(scriptsDir, "windows-real-asr-insertion-smoke.ps1");
|
||||
const imeRegisterPath = join(scriptsDir, "windows-ime-register.ps1");
|
||||
const imeUnregisterPath = join(scriptsDir, "windows-ime-unregister.ps1");
|
||||
const imeSolutionPath = join(appRoot, "windows-ime", "OpenLessIme.sln");
|
||||
@@ -26,6 +27,7 @@ const launcher = readFileSync(launcherPath, "utf8");
|
||||
const ciWorkflow = readFileSync(ciWorkflowPath, "utf8");
|
||||
const imeBuild = readFileSync(imeBuildPath, "utf8");
|
||||
const imeInstallSmoke = readFileSync(imeInstallSmokePath, "utf8");
|
||||
const realAsrInsertionSmoke = readFileSync(realAsrInsertionSmokePath, "utf8");
|
||||
const imeRegister = readFileSync(imeRegisterPath, "utf8");
|
||||
const imeUnregister = readFileSync(imeUnregisterPath, "utf8");
|
||||
const imeSolution = readFileSync(imeSolutionPath, "utf8");
|
||||
@@ -109,8 +111,11 @@ assert.match(imeTextService, /TF_ES_ASYNC \| TF_ES_READWRITE/, "IME should retry
|
||||
assert.match(imeTextService, /WaitForSingleObject/, "IME pipe submit should wait for async edit-session completion");
|
||||
assert.match(imeEditSession, /SetEvent/, "IME edit session should signal async completion back to the pipe submitter");
|
||||
assert.match(imeEditSession, /Collapse\(edit_cookie, TF_ANCHOR_END\)/, "IME should collapse the committed range to its end after insertion");
|
||||
assert.match(imeEditSession, /SetSelection\(edit_cookie, 1, &selection\)/, "IME should move the caret to the end of inserted text");
|
||||
assert.match(imeEditSession, /TF_AE_END/, "IME should make the end of the committed text the active selection end");
|
||||
assert.match(imeEditSession, /SetSelection\(edit_cookie, 1, &selection\)/, "IME should move the caret to the end of inserted text");
|
||||
assert.match(imeEditSession, /TF_AE_END/, "IME should make the end of the committed text the active selection end");
|
||||
const insertAtSelectionCalls = imeEditSession.match(/\bInsertTextAtSelection\s*\(/g) ?? [];
|
||||
assert.equal(insertAtSelectionCalls.length, 1, "IME should submit the dictated text to the host exactly once");
|
||||
assert.doesNotMatch(imeEditSession, /TF_IAS_QUERYONLY/, "IME should not preflight the full text through host text stores before committing it");
|
||||
|
||||
assert.match(wixFragment, /DirectoryRef Id="INSTALLDIR"/, "WiX fragment should install into the app directory");
|
||||
assert.match(wixFragment, /Component Id="OpenLessImeDllX64Component"/, "WiX fragment should define the x64 TSF DLL component");
|
||||
@@ -149,6 +154,7 @@ assert.match(imeInstallSmoke, /Join-ProcessArguments/, "install smoke should quo
|
||||
assert.match(imeInstallSmoke, /\$commandLine = Join-ProcessArguments \$ArgumentList/, "install smoke should build a single quoted command line");
|
||||
assert.match(imeInstallSmoke, /Start-Process -FilePath \$FilePath -ArgumentList \$commandLine/, "install smoke should pass a single quoted command line to Start-Process");
|
||||
assert.match(imeInstallSmoke, /OpenLessImeSubmit/, "install smoke should preserve TSF backend context");
|
||||
assert.match(realAsrInsertionSmoke, /\$targetTextForComparison -cne \$finalTextForComparison/, "real insertion smoke should reject duplicate or partial text in initially empty targets");
|
||||
assert.match(imeInstallSmoke, /Software\\Classes\\CLSID\\\{6B9F3F4F-5EE7-42D6-9C61-9F80B03A5D7D\}\\InprocServer32/, "install smoke should check x64 COM registration");
|
||||
assert.match(imeInstallSmoke, /Software\\WOW6432Node\\Classes\\CLSID\\\{6B9F3F4F-5EE7-42D6-9C61-9F80B03A5D7D\}\\InprocServer32/, "install smoke should check x86 COM registration");
|
||||
assert.match(imeInstallSmoke, /LanguageProfile\\0x00000804\\\{9B5F5E04-23F6-47DA-9A26-D221F6C3F02E\}/, "install smoke should check the TSF language profile");
|
||||
|
||||
@@ -1016,7 +1016,13 @@ try {
|
||||
if ([string]::IsNullOrWhiteSpace($targetText)) {
|
||||
throw "$Target readback is empty."
|
||||
}
|
||||
if (-not $targetText.Contains($latest.finalText)) {
|
||||
$targetTextForComparison = $targetText.Replace("`r`n", "`n")
|
||||
$finalTextForComparison = ([string]$latest.finalText).Replace("`r`n", "`n")
|
||||
$targetStartsEmpty = $Target -in @("notepad", "browser", "win32edit")
|
||||
if ($targetStartsEmpty -and $targetTextForComparison -cne $finalTextForComparison) {
|
||||
throw "$Target readback does not exactly match latest finalText in an initially empty target; duplicate or partial insertion detected (expected length=$($finalTextForComparison.Length), actual length=$($targetTextForComparison.Length))."
|
||||
}
|
||||
if (-not $targetStartsEmpty -and -not $targetText.Contains($latest.finalText)) {
|
||||
if ($targetText.Contains($clipboardSentinel)) {
|
||||
throw "$Target readback contains the pre-dictation clipboard sentinel instead of latest finalText."
|
||||
}
|
||||
|
||||
@@ -101,38 +101,30 @@ HRESULT OpenLessEditSession::InsertText(TfEditCookie edit_cookie) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
ITfRange* query_range = nullptr;
|
||||
if (cancellation_ && cancellation_->load()) {
|
||||
insert_at_selection->Release();
|
||||
return HRESULT_FROM_WIN32(ERROR_CANCELLED);
|
||||
}
|
||||
|
||||
// Commit in a single call. Some Chromium-backed text stores surface a
|
||||
// full-text QUERYONLY preflight as an edit and then duplicate the real commit.
|
||||
ITfRange* committed_range = nullptr;
|
||||
hr = insert_at_selection->InsertTextAtSelection(
|
||||
edit_cookie, TF_IAS_QUERYONLY, text_.c_str(),
|
||||
static_cast<LONG>(text_.size()), &query_range);
|
||||
if (query_range != nullptr) {
|
||||
query_range->Release();
|
||||
query_range = nullptr;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr) && cancellation_ && cancellation_->load()) {
|
||||
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
ITfRange* committed_range = nullptr;
|
||||
hr = insert_at_selection->InsertTextAtSelection(
|
||||
edit_cookie, 0, text_.c_str(), static_cast<LONG>(text_.size()),
|
||||
&committed_range);
|
||||
if (committed_range != nullptr) {
|
||||
if (SUCCEEDED(hr)) {
|
||||
const HRESULT collapse_hr =
|
||||
committed_range->Collapse(edit_cookie, TF_ANCHOR_END);
|
||||
if (SUCCEEDED(collapse_hr)) {
|
||||
TF_SELECTION selection = {};
|
||||
selection.range = committed_range;
|
||||
selection.style.ase = TF_AE_END;
|
||||
selection.style.fInterimChar = FALSE;
|
||||
(void)context_->SetSelection(edit_cookie, 1, &selection);
|
||||
}
|
||||
edit_cookie, 0, text_.c_str(), static_cast<LONG>(text_.size()),
|
||||
&committed_range);
|
||||
if (committed_range != nullptr) {
|
||||
if (SUCCEEDED(hr)) {
|
||||
const HRESULT collapse_hr =
|
||||
committed_range->Collapse(edit_cookie, TF_ANCHOR_END);
|
||||
if (SUCCEEDED(collapse_hr)) {
|
||||
TF_SELECTION selection = {};
|
||||
selection.range = committed_range;
|
||||
selection.style.ase = TF_AE_END;
|
||||
selection.style.fInterimChar = FALSE;
|
||||
(void)context_->SetSelection(edit_cookie, 1, &selection);
|
||||
}
|
||||
committed_range->Release();
|
||||
}
|
||||
committed_range->Release();
|
||||
}
|
||||
|
||||
insert_at_selection->Release();
|
||||
|
||||
Reference in new issue
Block a user