Skip to content

Commit 0f3ed70

Browse files
authored
Merge pull request #104 from WebCoder49/ctrl-z-ctrl-f-compatibility
Make Ctrl-Z-induced Find and Replace act normally
2 parents 8c16fa0 + fa124c5 commit 0f3ed70

File tree

1 file changed

+63
-17
lines changed

1 file changed

+63
-17
lines changed

plugins/find-and-replace.js

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
66
useCtrlF = false;
77
useCtrlH = false;
88

9+
findMatchesOnValueChange = true; // Needed so the program can insert text to the find value and thus add it to Ctrl+Z without highlighting matches.
10+
911
/**
1012
* Create a find-and-replace command plugin to pass into a template
1113
* @param {boolean} useCtrlF Should Ctrl+F be overriden for find-and-replace find functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element, false)`.
@@ -100,7 +102,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
100102
}, 100);
101103
}
102104

103-
/* Deal with Enter and Escape being pressed in the find field */
105+
/* Deal with Enter being pressed in the find field */
104106
checkFindPrompt(dialog, codeInput, event) {
105107
if (event.key == 'Enter') {
106108
// Find next match
@@ -109,19 +111,28 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
109111
}
110112
}
111113

112-
/* Deal with Enter and Escape being pressed in the replace field */
114+
/* Deal with Enter being pressed in the replace field */
113115
checkReplacePrompt(dialog, codeInput, event) {
114116
if (event.key == 'Enter') {
115117
// Replace focused match
116118
dialog.findMatchState.replaceOnce(dialog.replaceInput.value);
119+
dialog.replaceInput.focus();
117120
this.updateMatchDescription(dialog);
118121
}
119122
}
120123

121124
/* Called with a dialog box keyup event to close and clear the dialog box */
122125
cancelPrompt(dialog, codeInput, event) {
123126
event.preventDefault();
124-
127+
128+
// Add current value of find/replace to Ctrl+Z stack.
129+
this.findMatchesOnValueChange = false;
130+
dialog.findInput.focus();
131+
dialog.findInput.selectionStart = 0;
132+
dialog.findInput.selectionEnd = dialog.findInput.value.length;
133+
document.execCommand("insertText", false, dialog.findInput.value);
134+
this.findMatchesOnValueChange = true;
135+
125136
// Reset original selection in code-input
126137
dialog.textarea.focus();
127138
if(dialog.findMatchState.numMatches > 0) {
@@ -146,10 +157,11 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
146157
* @param {boolean} replacePartExpanded whether the replace part of the find-and-replace dialog should be expanded
147158
*/
148159
showPrompt(codeInputElement, replacePartExpanded) {
160+
let dialog;
149161
if(codeInputElement.pluginData.findAndReplace == undefined || codeInputElement.pluginData.findAndReplace.dialog == undefined) {
150162
const textarea = codeInputElement.textareaElement;
151163

152-
const dialog = document.createElement('div');
164+
dialog = document.createElement('div');
153165
const findInput = document.createElement('input');
154166
const findCaseSensitiveCheckbox = document.createElement('input');
155167
const findRegExpCheckbox = document.createElement('input');
@@ -229,7 +241,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
229241
event.preventDefault();
230242

231243
dialog.findMatchState.replaceOnce(replaceInput.value);
232-
replaceButton.focus();
244+
dialog.focus();
233245
});
234246
replaceAllButton.addEventListener("click", (event) => {
235247
// Stop form submit
@@ -275,14 +287,30 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
275287
/* Stop enter from submitting form */
276288
if (event.key == 'Enter') event.preventDefault();
277289
});
290+
replaceInput.addEventListener('input', (event) => {
291+
// Ctrl+Z can trigger this. If the dialog/replace dropdown aren't open, open them!
292+
if(dialog.classList.contains("code-input_find-and-replace_hidden-dialog")) {
293+
// Show prompt
294+
this.showPrompt(dialog.codeInput, true);
295+
} else if(!dialog.replaceDropdown.hasAttribute("open")) {
296+
// Open dropdown
297+
dialog.replaceDropdown.setAttribute("open", true);
298+
}
299+
});
278300

279301
dialog.addEventListener('keyup', (event) => {
280302
/* Close prompt on Enter pressed */
281303
if (event.key == 'Escape') this.cancelPrompt(dialog, codeInputElement, event);
282304
});
283305

284306
findInput.addEventListener('keyup', (event) => { this.checkFindPrompt(dialog, codeInputElement, event); });
285-
findInput.addEventListener('input', (event) => { this.updateFindMatches(dialog); });
307+
findInput.addEventListener('input', (event) => {
308+
if(this.findMatchesOnValueChange) this.updateFindMatches(dialog);
309+
// Ctrl+Z can trigger this. If the dialog isn't open, open it!
310+
if(dialog.classList.contains("code-input_find-and-replace_hidden-dialog")) {
311+
this.showPrompt(dialog.codeInput, false);
312+
}
313+
});
286314
findCaseSensitiveCheckbox.addEventListener('click', (event) => { this.updateFindMatches(dialog); });
287315
findRegExpCheckbox.addEventListener('click', (event) => { this.updateFindMatches(dialog); });
288316

@@ -303,24 +331,42 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
303331
// Save selection position
304332
dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
305333
dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
334+
335+
if(dialog.selectionStart < dialog.selectionEnd) {
336+
// Copy selected text to Find input
337+
let textToFind = codeInputElement.textareaElement.value.substring(dialog.selectionStart, dialog.selectionEnd);
338+
dialog.findInput.focus();
339+
dialog.findInput.selectionStart = 0;
340+
dialog.findInput.selectionEnd = dialog.findInput.value.length;
341+
document.execCommand("insertText", false, textToFind);
342+
}
306343
} else {
344+
dialog = codeInputElement.pluginData.findAndReplace.dialog;
307345
// Re-open dialog
308-
codeInputElement.pluginData.findAndReplace.dialog.classList.remove("code-input_find-and-replace_hidden-dialog");
309-
codeInputElement.pluginData.findAndReplace.dialog.findInput.focus();
346+
dialog.classList.remove("code-input_find-and-replace_hidden-dialog");
347+
dialog.findInput.focus();
310348
if(replacePartExpanded) {
311-
codeInputElement.pluginData.findAndReplace.dialog.replaceDropdown.setAttribute("open", true);
349+
dialog.replaceDropdown.setAttribute("open", true);
312350
} else {
313-
codeInputElement.pluginData.findAndReplace.dialog.replaceDropdown.removeAttribute("open");
351+
dialog.replaceDropdown.removeAttribute("open");
314352
}
353+
}
315354

316-
317-
// Highlight matches
318-
this.updateFindMatches(codeInputElement.pluginData.findAndReplace.dialog);
319-
320-
// Save selection position
321-
codeInputElement.pluginData.findAndReplace.dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
322-
codeInputElement.pluginData.findAndReplace.dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
355+
// Save selection position
356+
dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
357+
dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
358+
359+
if(dialog.selectionStart < dialog.selectionEnd) {
360+
// Copy selected text to Find input
361+
let textToFind = codeInputElement.textareaElement.value.substring(dialog.selectionStart, dialog.selectionEnd);
362+
dialog.findInput.focus();
363+
dialog.findInput.selectionStart = 0;
364+
dialog.findInput.selectionEnd = dialog.findInput.value.length;
365+
document.execCommand("insertText", false, textToFind);
323366
}
367+
368+
// Highlight matches
369+
this.updateFindMatches(dialog);
324370
}
325371

326372
/* Event handler for keydown event that makes Ctrl+F open find dialog */

0 commit comments

Comments
 (0)