Release v0.6.22: recover verified partial text selections

This commit is contained in:
2026-10-31 12:32:26 +02:00
parent 64a04b4382
commit c0ab02df16
12 changed files with 103 additions and 14 deletions
+25 -3
View File
@@ -227,7 +227,8 @@ class Transaction
return selectPrevious(n, [&] { return valid(); });
}
std::wstring copyRange(size_t count, const std::wstring &wordCandidate,
const std::function<bool(const std::wstring &)> &matches)
const std::function<bool(const std::wstring &)> &matches,
const std::function<bool(const std::wstring &)> &suffixMatches = {})
{
// WebView word boundaries are only candidates; exact verification is mandatory.
if (needsSelectionDelay(target) && !wordCandidate.empty() &&
@@ -247,7 +248,25 @@ class Transaction
return {};
Sleep(25);
}
return select(count) ? copy() : std::wstring{};
auto text = select(count) ? copy() : std::wstring{};
// Chromium editors can apply only part of a burst of Shift+Left events.
// Extend only a verified suffix; never replace a partial or unrelated range.
if (valid() && !matches(text) && !text.empty() &&
(suffixMatches ? suffixMatches(text) : newlines(wordCandidate).ends_with(newlines(text))) &&
caretSteps(text) < count)
{
const auto missing = count - caretSteps(text);
diagnosticEvent(L"Selection: extending verified suffix by " + std::to_wstring(missing));
for (size_t i = 0; i < missing; ++i)
{
if (!valid() || !chord(VK_LEFT, MOD_SHIFT))
return {};
Sleep(25);
}
text = copy();
diagnosticEvent(L"Selection: suffix recovery match=" + std::to_wstring(matches(text)));
}
return text;
}
bool paste(const Clip &clip)
{
@@ -1138,7 +1157,9 @@ void App::transform(int command, int snippet)
{
text =
tx.copyRange(recent.keys.size(), recent.keys, [&](const std::wstring &candidate)
{ return recent.match(candidate, available, physicalLayout()) >= 0; });
{ return recent.match(candidate, available, physicalLayout()) >= 0; },
[&](const std::wstring &candidate)
{ return recent.matchSuffix(candidate, available, physicalLayout()) >= 0; });
hint = recent.match(text, available, physicalLayout());
diagnosticEvent(L"Recent verification copied=" + std::to_wstring(text.size()) + L" keys=" +
std::to_wstring(recent.keys.size()) + L" match=" + std::to_wstring(hint));
@@ -1155,6 +1176,7 @@ void App::transform(int command, int snippet)
previous->version == version)
{
expected = previous->result;
diagnosticEvent(L"Previous result expected=" + diagnosticText(expected));
text = tx.copyRange(caretSteps(previous->result), previous->result,
[&](const std::wstring &candidate)
{ return newlines(candidate) == newlines(previous->result); });
+8
View File
@@ -479,6 +479,14 @@ void Run::backspace(HWND w, ULONGLONG now)
keys.pop_back();
at = now;
}
int Run::matchSuffix(const std::wstring &text, const std::vector<Layout> &layouts, const Layout &physical) const
{
if (text.empty() || text.size() >= keys.size() || overflow)
return -1;
Run tail = *this;
tail.keys = keys.substr(keys.size() - text.size());
return tail.match(text, layouts, physical);
}
int Run::match(const std::wstring &text, const std::vector<Layout> &layouts, const Layout &physical) const
{
if (text.size() != keys.size() || overflow)
+1
View File
@@ -94,5 +94,6 @@ struct Run
void record(wchar_t, HWND, HKL, ULONGLONG);
void backspace(HWND, ULONGLONG);
int match(const std::wstring &, const std::vector<Layout> &, const Layout &physical) const;
int matchSuffix(const std::wstring &, const std::vector<Layout> &, const Layout &physical) const;
};
} // namespace ks
+1 -1
View File
@@ -2,7 +2,7 @@
#include "platform.h"
namespace ks
{
inline constexpr wchar_t AppVersion[] = L"0.6.21";
inline constexpr wchar_t AppVersion[] = L"0.6.22";
inline constexpr UINT UpdateMessage = WM_APP + 7;
inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000;
inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000;