Skip to content

Commit 3f63b84

Browse files
committed
Implemented Find and Replace (Ctrl+H) in query editor. Minor UX refinements: Ctrl+F and Ctrl+H support when editor pane is not focused for quick replacements, added Escape keybind to close/cancel options dialog.
1 parent d9b4025 commit 3f63b84

5 files changed

Lines changed: 247 additions & 2 deletions

File tree

src/sqlite-ce-edit/dialogs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ void DoOptions(void) {
245245

246246
/* Modal message loop */
247247
while (g_hwndOptions && GetMessage(&msg, NULL, 0, 0)) {
248+
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE) {
249+
SendMessage(g_hwndOptions, WM_CLOSE, 0, 0);
250+
continue;
251+
}
248252
TranslateMessage(&msg);
249253
DispatchMessage(&msg);
250254
}

src/sqlite-ce-edit/editor.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ static void UpdateContextMenu(int mode) {
135135
AppendMenuW(hCtx, MF_SEPARATOR, 0, NULL);
136136
AppendMenuW(hCtx, MF_STRING, IDM_FIND, L"&Find...\tCtrl+F");
137137
AppendMenuW(hCtx, MF_STRING, IDM_FINDNEXT, L"Find &Next\tF3");
138+
AppendMenuW(hCtx, MF_STRING, IDM_REPLACE, L"&Replace...\tCtrl+H");
138139
InsertMenuW(hCBMenu, 1, MF_BYPOSITION | MF_POPUP, (UINT)hCtx, L"&Query");
139140
} else if (mode == 1) {
140141
wchar_t gridLabel[64];
@@ -350,11 +351,15 @@ LRESULT CALLBACK QueryEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam
350351
DoSaveQuery();
351352
return 0;
352353
}
353-
/* Ctrl+F - Find, F3 - Find Next */
354+
/* Ctrl+F - Find, Ctrl+H - Replace, F3 - Find Next */
354355
if (ctrl && wParam == 'F') {
355356
DoFind();
356357
return 0;
357358
}
359+
if (ctrl && wParam == 'H') {
360+
DoReplace();
361+
return 0;
362+
}
358363
if (wParam == VK_F3) {
359364
DoFindNext();
360365
return 0;

src/sqlite-ce-edit/find.c

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,232 @@ void DoFind(void) {
146146
g_hwndMain, NULL, g_hInst, NULL);
147147
ShowWindow(g_hwndFindDlg, SW_SHOW);
148148
}
149+
150+
/*============================================================================
151+
** Find and Replace (Ctrl+H) - Query view only
152+
**============================================================================*/
153+
154+
static HWND g_hwndReplaceDlg = NULL;
155+
static HWND g_hwndReplFind = NULL;
156+
static HWND g_hwndReplWith = NULL;
157+
static wchar_t g_replaceText[128] = L"";
158+
159+
#define ID_REPL_FIND 201
160+
#define ID_REPL_REPLACE 202
161+
#define ID_REPL_ALL 203
162+
163+
static void DoReplaceOne(void) {
164+
DWORD selStart, selEnd;
165+
166+
if (!g_findText[0]) return;
167+
if (g_viewMode != 0) return; /* Query view only */
168+
169+
SendMessage(g_hwndQuery, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd);
170+
171+
/* If we have a selection matching find text, replace it */
172+
if (selEnd > selStart) {
173+
int selLen = selEnd - selStart;
174+
int findLen = lstrlenW(g_findText);
175+
if (selLen == findLen) {
176+
int len = GetWindowTextLengthW(g_hwndQuery);
177+
wchar_t *buf = (wchar_t *)LocalAlloc(LMEM_FIXED, (len + 1) * sizeof(wchar_t));
178+
if (buf) {
179+
int i, match = 1;
180+
GetWindowTextW(g_hwndQuery, buf, len + 1);
181+
/* Case-insensitive compare of selection */
182+
for (i = 0; i < selLen && match; i++) {
183+
wchar_t c1 = buf[selStart + i], c2 = g_findText[i];
184+
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
185+
if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
186+
if (c1 != c2) match = 0;
187+
}
188+
LocalFree(buf);
189+
if (match) {
190+
SendMessage(g_hwndQuery, EM_REPLACESEL, TRUE, (LPARAM)g_replaceText);
191+
g_queryDirty = 1;
192+
}
193+
}
194+
}
195+
}
196+
/* Find next */
197+
DoFindNext();
198+
}
199+
200+
static int DoReplaceAll(void) {
201+
int len, findLen, replLen, count = 0, i, j;
202+
wchar_t *buf, *newBuf, *p;
203+
204+
if (!g_findText[0]) return 0;
205+
if (g_viewMode != 0) return 0;
206+
207+
findLen = lstrlenW(g_findText);
208+
replLen = lstrlenW(g_replaceText);
209+
len = GetWindowTextLengthW(g_hwndQuery);
210+
if (len == 0) return 0;
211+
212+
buf = (wchar_t *)LocalAlloc(LMEM_FIXED, (len + 1) * sizeof(wchar_t));
213+
if (!buf) return 0;
214+
GetWindowTextW(g_hwndQuery, buf, len + 1);
215+
216+
/* Count matches first */
217+
for (i = 0; i <= len - findLen; i++) {
218+
for (j = 0; j < findLen; j++) {
219+
wchar_t c1 = buf[i + j], c2 = g_findText[j];
220+
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
221+
if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
222+
if (c1 != c2) break;
223+
}
224+
if (j == findLen) count++;
225+
}
226+
227+
if (count == 0) {
228+
LocalFree(buf);
229+
return 0;
230+
}
231+
232+
/* Build new string */
233+
newBuf = (wchar_t *)LocalAlloc(LMEM_FIXED,
234+
(len + count * (replLen - findLen) + 1) * sizeof(wchar_t));
235+
if (!newBuf) {
236+
LocalFree(buf);
237+
return 0;
238+
}
239+
240+
p = newBuf;
241+
for (i = 0; i <= len; i++) {
242+
if (i <= len - findLen) {
243+
for (j = 0; j < findLen; j++) {
244+
wchar_t c1 = buf[i + j], c2 = g_findText[j];
245+
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
246+
if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
247+
if (c1 != c2) break;
248+
}
249+
if (j == findLen) {
250+
for (j = 0; j < replLen; j++) *p++ = g_replaceText[j];
251+
i += findLen - 1;
252+
continue;
253+
}
254+
}
255+
*p++ = buf[i];
256+
}
257+
258+
SetWindowTextW(g_hwndQuery, newBuf);
259+
g_queryDirty = 1;
260+
261+
LocalFree(newBuf);
262+
LocalFree(buf);
263+
return count;
264+
}
265+
266+
static WNDPROC g_pfnReplFindProc, g_pfnReplWithProc;
267+
268+
static LRESULT CALLBACK ReplEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
269+
WNDPROC origProc = (hwnd == g_hwndReplFind) ? g_pfnReplFindProc : g_pfnReplWithProc;
270+
if (msg == WM_KEYDOWN) {
271+
if (wParam == VK_TAB) {
272+
SetFocus((hwnd == g_hwndReplFind) ? g_hwndReplWith : g_hwndReplFind);
273+
return 0;
274+
}
275+
if (wParam == VK_RETURN) {
276+
SendMessage(GetParent(hwnd), WM_COMMAND, ID_REPL_FIND, 0);
277+
return 0;
278+
}
279+
if (wParam == VK_ESCAPE) {
280+
SendMessage(GetParent(hwnd), WM_CLOSE, 0, 0);
281+
return 0;
282+
}
283+
}
284+
if (msg == WM_CHAR && wParam == 27) { /* Escape as WM_CHAR */
285+
SendMessage(GetParent(hwnd), WM_CLOSE, 0, 0);
286+
return 0;
287+
}
288+
return CallWindowProc(origProc, hwnd, msg, wParam, lParam);
289+
}
290+
291+
static LRESULT CALLBACK ReplaceWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
292+
switch (msg) {
293+
case WM_CREATE: {
294+
CreateWindowW(L"STATIC", L"Find:",
295+
WS_CHILD | WS_VISIBLE, 5, 7, 45, 16, hwnd, NULL, g_hInst, NULL);
296+
g_hwndReplFind = CreateWindowW(L"EDIT", g_findText,
297+
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
298+
55, 5, 175, 20, hwnd, (HMENU)101, g_hInst, NULL);
299+
CreateWindowW(L"STATIC", L"Replace:",
300+
WS_CHILD | WS_VISIBLE, 5, 32, 50, 16, hwnd, NULL, g_hInst, NULL);
301+
g_hwndReplWith = CreateWindowW(L"EDIT", g_replaceText,
302+
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
303+
55, 30, 175, 20, hwnd, (HMENU)102, g_hInst, NULL);
304+
CreateWindowW(L"BUTTON", L"Find",
305+
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
306+
5, 58, 55, 22, hwnd, (HMENU)ID_REPL_FIND, g_hInst, NULL);
307+
CreateWindowW(L"BUTTON", L"Replace",
308+
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
309+
65, 58, 60, 22, hwnd, (HMENU)ID_REPL_REPLACE, g_hInst, NULL);
310+
CreateWindowW(L"BUTTON", L"All",
311+
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
312+
175, 58, 55, 22, hwnd, (HMENU)ID_REPL_ALL, g_hInst, NULL);
313+
g_pfnReplFindProc = (WNDPROC)SetWindowLong(g_hwndReplFind, GWL_WNDPROC, (LONG)ReplEditProc);
314+
g_pfnReplWithProc = (WNDPROC)SetWindowLong(g_hwndReplWith, GWL_WNDPROC, (LONG)ReplEditProc);
315+
SetFocus(g_hwndReplFind);
316+
SendMessage(g_hwndReplFind, EM_SETSEL, 0, -1);
317+
return 0;
318+
}
319+
case WM_COMMAND:
320+
GetWindowTextW(g_hwndReplFind, g_findText, 128);
321+
GetWindowTextW(g_hwndReplWith, g_replaceText, 128);
322+
if (LOWORD(wParam) == ID_REPL_FIND) {
323+
if (g_findText[0]) DoFindNext();
324+
} else if (LOWORD(wParam) == ID_REPL_REPLACE) {
325+
if (g_findText[0]) DoReplaceOne();
326+
} else if (LOWORD(wParam) == ID_REPL_ALL) {
327+
if (g_findText[0]) {
328+
int n = DoReplaceAll();
329+
wchar_t buf[64];
330+
wsprintfW(buf, L"%d replacement%s made.", n, n == 1 ? L"" : L"s");
331+
MessageBoxW(hwnd, buf, L"Replace All", MB_OK);
332+
}
333+
}
334+
return 0;
335+
case WM_CLOSE:
336+
DestroyWindow(hwnd);
337+
g_hwndReplaceDlg = NULL;
338+
SetFocus(g_hwndQuery);
339+
return 0;
340+
}
341+
return DefWindowProc(hwnd, msg, wParam, lParam);
342+
}
343+
344+
void DoReplace(void) {
345+
WNDCLASSW wc = {0};
346+
RECT rc;
347+
348+
/* Only available in Query view */
349+
if (g_viewMode != 0) {
350+
MessageBoxW(g_hwndMain, L"Replace is only available in Query view.", L"Replace", MB_OK);
351+
return;
352+
}
353+
354+
if (g_hwndReplaceDlg) {
355+
SetFocus(g_hwndReplFind);
356+
return;
357+
}
358+
359+
/* Close find dialog if open */
360+
if (g_hwndFindDlg) {
361+
DestroyWindow(g_hwndFindDlg);
362+
g_hwndFindDlg = NULL;
363+
}
364+
365+
wc.lpfnWndProc = ReplaceWndProc;
366+
wc.hInstance = g_hInst;
367+
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
368+
wc.lpszClassName = L"SQLiteCEReplace";
369+
RegisterClassW(&wc);
370+
371+
GetWindowRect(g_hwndMain, &rc);
372+
g_hwndReplaceDlg = CreateWindowExW(WS_EX_TOOLWINDOW, L"SQLiteCEReplace", L"Replace",
373+
WS_POPUP | WS_CAPTION | WS_SYSMENU,
374+
rc.left + 20, rc.top + 50, 240, 108,
375+
g_hwndMain, NULL, g_hInst, NULL);
376+
ShowWindow(g_hwndReplaceDlg, SW_SHOW);
377+
}

src/sqlite-ce-edit/globals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ BOOL WINAPI GetSaveFileNameW(CE_OPENFILENAME*);
106106
#define IDM_EXECUTE 201
107107
#define IDM_FIND 202
108108
#define IDM_FINDNEXT 203
109+
#define IDM_REPLACE 207
109110
#define IDM_EXECATCURSOR 204
110111
#define IDM_EXECATCURSOR_PLACEHOLDER 205
111112
#define IDM_STOP 206
@@ -334,6 +335,7 @@ void OpenQueryFile(const wchar_t *path);
334335

335336
void DoFind(void);
336337
void DoFindNext(void);
338+
void DoReplace(void);
337339

338340
/*============================================================================
339341
** Function Declarations - Dialogs (dialogs.c)

src/sqlite-ce-edit/main.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
6868
AppendMenuW(hQuery, MF_SEPARATOR, 0, NULL);
6969
AppendMenuW(hQuery, MF_STRING, IDM_FIND, L"&Find...\tCtrl+F");
7070
AppendMenuW(hQuery, MF_STRING, IDM_FINDNEXT, L"Find &Next\tF3");
71+
AppendMenuW(hQuery, MF_STRING, IDM_REPLACE, L"&Replace...\tCtrl+H");
7172
AppendMenuW(hMenu, MF_POPUP, (UINT)hQuery, L"&Query");
7273

7374
hView = CreatePopupMenu();
@@ -188,7 +189,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
188189
g_hwndQuery = CreateWindowW(
189190
L"EDIT", L"",
190191
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL |
191-
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN,
192+
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | ES_NOHIDESEL,
192193
queryLeft, cbHeight, rc.right - queryLeft, editHeight,
193194
hwnd, (HMENU)1001, g_hInst, NULL);
194195

@@ -289,6 +290,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
289290
break;
290291
case IDM_FIND: DoFind(); break;
291292
case IDM_FINDNEXT: DoFindNext(); break;
293+
case IDM_REPLACE: DoReplace(); break;
292294
case IDM_ABOUT:
293295
DoAbout();
294296
break;
@@ -390,6 +392,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
390392
if (wParam == 'O') { DoOpenQuery(); return 0; }
391393
if (wParam == 'N') { DoFileNew(); return 0; }
392394
if (wParam == 'C') { g_abortQuery = 1; return 0; }
395+
if (wParam == 'F') { DoFind(); return 0; }
396+
if (wParam == 'H') { DoReplace(); return 0; }
393397
if (wParam == '1') { SendMessage(hwnd, WM_COMMAND, IDM_VIEWQUERY, 0); return 0; }
394398
if (wParam == '2') { SendMessage(hwnd, WM_COMMAND, IDM_VIEWRESULT, 0); return 0; }
395399
if (wParam == '3') { SendMessage(hwnd, WM_COMMAND, IDM_VIEWSCHEMA, 0); return 0; }
@@ -528,6 +532,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR lpCmd, int nShow) {
528532
accel[nAccel].fVirt = FCONTROL | FVIRTKEY; accel[nAccel].key = 'O'; accel[nAccel].cmd = IDM_OPENQUERY; nAccel++;
529533
accel[nAccel].fVirt = FCONTROL | FVIRTKEY; accel[nAccel].key = 'S'; accel[nAccel].cmd = IDM_SAVEQUERY; nAccel++;
530534
accel[nAccel].fVirt = FCONTROL | FVIRTKEY; accel[nAccel].key = 'F'; accel[nAccel].cmd = IDM_FIND; nAccel++;
535+
accel[nAccel].fVirt = FCONTROL | FVIRTKEY; accel[nAccel].key = 'H'; accel[nAccel].cmd = IDM_REPLACE; nAccel++;
531536
accel[nAccel].fVirt = FCONTROL | FVIRTKEY; accel[nAccel].key = '1'; accel[nAccel].cmd = IDM_VIEWQUERY; nAccel++;
532537
accel[nAccel].fVirt = FCONTROL | FVIRTKEY; accel[nAccel].key = '2'; accel[nAccel].cmd = IDM_VIEWRESULT; nAccel++;
533538
accel[nAccel].fVirt = FCONTROL | FVIRTKEY; accel[nAccel].key = '3'; accel[nAccel].cmd = IDM_VIEWSCHEMA; nAccel++;

0 commit comments

Comments
 (0)