Fix partial snippet selection in desktop chat editors

This commit is contained in:
agrynco committed 2026-11-06 14:58:24 +02:00
1 parent 63b7b879a3
commit 99db99e603
5 files changed
+45 -10

No files matched your search

+9 -3
View File
@@ -1490,11 +1490,17 @@ void App::transform(int command, int snippet)
}
Run tail = recent;
tail.keys = tail.keys.substr(tail.keys.size() - sn.trigger.size());
if (!tx.select(sn.trigger.size()))
return;
autoSelected = true;
reason = L"snippet-verification-mismatch";
text = tx.copy();
if (farSnippet)
{
if (!tx.select(sn.trigger.size())) return;
text = tx.copy();
}
else
text = tx.copyRange(sn.trigger.size(), tail.keys,
[&](const std::wstring &candidate) { return tail.match(candidate, available, physicalLayout()) >= 0; },
[&](const std::wstring &candidate) { return tail.matchSuffix(candidate, available, physicalLayout()) >= 0; });
if (tail.match(text, available, physicalLayout()) < 0)
return;
automatic = true;
+1 -1
View File
@@ -435,7 +435,7 @@ bool needsSelectionDelay(HWND target)
{
const auto name = lower(processName(target));
return name == L"ms-teams" || name == L"msedgewebview2" ||
name == L"chrome" || name == L"msedge";
name == L"chrome" || name == L"msedge" || name == L"chatgpt" || name == L"codex";
}
bool selectPrevious(size_t count, const std::function<bool()> &valid)
{
+9 -1
View File
@@ -21,6 +21,7 @@ void contextChecks()
ks::validateSettings(scoped);
require(ks::snippetAppPriority(scoped.snippets[1], L"ms-teams") > ks::snippetAppPriority(scoped.snippets[0], L"ms-teams"), "app snippet precedes global regardless of list order");
require(ks::snippetAppPriority(scoped.snippets[1], L"Telegram.exe") == 0, "restricted snippet cannot run in another app");
require(ks::snippetAppPriority(scoped.snippets[1], L"Outlook.EXE") == 2, "restricted snippet also runs in its second application");
require(ks::snippetAppPriority(scoped.snippets[0], L"") == 1, "legacy/global snippets remain available");
auto scopedRoundtrip = ks::decodeSettings(ks::encodeSettings(scoped), {});
require(scopedRoundtrip.snippets[1].apps == std::vector<std::wstring>{L"ms-teams", L"outlook"}, "scopes normalize and persist");
@@ -257,7 +258,14 @@ int main()
require(IsDlgButtonChecked(snippets,314)==BST_UNCHECKED, "old snippet shows everywhere");
CheckDlgButton(snippets,314,BST_CHECKED);
SendMessageW(snippets,WM_COMMAND,314,0);
SetDlgItemTextW(snippets,315,L"Notepad.exe, MS-Teams.EXE");
SetDlgItemTextW(snippets,315,L"Notepad.exe");
auto secondApp = SendDlgItemMessageW(snippets,316,CB_ADDSTRING,0,(LPARAM)L"MS-Teams.EXE");
SendDlgItemMessageW(snippets,316,CB_SETCURSEL,secondApp,0);
SendMessageW(snippets,WM_COMMAND,317,0);
SendMessageW(snippets,WM_COMMAND,317,0);
wchar_t appList[128]{};
GetDlgItemTextW(snippets,315,appList,128);
require(std::wstring(appList)==L"Notepad.exe, MS-Teams.EXE", "Add app appends a second application without duplicates");
SendMessageW(snippets,WM_COMMAND,IDOK,0);
require(!app.snippetsWindow && app.settings.snippets[0].apps==std::vector<std::wstring>{L"notepad",L"ms-teams"}, "snippet editor saves selected applications");
require(!saveEnabled() && status()==L"No unsaved changes.","separately saved snippet edit leaves Settings clean");
+24 -3
View File
@@ -22,6 +22,7 @@ namespace
ks::App *app{};
HWND host{}, field{};
ks::Clip saved;
bool snippetRecoveryOnly = false;
bool captured = false, failed = false;
bool conversionOnly = false;
bool typosOnly = false;
@@ -588,10 +589,29 @@ LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
require(ks::readClipboard(app->window).text() == sentinel, "selected and repeated conversions restore clipboard");
finish();
break;
case 62:
case 64:
saved = ks::readClipboard(app->window, 64 * 1024 * 1024);
captured = true;
app->ownSequence = ks::writeClipboard(app->window, ks::textClip(sentinel));
app->settings.snippets = {{L";repo-changes-jira", L"expanded{cursor}"}};
ActivateKeyboardLayout(app->layouts[en].handle, 0);
prepare(L"prefix ;repo-changes-jira", false);
record(L";repo-changes-jira");
droppedSelectionKeys = 10;
enterPresses = 0;
app->transform(0, 0);
break;
case 65:
require(text() == L"prefix expanded", "long snippet recovers partial selection and preserves prefix");
require(droppedSelectionKeys == 0 && enterPresses == 0, "snippet recovery consumes missing arrows without Enter");
require(ks::readClipboard(app->window).text() == sentinel, "snippet recovery restores clipboard");
if (snippetRecoveryOnly) { finish(); break; }
stage = 62;
break;
case 62:
if (!captured) saved = ks::readClipboard(app->window, 64 * 1024 * 1024);
captured = true;
app->ownSequence = ks::writeClipboard(app->window, ks::textClip(sentinel));
app->settings.snippets = {{L";r", L"unexpected{enter}"}};
ActivateKeyboardLayout(app->layouts[en].handle, 0);
prepare(L"prefix ", false);
@@ -1038,8 +1058,9 @@ int main(int argc, char **argv)
SetWindowSubclass(field, observeEnter, 1, 0);
if (typosOnly || (argc > 1 && std::string(argv[1]) == "--typing-only"))
stage = 20;
if (argc > 1 && std::string(argv[1]) == "--snippets-only")
stage = 62;
snippetRecoveryOnly = argc > 1 && std::string(argv[1]) == "--snippet-recovery-only";
if (snippetRecoveryOnly || (argc > 1 && std::string(argv[1]) == "--snippets-only"))
stage = 64;
SetTimer(host, 1, 100, nullptr);
auto forwarding = SetWindowsHookExW(WH_KEYBOARD_LL,forwardingHook,GetModuleHandleW(nullptr),0);
require(forwarding!=nullptr,"downstream forwarding fixture installed before application hook");
+2 -2
View File
@@ -145,9 +145,9 @@ int main()
}
{
Settings scoped = local;
scoped.snippets.push_back({L"test", L"Only Teams", L"teams-scope", {L"ms-teams"}});
scoped.snippets.push_back({L"test", L"Work apps", L"teams-scope", {L"ms-teams", L"outlook"}});
auto wire=sharedSyncSettings(scoped);
require(restoreSyncSettings(wire, local, {}).snippets[1].apps==std::vector<std::wstring>{L"ms-teams"}, "sync preserves app scope");
require(restoreSyncSettings(wire, local, {}).snippets[1].apps==std::vector<std::wstring>{L"ms-teams", L"outlook"}, "sync preserves multiple app scopes");
auto leftScope=j,rightScope=j;
leftScope["snippets"].push_back({{"id","left-scope"},{"trigger","same"},{"text","left"},{"apps",Json::array({"notepad"})}});
rightScope["snippets"].push_back({{"id","right-scope"},{"trigger","same"},{"text","right"},{"apps",Json::array({"ms-teams"})}});