Add configurable offline layout correction and latest Windows release
This commit is contained in:
+153
-1
@@ -1,3 +1,4 @@
|
||||
#include "autocorrect.h"
|
||||
#include "ocr.h"
|
||||
#include "richtext.h"
|
||||
#include "storage.h"
|
||||
@@ -29,10 +30,34 @@ void rejects(const std::function<void()> &f, const char *name)
|
||||
require(thrown, name);
|
||||
}
|
||||
} // namespace
|
||||
int main()
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1 && std::string(argv[1]) == "--hidden-start-window")
|
||||
{
|
||||
HWND w = CreateWindowExW(0, L"STATIC", L"KSwitcher visibility regression", WS_POPUP, 0, 0, 1, 1,
|
||||
nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||
ks::showRequestedWindow(w);
|
||||
bool visible = w && IsWindowVisible(w);
|
||||
if (w)
|
||||
DestroyWindow(w);
|
||||
return visible ? 0 : 1;
|
||||
}
|
||||
try
|
||||
{
|
||||
STARTUPINFOW startup{sizeof(startup)};
|
||||
startup.dwFlags = STARTF_USESHOWWINDOW;
|
||||
startup.wShowWindow = SW_HIDE;
|
||||
PROCESS_INFORMATION child{};
|
||||
std::wstring command = L"\"" + ks::executable() + L"\" --hidden-start-window";
|
||||
require(CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr,
|
||||
&startup, &child),
|
||||
"start visibility test with hidden startup state");
|
||||
DWORD wait = WaitForSingleObject(child.hProcess, 10000), exitCode = 1;
|
||||
GetExitCodeProcess(child.hProcess, &exitCode);
|
||||
CloseHandle(child.hThread);
|
||||
CloseHandle(child.hProcess);
|
||||
require(wait == WAIT_OBJECT_0 && exitCode == 0,
|
||||
"first explicit window request is visible after hidden process launch");
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
HWND richParent = CreateWindowExW(0, L"STATIC", L"Rich preview test", WS_POPUP, 0, 0, 300, 200,
|
||||
nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||
@@ -93,6 +118,100 @@ int main()
|
||||
require(ks::convert(L"hello привет", en, ru) == L"руддщ привет", "mixed text preserves target words");
|
||||
require(ks::convert(L"htdm.dftim", en, ru) == L"ревьюваешь", "punctuation in words");
|
||||
std::vector<ks::Layout> layouts{en, ru, uk};
|
||||
ks::AutoCorrector corrector;
|
||||
ks::Settings autoSettings;
|
||||
require(!autoSettings.autoCorrect, "auto-correction defaults off");
|
||||
autoSettings.cycle = {L"en", L"uk"};
|
||||
corrector.configure(L"dictionaries", autoSettings.autoLanguages);
|
||||
auto suggested = corrector.suggest(L"ghbdsn ", 0, layouts, autoSettings);
|
||||
require(suggested && suggested->text == L"привіт " && suggested->target == 2,
|
||||
"dictionary corrects Ukrainian and retains trailing space");
|
||||
suggested = corrector.suggest(L"lsv ", 0, layouts, autoSettings);
|
||||
require(suggested && suggested->text == L"дім " && suggested->target == 2,
|
||||
"three-letter Ukrainian word is corrected");
|
||||
autoSettings.autoMinLength = 4;
|
||||
require(!corrector.suggest(L"lsv ", 0, layouts, autoSettings),
|
||||
"configured minimum excludes short words");
|
||||
require(!corrector.suggest(L"(lsv); ", 0, layouts, autoSettings),
|
||||
"punctuation does not count towards minimum length");
|
||||
autoSettings.autoMinLength = 3;
|
||||
require(!corrector.suggest(L"the ", 0, layouts, autoSettings),
|
||||
"known three-letter English word stays unchanged");
|
||||
require(!corrector.suggest(L"ls ", 0, layouts, autoSettings),
|
||||
"two-letter input remains below automatic correction minimum");
|
||||
suggested = corrector.suggest(L"ljvfiyz ", 0, layouts, autoSettings);
|
||||
require(suggested && suggested->text == L"домашня " && suggested->target == 2,
|
||||
"less frequent Ukrainian inflections are eligible for correction");
|
||||
require(autoSettings.autoMaxRank == 0, "default frequency limit includes entire dictionary");
|
||||
autoSettings.autoMaxRank = 21944;
|
||||
require(!corrector.suggest(L"ljvfiyz ", 0, layouts, autoSettings), "rank above limit is excluded");
|
||||
autoSettings.autoMaxRank = 21945;
|
||||
require(corrector.suggest(L"ljvfiyz ", 0, layouts, autoSettings).has_value(),
|
||||
"rank limit is inclusive");
|
||||
autoSettings.autoMaxRank = 1;
|
||||
require(!corrector.suggest(L"hello ", 0, layouts, autoSettings),
|
||||
"rank limit never disables known-word protection");
|
||||
autoSettings.autoMaxRank = 0;
|
||||
suggested = corrector.suggest(L"(ljvf ", 0, layouts, autoSettings);
|
||||
require(suggested && suggested->text == L"(дома ", "opening parenthesis is preserved");
|
||||
for (auto pair : {std::pair{L"(ljvf) ", L"(дома) "},
|
||||
{L"[ljvf] ", L"[дома] "},
|
||||
{L"{ljvf} ", L"{дома} "},
|
||||
{L"«ljvf» ", L"«дома» "},
|
||||
{L"“ljvf” ", L"“дома” "},
|
||||
{L"ljvf) ", L"дома) "},
|
||||
{L"([{ljvf}]), ", L"([{дома}]), "}})
|
||||
{
|
||||
suggested = corrector.suggest(pair.first, 0, layouts, autoSettings);
|
||||
require(suggested && suggested->text == pair.second, "paired and closing delimiters preserved");
|
||||
}
|
||||
require(ks::autoClosingSelection(L")] "), "closing-only selection can be extended");
|
||||
require(!ks::autoClosingSelection(L"word) ") && !ks::autoClosingSelection(L"@ "),
|
||||
"selection extension excludes words and arbitrary punctuation");
|
||||
require(!corrector.suggest(L"user@(ljvf) ", 0, layouts, autoSettings),
|
||||
"wrapped email suffix is not a word");
|
||||
autoSettings.autoPrefixChars += L"#";
|
||||
autoSettings.autoSuffixChars += L"%";
|
||||
suggested = corrector.suggest(L"#ljvf%; ", 0, layouts, autoSettings);
|
||||
require(suggested && suggested->text == L"#дома%; ", "custom delimiters are preserved");
|
||||
require(ks::autoClosingSelection(L"%; ", autoSettings), "custom closing selection is supported");
|
||||
autoSettings.autoPrefixChars.clear();
|
||||
require(!corrector.suggest(L"(ljvf ", 0, layouts, autoSettings),
|
||||
"removed prefix delimiter is respected");
|
||||
autoSettings.autoSuffixChars.clear();
|
||||
require(!corrector.suggest(L"ljvf; ", 0, layouts, autoSettings),
|
||||
"removed suffix delimiter is respected");
|
||||
autoSettings.autoPrefixChars = ks::Settings{}.autoPrefixChars;
|
||||
autoSettings.autoSuffixChars = ks::Settings{}.autoSuffixChars;
|
||||
ks::Run parenthesized;
|
||||
parenthesized.keys = L"(ljvf ";
|
||||
require(ks::matchAutoInput(parenthesized, L"ljvf ", layouts, en) == 0,
|
||||
"editor selection may omit the opening parenthesis");
|
||||
require(ks::matchAutoInput(parenthesized, L"(ljvf ", layouts, en) == 0,
|
||||
"editor selection may include the opening parenthesis");
|
||||
parenthesized.keys = L"OLDljvf ";
|
||||
require(ks::matchAutoInput(parenthesized, L"ljvf ", layouts, en) < 0,
|
||||
"automatic verification never accepts an arbitrary word suffix");
|
||||
suggested = corrector.suggest(L"Ghbdsn! ", 0, layouts, autoSettings);
|
||||
require(suggested && suggested->text == L"Привіт! ", "title case and punctuation retained");
|
||||
suggested = corrector.suggest(L"руддщ ", 2, layouts, autoSettings);
|
||||
require(suggested && suggested->text == L"hello ", "dictionary corrects English");
|
||||
for (auto word : {L"hello ", L"привіт ", L"GHBDSN ", L"ghBdsn ", L"ghbdsn123 ", L"user@ghbdsn ",
|
||||
L"https://ghbdsn ", L"gh ", L"qzxqzxqzx ", L"oldghbdsn "})
|
||||
require(!corrector.suggest(word, 0, layouts, autoSettings), "unsafe or known word unchanged");
|
||||
autoSettings.autoNever = {L"ghbdsn"};
|
||||
require(!corrector.suggest(L"Ghbdsn ", 0, layouts, autoSettings), "personal exception ignores case");
|
||||
autoSettings.autoNever.clear();
|
||||
autoSettings.cycle = {L"en"};
|
||||
require(!corrector.suggest(L"ghbdsn ", 0, layouts, autoSettings), "disabled target layout excluded");
|
||||
autoSettings.cycle = {L"en", L"uk"};
|
||||
corrector.reject(L"Ghbdsn; ");
|
||||
require(!corrector.suggest(L"ghbdsn ", 0, layouts, autoSettings),
|
||||
"undo rejection survives punctuation");
|
||||
require(corrector.memoryBytes() < 3 * 1024 * 1024, "two dictionaries fit within 3 MiB");
|
||||
require(!ks::autoCorrectionAllowed(nullptr, autoSettings), "missing focused editor fails closed");
|
||||
ks::AutoCorrector missing;
|
||||
rejects([&] { missing.configure(L"missing-dictionaries", {L"en"}); }, "missing dictionary rejected");
|
||||
require(ks::convertTo(L"ghbdtn", layouts, 1, 0) == L"привет", "hint conversion");
|
||||
require(ks::convertTo(L"привет", layouts, 1, 1) == L"привет", "same source is unchanged");
|
||||
require(ks::convertTo(L"?", layouts, 1, 0) == L",", "punctuation conversion");
|
||||
@@ -228,6 +347,13 @@ int main()
|
||||
}
|
||||
ks::Settings settings;
|
||||
settings.elevatedAutostart = true;
|
||||
settings.autoCorrect = true;
|
||||
settings.autoMaxRank = 10000;
|
||||
settings.autoMinLength = 5;
|
||||
settings.autoPrefixChars = L"(#";
|
||||
settings.autoSuffixChars = L");%";
|
||||
settings.autoNever = {L"myWord"};
|
||||
settings.autoExcluded = {L"Code.exe"};
|
||||
settings.cycle = {L"en", L"uk"};
|
||||
settings.snippets = {{L";address", L"Line 1\r\n{cursor}Line 2"}};
|
||||
ks::saveSettings(temp / L"settings.json", settings);
|
||||
@@ -235,6 +361,32 @@ int main()
|
||||
require(restored.snippets[0].text == settings.snippets[0].text, "settings snippet roundtrip");
|
||||
require(restored.cycle == settings.cycle, "layout order roundtrip");
|
||||
require(restored.elevatedAutostart, "elevated autostart setting roundtrip");
|
||||
require(restored.autoMaxRank == 10000, "frequency rank limit roundtrip");
|
||||
require(restored.autoMinLength == 5, "minimum word length roundtrip");
|
||||
require(ks::loadSettings(temp / L"defaults.json", {}).autoMinLength == 3,
|
||||
"missing minimum length defaults to three");
|
||||
for (int badLength : {0, 33})
|
||||
{
|
||||
auto invalidLength = settings;
|
||||
invalidLength.autoMinLength = badLength;
|
||||
rejects([&] { ks::validateSettings(invalidLength); }, "invalid minimum word length rejected");
|
||||
}
|
||||
require(restored.autoPrefixChars == settings.autoPrefixChars &&
|
||||
restored.autoSuffixChars == settings.autoSuffixChars,
|
||||
"custom punctuation settings roundtrip");
|
||||
auto invalidCharacters = settings;
|
||||
invalidCharacters.autoPrefixChars = L"a";
|
||||
rejects([&] { ks::validateSettings(invalidCharacters); },
|
||||
"letters cannot be treated as word delimiters");
|
||||
require(ks::loadSettings(temp / L"defaults.json", {}).autoMaxRank == 0,
|
||||
"missing frequency limit defaults to all words");
|
||||
auto invalidRank = settings;
|
||||
invalidRank.autoMaxRank = -1;
|
||||
rejects([&] { ks::validateSettings(invalidRank); }, "negative frequency limit rejected");
|
||||
require(restored.autoCorrect && restored.autoLanguages == settings.autoLanguages &&
|
||||
restored.autoNever == settings.autoNever &&
|
||||
restored.autoExcluded == settings.autoExcluded,
|
||||
"auto-correction settings roundtrip");
|
||||
require(!ks::loadSettings(temp / L"defaults.json", {}).elevatedAutostart,
|
||||
"elevated autostart is opt-in for existing and default settings");
|
||||
auto invalid = settings;
|
||||
|
||||
Reference in New Issue
Block a user