Release v0.6.28: typo correction, partial sync and selection diagnostics

This commit is contained in:
2026-11-02 11:19:13 +02:00
parent 738c81dce2
commit 55993c0f90
23 changed files with 676 additions and 76 deletions
+80 -1
View File
@@ -23,6 +23,7 @@ HWND host{}, field{};
ks::Clip saved;
bool captured = false, failed = false;
bool conversionOnly = false;
bool typosOnly = false;
int pasteBehavior = 0;
int enterPresses = 0;
int copyFailuresRemaining = 0, copyRequests = 0;
@@ -427,8 +428,30 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
case 47:
require(text() == L"OLDsnipping ", "recent Cyrillic suffix recovery preserves older prefix");
require(droppedSelectionKeys == 0, "recent suffix regression dropped four selection keys");
app->settings.writeLog = true;
prepare(L"prefix wrong ", false);
record(L"other ");
app->transform(ks::Direct + ru);
break;
case 48:
{
auto rows = ks::parseOperationLog(ks::wide(diagnosticLog()));
require(text() == L"prefix wrong " && !rows.empty() &&
rows.back().outcome == L"recent-verification-mismatch",
"diagnostic mismatch does not replace unverified text");
auto details = rows.back().details;
require(details.find(L"selection-mismatch") != details.npos &&
details.find(L"Expected selection:\r\nother ") != details.npos &&
details.find(L"Read back:\r\nwrong ") != details.npos,
"failed verification includes expected and copied text in exported details");
require(details.find(L"Copy begin number=") != details.npos &&
details.find(L"Copy result number=") != details.npos &&
details.find(L"Selection initial match=0") != details.npos &&
details.find(L"SelectPrevious") != details.npos,
"incident includes clipboard and selection stages");
finish();
break;
}
case 30:
case 32:
case 34:
@@ -597,6 +620,61 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
}
case 22:
require(text() == L"робить ", "automatic correction selects and converts the whole word across an internal comma");
app->settings.autoReplace = true;
app->settings.autoReplacements = {{L"helo", L"hello"}};
ActivateKeyboardLayout(app->layouts[en].handle, 0);
prepare(L"(Helo)! ", false);
record(L"(Helo)! ");
app->transform(ks::AutoCorrect);
break;
case 23:
require(text() == L"(Hello)! ", "explicit typo pair replaces verified text with punctuation and capital");
app->transform(ks::Undo);
break;
case 24:
require(text() == L"(Helo)! ", "Undo restores explicit typo replacement");
app->settings.autoReplace = false;
app->settings.autoTypos = true;
app->settings.autoWords[L"en"].push_back(L"kswitcher");
// Use a personal target to avoid bundled dictionary ambiguity.
prepare(L"kswithcer ", false);
record(L"kswithcer ");
app->transform(ks::AutoCorrect);
break;
case 25:
require(text() == L"kswitcher ", "dictionary typo correction replaces text in the current layout");
require(ks::readClipboard(app->window).text() == sentinel, "typo correction restores clipboard");
app->transform(ks::Undo);
break;
case 26:
require(text() == L"kswithcer ", "Undo restores dictionary typo correction");
record(L"kswithcer ");
app->transform(ks::AutoCorrect);
break;
case 27:
require(text() == L"kswithcer ", "Undo suppresses the rejected spelling correction");
app->settings.autoReplace = true;
app->settings.autoReplacements = {{L"tehh", L"the"}};
app->autoPredictor.configure(L"dictionaries", app->settings.autoLanguages);
prepare(L"", false);
{
std::vector<INPUT> inputs;
for (WORD key : std::wstring(L"TEHH X"))
for (DWORD flags : {DWORD(0), DWORD(KEYEVENTF_KEYUP)})
{
auto input = ks::keyboardInput(key == L' ' ? VK_SPACE : key, flags);
input.ki.dwExtraInfo = 123;
inputs.push_back(input);
}
require(SendInput((UINT)inputs.size(), inputs.data(), sizeof(INPUT)) == inputs.size(),
"custom typo and following word submitted without a pause");
}
break;
case 28:
require(text() == L"the x", "custom replacement through keyboard hook preserves subsequent typing");
app->settings.autoReplace = false;
app->settings.autoTypos = false;
if (typosOnly) { finish(); break; }
stage = 30;
break;
}
@@ -621,6 +699,7 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
int main(int argc, char **argv)
{
conversionOnly = argc > 1 && std::string(argv[1]) == "--conversion-only";
typosOnly = argc > 1 && std::string(argv[1]) == "--typos-only";
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
auto root = std::filesystem::temp_directory_path() /
(L"KSwitcher-integration-" + std::to_wstring(GetCurrentProcessId()));
@@ -776,7 +855,7 @@ int main(int argc, char **argv)
20, 20, 590, 230, host, nullptr, cls.hInstance, nullptr);
deadline = GetTickCount64() + 30000;
SetWindowSubclass(field, observeEnter, 1, 0);
if (argc > 1 && std::string(argv[1]) == "--typing-only")
if (typosOnly || (argc > 1 && std::string(argv[1]) == "--typing-only"))
stage = 20;
if (argc > 1 && std::string(argv[1]) == "--snippets-only")
stage = 30;