Add application-scoped snippets with global fallback and scope-aware sync

This commit is contained in:
agrynco committed 2026-11-06 10:37:01 +02:00
1 parent c18148a574
commit a124e030ab
10 files changed
+215 -18

No files matched your search

+68 -13
View File
@@ -1712,14 +1712,27 @@ void updateSnippetEditor(HWND w, SnippetView *v)
bool selected = v->selection >= 0 && v->selection < (int)v->snippets.size();
value(w, 302, selected ? v->snippets[v->selection].trigger : L"");
value(w, 303, selected ? v->snippets[v->selection].text : L"");
for (int id : {302, 303, 311, 312, 313})
std::wstring apps;
if (selected) for (const auto &app : v->snippets[v->selection].apps)
{
if (!apps.empty()) apps += L", ";
if (!app.empty()) apps += app + L".exe";
}
value(w, 315, apps);
bool restricted = selected && !v->snippets[v->selection].apps.empty();
CheckDlgButton(w, 314, restricted ? BST_CHECKED : BST_UNCHECKED);
for (int id : {302, 303, 311, 312, 313, 314})
EnableWindow(GetDlgItem(w, id), selected);
for (int id : {315, 316, 317}) EnableWindow(GetDlgItem(w, id), selected && restricted);
}
void fillSnippets(HWND w, SnippetView *v)
{
SendDlgItemMessageW(w, 301, LB_RESETCONTENT, 0, 0);
for (auto &sn : v->snippets)
SendDlgItemMessageW(w, 301, LB_ADDSTRING, 0, (LPARAM)sn.trigger.c_str());
{
auto label = sn.trigger + (sn.apps.empty() ? L" [All apps]" : L" [" + sn.apps.front() + (sn.apps.size() > 1 ? L", ...]" : L"]"));
SendDlgItemMessageW(w, 301, LB_ADDSTRING, 0, (LPARAM)label.c_str());
}
if (v->selection >= 0)
SendDlgItemMessageW(w, 301, LB_SETCURSEL, v->selection, 0);
updateSnippetEditor(w, v);
@@ -1732,6 +1745,18 @@ void collectSnippet(HWND w, SnippetView *v)
if (snippet.id.empty()) snippet.id = snippetId(snippet);
snippet.trigger = value(w, 302);
snippet.text = value(w, 303);
snippet.apps.clear();
if (IsDlgButtonChecked(w, 314) == BST_CHECKED)
{
std::wistringstream input(value(w, 315)); std::wstring item;
while (std::getline(input, item, L','))
{
auto app = snippetAppName(item);
if (!app.empty()) snippet.apps.push_back(app);
}
// An explicitly restricted but empty list must not become global.
if (snippet.apps.empty()) snippet.apps.push_back(L"");
}
}
}
LRESULT CALLBACK snippetProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
@@ -1758,6 +1783,24 @@ LRESULT CALLBACK snippetProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
case WM_COMMAND:
{
int id = LOWORD(wp);
if (id == 314)
{
bool restricted = IsDlgButtonChecked(w, 314) == BST_CHECKED;
for (int field : {315, 316, 317}) EnableWindow(GetDlgItem(w, field), restricted);
return 0;
}
if (id == 317)
{
auto app = value(w, 316);
if (!app.empty())
{
auto current = value(w, 315);
std::wistringstream input(current); std::wstring entry;
while (std::getline(input, entry, L',')) if (snippetAppName(entry) == snippetAppName(app)) return 0;
value(w, 315, current.empty() ? app : current + L", " + app);
}
return 0;
}
if (id == 301 && HIWORD(wp) == LBN_SELCHANGE)
{
collectSnippet(w, v);
@@ -2046,22 +2089,34 @@ void showSnippets(App &app)
SetForegroundWindow(app.snippetsWindow);
return;
}
HWND w = makeWindow(app, L"KSwitcher.Snippets", L"Text snippets", snippetProc, 740, 520);
HWND w = makeWindow(app, L"KSwitcher.Snippets", L"Text snippets", snippetProc, 740, 620);
app.snippetsWindow = w;
label(w, L"Triggers", 16, 16);
control(w, L"LISTBOX", L"", WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_BORDER, 301,
16, 44, 200, 380);
button(w, L"&Add", 310, 16, 438, 90);
button(w, L"&Delete", 311, 116, 438, 100);
16, 44, 200, 480);
button(w, L"&Add", 310, 16, 538, 90);
button(w, L"&Delete", 311, 116, 538, 100);
label(w, L"&Trigger (for example ;addr)", 236, 16, 470);
edit(w, 302, 236, 44, 488);
label(w, L"&Replacement text", 236, 82, 480);
edit(w, 303, 236, 110, 488, 270, ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL | WS_VSCROLL);
control(w, L"STATIC", L"{cursor}: caret; {enter}: press Enter after insertion.\nUse {{cursor}} / {{enter}} for literal markers.", 0, 0, 236, 392, 488, 36);
button(w, L"Insert cursor &marker", 312, 236, 430, 190);
button(w, L"Insert &Enter marker", 313, 436, 430, 190);
button(w, L"&Save", IDOK, 510, 478, 100);
button(w, L"&Cancel", IDCANCEL, 624, 478, 100);
control(w, L"BUTTON", L"Only in these &applications (unchecked: everywhere)", WS_TABSTOP | BS_AUTOCHECKBOX, 314, 236, 82, 488, 24);
edit(w, 315, 236, 112, 488);
control(w, L"COMBOBOX", L"", WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 316, 236, 148, 364, 200);
button(w, L"Add a&pp", 317, 612, 148, 112);
std::set<std::wstring> apps;
EnumWindows([](HWND candidate, LPARAM context)->BOOL {
if (IsWindowVisible(candidate)) { auto name = processName(candidate); if (!name.empty()) ((std::set<std::wstring>*)context)->insert(lower(name) + L".exe"); }
return TRUE;
}, (LPARAM)&apps);
for (const auto &name : apps) SendDlgItemMessageW(w, 316, CB_ADDSTRING, 0, (LPARAM)name.c_str());
SendDlgItemMessageW(w, 316, CB_SETCURSEL, 0, 0);
label(w, L"Process names, comma-separated; e.g. chrome.exe, ms-teams.exe", 236, 182, 488);
label(w, L"&Replacement text", 236, 212, 480);
edit(w, 303, 236, 240, 488, 240, ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL | WS_VSCROLL);
control(w, L"STATIC", L"{cursor}: caret; {enter}: press Enter after insertion.\nUse {{cursor}} / {{enter}} for literal markers.", 0, 0, 236, 492, 488, 36);
button(w, L"Insert cursor &marker", 312, 236, 530, 190);
button(w, L"Insert &Enter marker", 313, 436, 530, 190);
button(w, L"&Save", IDOK, 510, 578, 100);
button(w, L"&Cancel", IDCANCEL, 624, 578, 100);
fillSnippets(w, (SnippetView *)GetWindowLongPtrW(w, GWLP_USERDATA));
showRequestedWindow(w);
SetForegroundWindow(w);