Release v0.2.0 with incident diagnostics and in-app log viewer
This commit is contained in:
+94
-1
@@ -1,5 +1,7 @@
|
||||
#include "app.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <commctrl.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -14,6 +16,12 @@ const std::vector<std::pair<std::wstring, std::wstring>> punctuationCases{
|
||||
{L"ljvf: ", L"дома: "}, {L"ljvf; ", L"дома; "}, {L"#ljvf%; ", L"#дома%; "}};
|
||||
ULONGLONG deadline{};
|
||||
std::wstring sentinel = L"KSwitcher integration clipboard sentinel";
|
||||
std::string diagnosticLog()
|
||||
{
|
||||
std::ifstream file(ks::dataDirectory() / L"kswitcher.log");
|
||||
return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
size_t diagnosticStart{};
|
||||
void require(bool ok, const char *name)
|
||||
{
|
||||
if (!ok)
|
||||
@@ -109,10 +117,28 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
require(text() == L"OLDпривет", "recent-input conversion preserves adjacent older text");
|
||||
prepare(L"OLDabcdef", false);
|
||||
record(L"ghbdtn");
|
||||
diagnosticStart = diagnosticLog().size();
|
||||
app->busy = true;
|
||||
app->transform(ks::Direct + ru);
|
||||
app->transform(ks::Direct + ru);
|
||||
app->busy = false;
|
||||
app->transform(ks::Direct + ru);
|
||||
break;
|
||||
case 6:
|
||||
require(text() == L"OLDabcdef", "recent-input mismatch refuses replacement");
|
||||
{
|
||||
auto diagnostics = diagnosticLog();
|
||||
diagnostics = diagnostics.substr(std::min(diagnosticStart, diagnostics.size()));
|
||||
require(diagnostics.find("reason=busy") != std::string::npos,
|
||||
"busy conversion attempt is logged");
|
||||
require(diagnostics.find("repeat=3") != std::string::npos,
|
||||
"three repeated conversion attempts are logged");
|
||||
require(diagnostics.find("reason=recent-verification-mismatch") != std::string::npos,
|
||||
"failed recent-word verification has explicit outcome");
|
||||
require(diagnostics.find("copied=\"abcdef\"") != std::string::npos &&
|
||||
diagnostics.find("physicalKeys=\"ghbdtn\"") != std::string::npos,
|
||||
"conversion diagnostics contain actual text and physical input for reproduction");
|
||||
}
|
||||
app->settings.snippets = {{L";addr", L"LINE1\r\n{cursor}LINE2"}};
|
||||
prepare(L"prefix ;addr", false);
|
||||
record(L";addr");
|
||||
@@ -237,7 +263,7 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
return DefWindowProcW(w, msg, wp, lp);
|
||||
}
|
||||
} // namespace
|
||||
int main()
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
auto root = std::filesystem::temp_directory_path() /
|
||||
@@ -249,6 +275,73 @@ int main()
|
||||
app = &instance;
|
||||
app->settings.history = false;
|
||||
app->settings.switchLayout = false;
|
||||
if (argc > 1 && std::string(argv[1]) == "--logs-only")
|
||||
{
|
||||
ks::showLogs(instance);
|
||||
HWND viewer = instance.logWindow;
|
||||
HWND rows = GetDlgItem(viewer, 602);
|
||||
require(rows && (GetWindowLongPtrW(rows, GWL_STYLE) & LVS_TYPEMASK) == LVS_REPORT,
|
||||
"log viewer uses report ListView");
|
||||
require(Header_GetItemCount(ListView_GetHeader(rows)) == 4, "log viewer has four columns");
|
||||
require(GetDlgItem(viewer, IDCANCEL) != rows, "log rows and Close have different IDs");
|
||||
SetFocus(rows);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(IDCANCEL, EN_SETFOCUS), (LPARAM)rows);
|
||||
NMHDR clicked{rows, 602, NM_CLICK};
|
||||
SendMessageW(viewer, WM_NOTIFY, 602, (LPARAM)&clicked);
|
||||
require(IsWindow(viewer), "focus and click notifications do not close log viewer");
|
||||
ks::showLogs(instance);
|
||||
require(instance.logWindow == viewer, "opening logs reuses the existing window");
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(603, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 603));
|
||||
require(IsWindow(viewer) && ListView_GetItemCount(rows) > 0, "refresh populates log rows");
|
||||
SendDlgItemMessageW(viewer, 601, CB_SETCURSEL, 1, 0);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(601, CBN_SELCHANGE), (LPARAM)GetDlgItem(viewer, 601));
|
||||
require(IsWindow(viewer) && ListView_GetItemCount(rows) > 0, "previous log or empty state is displayed");
|
||||
if (argc > 2 && std::string(argv[2]) == "--clear-test")
|
||||
{
|
||||
require(std::filesystem::path(ks::executable()).parent_path().filename() == L"log-clear-test",
|
||||
"clear test runs in isolated executable directory");
|
||||
const auto logRoot = ks::dataDirectory();
|
||||
require(logRoot == std::filesystem::path(ks::executable()).parent_path() / L"data",
|
||||
"clear test uses isolated portable data");
|
||||
std::ofstream(logRoot / L"kswitcher.log") << "current-marker\n";
|
||||
std::ofstream(logRoot / L"kswitcher.log.1") << "previous-marker\n";
|
||||
ks::diagnosticEvent(L"buffered-before-clear");
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(606, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 606));
|
||||
require(!std::filesystem::exists(logRoot / L"kswitcher.log.1") &&
|
||||
ks::readDiagnosticLog(false).find(L"current-marker") != std::wstring::npos,
|
||||
"Clear log removes selected previous log only");
|
||||
SendDlgItemMessageW(viewer, 601, CB_SETCURSEL, 0, 0);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(606, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 606));
|
||||
require(!std::filesystem::exists(logRoot / L"kswitcher.log") && IsWindow(viewer),
|
||||
"Clear log clears current file without closing viewer");
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(606, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 606));
|
||||
ks::diagnosticEvent(L"new-problem", true);
|
||||
auto newLog = ks::readDiagnosticLog(false);
|
||||
require(newLog.find(L"new-problem") != std::wstring::npos &&
|
||||
newLog.find(L"buffered-before-clear") == std::wstring::npos,
|
||||
"logging resumes after clear without stale context");
|
||||
}
|
||||
auto savedClipboard = ks::readClipboard(viewer);
|
||||
instance.window = viewer;
|
||||
try
|
||||
{
|
||||
ListView_SetItemState(rows, 0, LVIS_SELECTED, LVIS_SELECTED);
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(605, BN_CLICKED), (LPARAM)GetDlgItem(viewer, 605));
|
||||
require(ks::readClipboard(viewer).text().find(L'\t') != std::wstring::npos,
|
||||
"selected log row copies as tab-separated columns");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ks::writeClipboard(viewer, savedClipboard);
|
||||
throw;
|
||||
}
|
||||
ks::writeClipboard(viewer, savedClipboard);
|
||||
instance.window = nullptr;
|
||||
SendMessageW(viewer, WM_COMMAND, MAKEWPARAM(IDCANCEL, BN_CLICKED), (LPARAM)GetDlgItem(viewer, IDCANCEL));
|
||||
require(!IsWindow(viewer) && !instance.logWindow, "Close button closes and releases viewer");
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < (int)app->layouts.size(); ++i)
|
||||
{
|
||||
auto language = LOWORD((uintptr_t)app->layouts[i].handle);
|
||||
|
||||
Reference in New Issue
Block a user