From 9f4e1ad80d57e309b1ba1eebee95ce7910d4cfb6 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Thu, 6 Aug 2020 21:11:24 +0530 Subject: [PATCH 01/32] Implemented replace functionality --- client/modules/IDE/components/Editor.jsx | 1 + client/utils/codemirror-search.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index ae379334b7..fc5eafab27 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -137,6 +137,7 @@ class Editor extends React.Component { [`${metaKey}-F`]: 'findPersistent', [`${metaKey}-G`]: 'findNext', [`Shift-${metaKey}-G`]: 'findPrev', + [`${metaKey}-R`]: 'replace', }); this.initializeDocuments(this.props.files); diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 9dbfd010a8..ced8b03b6f 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -86,16 +86,16 @@ export default function(CodeMirror) { var upArrow = dialog.getElementsByClassName("up-arrow")[0]; CodeMirror.on(upArrow, "click", function () { + cm.focus(); CodeMirror.commands.findPrev(cm); searchField.blur(); - cm.focus(); }); var downArrow = dialog.getElementsByClassName("down-arrow")[0]; CodeMirror.on(downArrow, "click", function () { + cm.focus(); CodeMirror.commands.findNext(cm); searchField.blur(); - cm.focus(); }); var regexpButton = dialog.getElementsByClassName("CodeMirror-regexp-button")[0]; @@ -401,6 +401,7 @@ export default function(CodeMirror) { // TODO: This will need updating if replace is implemented function replace(cm, all) { + const state = getSearchState(cm); var prevDialog = document.getElementsByClassName("CodeMirror-dialog")[0]; if (prevDialog) { clearSearch(cm); @@ -408,11 +409,12 @@ export default function(CodeMirror) { cm.focus(); } if (cm.getOption("readOnly")) return; - var query = cm.getSelection() || getSearchState(cm).lastQuery; + var query = cm.getSelection() || state.lastQuery; var dialogText = all ? "Replace all:" : "Replace:" dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) { if (!query) return; - query = parseQuery(query); + query = parseQuery(query, state); + dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { text = parseString(text) if (all) { @@ -451,6 +453,6 @@ export default function(CodeMirror) { CodeMirror.commands.findNext = doSearch; CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; CodeMirror.commands.clearSearch = clearSearch; - // CodeMirror.commands.replace = replace; - // CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; + CodeMirror.commands.replace = replace; + CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; }; From e402d07378227cf34d760fd124bb55b2a5910405 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Fri, 7 Aug 2020 00:49:04 +0530 Subject: [PATCH 02/32] Implemented replace & replace all feature --- client/components/Nav.jsx | 34 +++++++++++++++++++ client/modules/IDE/components/Editor.jsx | 13 +++++++ .../IDE/components/KeyboardShortcutModal.jsx | 12 +++++++ .../components/_keyboard-shortcuts.scss | 1 + translations/locales/en-US/translations.json | 6 +++- translations/locales/es-419/translations.json | 6 +++- 6 files changed, 70 insertions(+), 2 deletions(-) diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index cd20a91522..348f44846a 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -42,6 +42,8 @@ class Nav extends React.PureComponent { this.handleFindNext = this.handleFindNext.bind(this); this.handleRun = this.handleRun.bind(this); this.handleFindPrevious = this.handleFindPrevious.bind(this); + this.handleReplace = this.handleReplace.bind(this); + this.handleReplaceAll = this.handleReplaceAll.bind(this); this.handleStop = this.handleStop.bind(this); this.handleStartAccessible = this.handleStartAccessible.bind(this); this.handleStopAccessible = this.handleStopAccessible.bind(this); @@ -134,6 +136,16 @@ class Nav extends React.PureComponent { this.setDropdown('none'); } + handleReplace() { + this.props.cmController.showReplace(); + this.setDropdown('none'); + } + + handleReplaceAll() { + this.props.cmController.showReplaceAll(); + this.setDropdown('none'); + } + handleAddFile() { this.props.newFile(this.props.rootFile.id); this.setDropdown('none'); @@ -416,6 +428,26 @@ class Nav extends React.PureComponent { {'\u21E7'}+{metaKeyName}+G +
  • + +
  • +
  • + +
  • @@ -777,6 +809,8 @@ Nav.propTypes = { showFind: PropTypes.func, findNext: PropTypes.func, findPrev: PropTypes.func, + showReplace: PropTypes.func, + showReplaceAll: PropTypes.func, getContent: PropTypes.func }), startSketch: PropTypes.func.isRequired, diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index fc5eafab27..47066b5e75 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -86,6 +86,8 @@ class Editor extends React.Component { this.showFind = this.showFind.bind(this); this.findNext = this.findNext.bind(this); this.findPrev = this.findPrev.bind(this); + this.showReplace = this.showReplace.bind(this); + this.showReplaceAll = this.showReplaceAll.bind(this); this.getContent = this.getContent.bind(this); } @@ -138,6 +140,7 @@ class Editor extends React.Component { [`${metaKey}-G`]: 'findNext', [`Shift-${metaKey}-G`]: 'findPrev', [`${metaKey}-R`]: 'replace', + [`Shift-${metaKey}-R`]: 'replaceAll', }); this.initializeDocuments(this.props.files); @@ -171,6 +174,8 @@ class Editor extends React.Component { showFind: this.showFind, findNext: this.findNext, findPrev: this.findPrev, + showReplace: this.showReplace, + showReplaceAll: this.showReplaceAll, getContent: this.getContent }); } @@ -284,6 +289,14 @@ class Editor extends React.Component { this._cm.execCommand('findPersistent'); } + showReplace() { + this._cm.execCommand('replace'); + } + + showReplaceAll() { + this._cm.execCommand('replaceAll'); + } + tidyCode() { const beautifyOptions = { indent_size: INDENTATION_AMOUNT, diff --git a/client/modules/IDE/components/KeyboardShortcutModal.jsx b/client/modules/IDE/components/KeyboardShortcutModal.jsx index 4d471ea06e..b42e8f2bbf 100644 --- a/client/modules/IDE/components/KeyboardShortcutModal.jsx +++ b/client/modules/IDE/components/KeyboardShortcutModal.jsx @@ -33,6 +33,18 @@ function KeyboardShortcutModal() { {t('KeyboardShortcuts.CodeEditing.FindPreviousTextMatch')}
  • +
  • + + {metaKeyName} + R + + {t('KeyboardShortcuts.CodeEditing.ReplaceTextMatch')} +
  • +
  • + + {metaKeyName} + {'\u21E7'} + R + + {t('KeyboardShortcuts.CodeEditing.ReplaceAllTextMatches')} +
  • {metaKeyName} + [ diff --git a/client/styles/components/_keyboard-shortcuts.scss b/client/styles/components/_keyboard-shortcuts.scss index ca6f81fd74..6530af5558 100644 --- a/client/styles/components/_keyboard-shortcuts.scss +++ b/client/styles/components/_keyboard-shortcuts.scss @@ -2,6 +2,7 @@ padding: #{20 / $base-font-size}rem; padding-bottom: #{40 / $base-font-size}rem; width: #{450 / $base-font-size}rem; + overflow-y: scroll; } .keyboard-shortcuts-note { diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index 0b35ee8bbd..fa39f8ba62 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -15,7 +15,9 @@ "TidyCode": "Tidy Code", "Find": "Find", "FindNext": "Find Next", - "FindPrevious": "Find Previous" + "FindPrevious": "Find Previous", + "Replace": "Replace", + "ReplaceAll": "Replace All" }, "Sketch": { "Title": "Sketch", @@ -165,6 +167,8 @@ "FindText": "Find Text", "FindNextMatch": "Find Next Match", "FindPrevMatch": "Find Previous Match", + "ReplaceTextMatch": "Replace Text Match", + "ReplaceAllTextMatches": "Replace All Text Matches", "IndentCodeLeft": "Indent Code Left", "IndentCodeRight": "Indent Code Right", "CommentLine": "Comment Line", diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json index 227ecac5fe..81d536dd2e 100644 --- a/translations/locales/es-419/translations.json +++ b/translations/locales/es-419/translations.json @@ -15,7 +15,9 @@ "TidyCode": "Ordenar código", "Find": "Buscar", "FindNext": "Buscar siguiente", - "FindPrevious": "Buscar anterior" + "FindPrevious": "Buscar anterior", + "Replace": "Reemplazar", + "ReplaceAll": "Reemplaza todo" }, "Sketch": { "Title": "Bosquejo", @@ -165,6 +167,8 @@ "FindText": "Encontrar texto", "FindNextMatch": "Encontrar siguiente ocurrencia", "FindPrevMatch": "Encontrar ocurrencia previa", + "ReplaceTextMatch": "Reemplazar coincidencia de texto", + "ReplaceAllTextMatches": "Reemplazar todas las coincidencias de texto", "IndentCodeLeft": "Indentar código a la izquierda", "IndentCodeRight": "Indentar código a la derecha", "CommentLine": "Comentar línea de código", From 52e64619f73c8f46a426a6d932713ca5b36b6ca9 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Fri, 7 Aug 2020 22:39:57 +0530 Subject: [PATCH 03/32] Fixed styling of keyboard shortcuts overlay --- client/styles/components/_keyboard-shortcuts.scss | 1 - client/styles/components/_overlay.scss | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/client/styles/components/_keyboard-shortcuts.scss b/client/styles/components/_keyboard-shortcuts.scss index 6530af5558..82ca7ccbc9 100644 --- a/client/styles/components/_keyboard-shortcuts.scss +++ b/client/styles/components/_keyboard-shortcuts.scss @@ -1,6 +1,5 @@ .keyboard-shortcuts { padding: #{20 / $base-font-size}rem; - padding-bottom: #{40 / $base-font-size}rem; width: #{450 / $base-font-size}rem; overflow-y: scroll; } diff --git a/client/styles/components/_overlay.scss b/client/styles/components/_overlay.scss index 3e0efbadeb..61cf0219c9 100644 --- a/client/styles/components/_overlay.scss +++ b/client/styles/components/_overlay.scss @@ -25,6 +25,7 @@ max-height: 80%; max-width: 65%; position: relative; + padding-bottom: 25px; } .overlay__header { From ad7975bf01fc692d3ad869e5fbdd8b50f4b3296e Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Sun, 16 Aug 2020 13:34:24 +0530 Subject: [PATCH 04/32] Updated test snapshot --- client/components/__test__/Nav.test.jsx | 4 +++- .../__test__/__snapshots__/Nav.test.jsx.snap | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/client/components/__test__/Nav.test.jsx b/client/components/__test__/Nav.test.jsx index 791db4902c..32a00a82a5 100644 --- a/client/components/__test__/Nav.test.jsx +++ b/client/components/__test__/Nav.test.jsx @@ -35,7 +35,9 @@ describe('Nav', () => { tidyCode: jest.fn(), showFind: jest.fn(), findNext: jest.fn(), - findPrev: jest.fn() + findPrev: jest.fn(), + showReplace: jest.fn(), + showReplaceAll: jest.fn(), }, startSketch: jest.fn(), stopSketch: jest.fn(), diff --git a/client/components/__test__/__snapshots__/Nav.test.jsx.snap b/client/components/__test__/__snapshots__/Nav.test.jsx.snap index 78a9bc5483..8d0c98a4ce 100644 --- a/client/components/__test__/__snapshots__/Nav.test.jsx.snap +++ b/client/components/__test__/__snapshots__/Nav.test.jsx.snap @@ -122,6 +122,28 @@ exports[`Nav renders correctly 1`] = `
  • + +
  • Date: Fri, 28 Aug 2020 23:20:43 +0530 Subject: [PATCH 05/32] Modified replace feature, bug fixes needed --- client/utils/codemirror-search.js | 300 +++++++++++++++++++----------- 1 file changed, 196 insertions(+), 104 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index ced8b03b6f..4b71126069 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -49,7 +49,7 @@ export default function(CodeMirror) { else return false; } - function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { + function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { var searchField = document.getElementsByClassName("CodeMirror-search-field")[0]; if (!searchField) { cm.openDialog(text, onEnter, { @@ -63,7 +63,7 @@ export default function(CodeMirror) { closeOnBlur: false }); - searchField = document.getElementsByClassName("CodeMirror-search-field")[0]; + searchField = document.getElementById("Find-input-field"); var dialog = document.getElementsByClassName("CodeMirror-dialog")[0]; var closeButton = dialog.getElementsByClassName("close")[0]; @@ -141,6 +141,35 @@ export default function(CodeMirror) { el.setAttribute('aria-checked', nextState); return nextState; } + + var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-button")[0]; + var replaceDiv = dialog.getElementsByClassName("Replace-div")[0]; + if (replaceOpened) { + replaceDiv.style.height = "138px"; + } + CodeMirror.on(showReplaceButton, "click", function () { + if (replaceDiv.style.height === "0px") { + replaceDiv.style.height = "138px"; + } else { + replaceDiv.style.height = "0px"; + } + }); + + var replaceField = document.getElementById('Replace-input-field'); + CodeMirror.on(replaceField, "keyup", function (e) { + if (e.keyCode == 13) // if enter + { + startSearch(cm, getSearchState(cm), searchField.value); + replace(cm, parseString(searchField.value), parseString(replaceField.value)); + } + }) + + var doReplaceButton = document.getElementById('Btn-replace'); + CodeMirror.on(doReplaceButton, "click", function(e) { + startSearch(cm, getSearchState(cm), searchField.value); + replace(cm, parseString(searchField.value), parseString(replaceField.value)); + }) + } else { searchField.focus(); searchField.select(); @@ -232,59 +261,6 @@ export default function(CodeMirror) { return regexp; } - var queryDialog = ` -

    Find

    - -
    -
    - - - -
    -
    - - - -
    -
    - - `; - function startSearch(cm, state, originalQuery) { state.queryText = originalQuery; state.query = parseQuery(originalQuery, state); @@ -301,6 +277,60 @@ export default function(CodeMirror) { } } + function doFindAndReplace(cm, rev, persistent, immediate, ignoreQuery, replaceOpened) { + var state = getSearchState(cm); + if (!ignoreQuery && state.query) { + console.log('Here'); + return findNext(cm, rev); + } + var q = cm.getSelection() || state.lastQuery; + if (persistent && cm.openDialog) { + var hiding = null; + var searchNext = function(query, event) { + CodeMirror.e_stop(event); + if (!query) return; + if (query != state.queryText) { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + } + if (hiding) hiding.style.opacity = 1 + findNext(cm, event.shiftKey, function(_, to) { + var dialog + if (to.line < 3 && document.querySelector && + (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && + dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) + (hiding = dialog).style.opacity = 1 + }) + }; + persistentDialog(cm, queryDialog, q, searchNext, replaceOpened, function(event, query) { + var keyName = CodeMirror.keyName(event) + var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName] + if (!cmd) cmd = cm.getOption('extraKeys')[keyName] + if (cmd == "findNext" || cmd == "findPrev" || + cmd == "findPersistentNext" || cmd == "findPersistentPrev") { + CodeMirror.e_stop(event); + startSearch(cm, getSearchState(cm), query); + cm.execCommand(cmd); + } else if (cmd == "find" || cmd == "findPersistent") { + CodeMirror.e_stop(event); + searchNext(query, event); + } + }); + if (immediate && q) { + startSearch(cm, state, q); + findNext(cm, rev); + } + } else { + dialog(cm, queryDialog, "Search for:", q, function(query) { + if (query && !state.query) cm.operation(function() { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + findNext(cm, rev); + }); + }); + } + } + function doSearch(cm, rev, persistent, immediate, ignoreQuery) { var state = getSearchState(cm); if (!ignoreQuery && state.query) return findNext(cm, rev); @@ -382,12 +412,6 @@ export default function(CodeMirror) { if (state.annotate) { state.annotate.clear(); state.annotate = null; } });} - var replaceQueryDialog = - '
    '; - - var replacementQueryDialog = 'With: '; - var doReplaceConfirm = "Replace? "; - function replaceAll(cm, query, text) { cm.operation(function() { for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { @@ -399,60 +423,128 @@ export default function(CodeMirror) { }); } - // TODO: This will need updating if replace is implemented - function replace(cm, all) { + function replace(cm, queryText, withText, all) { + if (!queryText) return; const state = getSearchState(cm); var prevDialog = document.getElementsByClassName("CodeMirror-dialog")[0]; if (prevDialog) { clearSearch(cm); - prevDialog.parentNode.removeChild(prevDialog); - cm.focus(); } + prevDialog.parentNode.removeChild(prevDialog); + cm.focus(); if (cm.getOption("readOnly")) return; var query = cm.getSelection() || state.lastQuery; - var dialogText = all ? "Replace all:" : "Replace:" - dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) { - if (!query) return; - query = parseQuery(query, state); - - dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { - text = parseString(text) - if (all) { - replaceAll(cm, query, text) - } else { - clearSearch(cm); - var cursor = getSearchCursor(cm, query, cm.getCursor("from")); - var advance = function() { - var start = cursor.from(), match; - if (!(match = cursor.findNext())) { - cursor = getSearchCursor(cm, query); - if (!(match = cursor.findNext()) || - (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; - } - cm.setSelection(cursor.from(), cursor.to()); - cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60); - confirmDialog(cm, doReplaceConfirm, "Replace?", - [function() {doReplace(match);}, advance, - function() {replaceAll(cm, query, text)}]); - }; - var doReplace = function(match) { - cursor.replace(typeof query == "string" ? text : - text.replace(/\$(\d)/g, function(_, i) {return match[i];})); - advance(); - }; - advance(); + + if (all) { + replaceAll(cm, query, withText) + } else { + clearSearch(cm); + var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var advance = function() { + var start = cursor.from(), match; + if (!(match = cursor.findNext())) { + cursor = getSearchCursor(cm, query); + if (!(match = cursor.findNext()) || + (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; } - }); - }); + cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60); + confirmDialog(cm, doReplaceConfirm, "Replace?", + [function() {doReplace(match);}, advance, + function() {replaceAll(cm, query, withText)}]); + }; + var doReplace = function(match) { + cursor.replace(typeof query == "string" ? withText : + withText.replace(/\$(\d)/g, function(_, i) {return match[i];})); + advance(); + }; + advance(); + } } - CodeMirror.commands.find = function(cm) {doSearch(cm);}; - CodeMirror.commands.findPersistent = function(cm) { doSearch(cm, false, true, false, true);}; - CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);}; - CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);}; + var doReplaceConfirm = " "; + + var queryDialog = ` +

    Find

    + +
    +
    + + + + +
    +
    + + + +
    +
    + +
    +
    +

    Replace

    +
    + +
    + `; + + CodeMirror.commands.findPersistent = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; + CodeMirror.commands.findPersistentNext = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; + CodeMirror.commands.findPersistentPrev = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; CodeMirror.commands.findNext = doSearch; CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; CodeMirror.commands.clearSearch = clearSearch; - CodeMirror.commands.replace = replace; - CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; + CodeMirror.commands.replace = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; + CodeMirror.commands.replaceAll = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; }; From 328616fe2bf7508aad537f415fae3462359d5fee Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Tue, 1 Sep 2020 16:53:46 +0530 Subject: [PATCH 06/32] Fixed bug in replace using regexp --- client/utils/codemirror-search.js | 59 ++----------------------------- 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 4b71126069..436cb92158 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -280,7 +280,6 @@ export default function(CodeMirror) { function doFindAndReplace(cm, rev, persistent, immediate, ignoreQuery, replaceOpened) { var state = getSearchState(cm); if (!ignoreQuery && state.query) { - console.log('Here'); return findNext(cm, rev); } var q = cm.getSelection() || state.lastQuery; @@ -331,57 +330,6 @@ export default function(CodeMirror) { } } - function doSearch(cm, rev, persistent, immediate, ignoreQuery) { - var state = getSearchState(cm); - if (!ignoreQuery && state.query) return findNext(cm, rev); - var q = cm.getSelection() || state.lastQuery; - if (persistent && cm.openDialog) { - var hiding = null - var searchNext = function(query, event) { - CodeMirror.e_stop(event); - if (!query) return; - if (query != state.queryText) { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - } - if (hiding) hiding.style.opacity = 1 - findNext(cm, event.shiftKey, function(_, to) { - var dialog - if (to.line < 3 && document.querySelector && - (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && - dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) - (hiding = dialog).style.opacity = .4 - }) - }; - persistentDialog(cm, queryDialog, q, searchNext, function(event, query) { - var keyName = CodeMirror.keyName(event) - var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName] - if (!cmd) cmd = cm.getOption('extraKeys')[keyName] - if (cmd == "findNext" || cmd == "findPrev" || - cmd == "findPersistentNext" || cmd == "findPersistentPrev") { - CodeMirror.e_stop(event); - startSearch(cm, getSearchState(cm), query); - cm.execCommand(cmd); - } else if (cmd == "find" || cmd == "findPersistent") { - CodeMirror.e_stop(event); - searchNext(query, event); - } - }); - if (immediate && q) { - startSearch(cm, state, q); - findNext(cm, rev); - } - } else { - dialog(cm, queryDialog, "Search for:", q, function(query) { - if (query && !state.query) cm.operation(function() { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - findNext(cm, rev); - }); - }); - } - } - function findNext(cm, rev, callback) {cm.operation(function() { var state = getSearchState(cm); var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); @@ -433,8 +381,7 @@ export default function(CodeMirror) { prevDialog.parentNode.removeChild(prevDialog); cm.focus(); if (cm.getOption("readOnly")) return; - var query = cm.getSelection() || state.lastQuery; - + var query = parseQuery(queryText, state); if (all) { replaceAll(cm, query, withText) } else { @@ -542,8 +489,8 @@ export default function(CodeMirror) { CodeMirror.commands.findPersistent = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; CodeMirror.commands.findPersistentNext = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; CodeMirror.commands.findPersistentPrev = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; - CodeMirror.commands.findNext = doSearch; - CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; + CodeMirror.commands.findNext = doFindAndReplace; + CodeMirror.commands.findPrev = function(cm) {doFindAndReplace(cm, true);}; CodeMirror.commands.clearSearch = clearSearch; CodeMirror.commands.replace = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; CodeMirror.commands.replaceAll = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; From aeea0c3274b6ec8f73a7d7a7808745042ae6e091 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Tue, 1 Sep 2020 17:30:47 +0530 Subject: [PATCH 07/32] Added a separate replace all button --- client/utils/codemirror-search.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 436cb92158..016ef85f43 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -71,7 +71,11 @@ export default function(CodeMirror) { var state = getSearchState(cm); CodeMirror.on(searchField, "keyup", function (e) { - if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search + if (e.keyCode === 13) { + // If enter is pressed, then shift focus to replace field + replaceField.focus(); + } + else if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search startSearch(cm, getSearchState(cm), searchField.value); } else if (searchField.value.length <= 1) { cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = ''; @@ -157,7 +161,7 @@ export default function(CodeMirror) { var replaceField = document.getElementById('Replace-input-field'); CodeMirror.on(replaceField, "keyup", function (e) { - if (e.keyCode == 13) // if enter + if (e.keyCode === 13) // if enter { startSearch(cm, getSearchState(cm), searchField.value); replace(cm, parseString(searchField.value), parseString(replaceField.value)); @@ -170,6 +174,12 @@ export default function(CodeMirror) { replace(cm, parseString(searchField.value), parseString(replaceField.value)); }) + var doReplaceAllButton = document.getElementById('Btn-replace-all'); + CodeMirror.on(doReplaceAllButton, "click", function(e) { + startSearch(cm, getSearchState(cm), searchField.value); + replace(cm, parseString(searchField.value), parseString(replaceField.value), true); + }) + } else { searchField.focus(); searchField.select(); @@ -483,6 +493,15 @@ export default function(CodeMirror) { > + `; From eda8ca311e4bfcd2c2d4ac3835d41f82cab965d8 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Tue, 1 Sep 2020 19:40:02 +0530 Subject: [PATCH 08/32] Removed Replace All from Edit menu --- client/components/Nav.jsx | 17 ----------------- .../__test__/__snapshots__/Nav.test.jsx.snap | 11 ----------- client/modules/IDE/components/Editor.jsx | 7 ------- .../IDE/components/KeyboardShortcutModal.jsx | 6 ------ 4 files changed, 41 deletions(-) diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index 348f44846a..323e93001c 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -43,7 +43,6 @@ class Nav extends React.PureComponent { this.handleRun = this.handleRun.bind(this); this.handleFindPrevious = this.handleFindPrevious.bind(this); this.handleReplace = this.handleReplace.bind(this); - this.handleReplaceAll = this.handleReplaceAll.bind(this); this.handleStop = this.handleStop.bind(this); this.handleStartAccessible = this.handleStartAccessible.bind(this); this.handleStopAccessible = this.handleStopAccessible.bind(this); @@ -141,11 +140,6 @@ class Nav extends React.PureComponent { this.setDropdown('none'); } - handleReplaceAll() { - this.props.cmController.showReplaceAll(); - this.setDropdown('none'); - } - handleAddFile() { this.props.newFile(this.props.rootFile.id); this.setDropdown('none'); @@ -438,16 +432,6 @@ class Nav extends React.PureComponent { {metaKeyName}+R
  • -
  • - -
  • @@ -810,7 +794,6 @@ Nav.propTypes = { findNext: PropTypes.func, findPrev: PropTypes.func, showReplace: PropTypes.func, - showReplaceAll: PropTypes.func, getContent: PropTypes.func }), startSketch: PropTypes.func.isRequired, diff --git a/client/components/__test__/__snapshots__/Nav.test.jsx.snap b/client/components/__test__/__snapshots__/Nav.test.jsx.snap index 8d0c98a4ce..fa0c11f10f 100644 --- a/client/components/__test__/__snapshots__/Nav.test.jsx.snap +++ b/client/components/__test__/__snapshots__/Nav.test.jsx.snap @@ -133,17 +133,6 @@ exports[`Nav renders correctly 1`] = `
  • -
  • {t('KeyboardShortcuts.CodeEditing.ReplaceTextMatch')}
  • -
  • - - {metaKeyName} + {'\u21E7'} + R - - {t('KeyboardShortcuts.CodeEditing.ReplaceAllTextMatches')} -
  • {metaKeyName} + [ From 8f90bf2860248403a680f01c55c56bd5fe52e32f Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Wed, 9 Sep 2020 21:32:42 +0530 Subject: [PATCH 09/32] Updated find & replace popup layout --- client/styles/components/_editor.scss | 71 +++++++-- client/utils/codemirror-search.js | 202 ++++++++++++++++---------- 2 files changed, 184 insertions(+), 89 deletions(-) diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 7fc22f8e2e..338b20376a 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -87,16 +87,14 @@ pre.CodeMirror-line { position: fixed; top: 0; left: 50%; - margin-left: - #{365/2/$base-font-size}rem; + margin-left: - #{552/2/$base-font-size}rem; z-index: 10; - width: 100%; - max-width: #{365 / $base-font-size}rem; - + width: 552px; font-family: Montserrat, sans-serif; - padding: #{14 / $base-font-size}rem #{20 / $base-font-size}rem #{14 / $base-font-size}rem #{18 / $base-font-size}rem; + padding: #{7 / $base-font-size}rem #{10 / $base-font-size}rem #{7 / $base-font-size}rem #{9 / $base-font-size}rem; border-radius: 2px; @@ -107,6 +105,59 @@ pre.CodeMirror-line { } } +.CodeMirror-find-popup-container { + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} +.Toggle-replace-btn-div { + height: 40px; + margin-right: 10px; + padding: 0; +} +.Toggle-replace-btn-div > button { + width: 100%; + height: 100%; +} +.CodeMirror-find-div { + padding: 0px; + display: flex; + justify-content: flex-start; + align-items: center; + flex-wrap: nowrap; +} +.CodeMirror-search-modifiers { + margin-left: 10px; +} +.CodeMirror-search-results { + margin: 0px 20px; + width: 75px; +} +.CodeMirror-find-controls { + display: flex; +} + +.CodeMirror-replace-div { + display: flex; + width: 293px; + justify-content: center; + align-items: center; +} +.CodeMirror-replace-controls { + display: flex; +} + +.CodeMirror-replace-options { + width: 552px; + height: 65px; + display: flex; + justify-content: center; + align-items: center; +} +.CodeMirror-replace-options button { + width: 200px; +} + .CodeMirror-search-title { display: block; margin-bottom: #{12 / $base-font-size}rem; @@ -158,11 +209,11 @@ pre.CodeMirror-line { } } - width: #{35 / $base-font-size}rem; - height: #{35 / $base-font-size}rem; + width: #{25 / $base-font-size}rem; + height: #{25 / $base-font-size}rem; & + & { - margin-left: #{10 / $base-font-size}rem; + margin-left: #{3 / $base-font-size}rem; } word-break: keep-all; @@ -223,10 +274,6 @@ pre.CodeMirror-line { Close button */ .CodeMirror-close-button { - position: absolute; - top: #{14 / $base-font-size}rem; - right: #{18 / $base-font-size}rem; - display: flex; flex-direction: row; } diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 016ef85f43..8f7aefefa6 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -78,7 +78,7 @@ export default function(CodeMirror) { else if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search startSearch(cm, getSearchState(cm), searchField.value); } else if (searchField.value.length <= 1) { - cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = ''; + cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = 'No results'; } }); @@ -147,15 +147,25 @@ export default function(CodeMirror) { } var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-button")[0]; - var replaceDiv = dialog.getElementsByClassName("Replace-div")[0]; + var toggleReplaceBtnDiv = dialog.getElementsByClassName("Toggle-replace-btn-div")[0]; + var replaceDiv = dialog.getElementsByClassName("CodeMirror-replace-div")[0]; if (replaceOpened) { - replaceDiv.style.height = "138px"; + replaceDiv.style.height = "50px"; + toggleReplaceBtnDiv.style.height = "90px"; + showReplaceButton.style.height = "90px"; + showReplaceButton.innerHTML = "▼"; } CodeMirror.on(showReplaceButton, "click", function () { if (replaceDiv.style.height === "0px") { - replaceDiv.style.height = "138px"; + replaceDiv.style.height = "50px"; + toggleReplaceBtnDiv.style.height = "90px"; + showReplaceButton.style.height = "90px"; + showReplaceButton.innerHTML = "▼"; } else { replaceDiv.style.height = "0px"; + toggleReplaceBtnDiv.style.height = "40px"; + showReplaceButton.style.height = "40px"; + showReplaceButton.innerHTML = "▶"; } }); @@ -346,7 +356,7 @@ export default function(CodeMirror) { if (!cursor.find(rev)) { cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); if (!cursor.find(rev)) { - cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = ''; + cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = 'No results'; return; } } @@ -419,90 +429,128 @@ export default function(CodeMirror) { } } - var doReplaceConfirm = " "; + var doReplaceConfirm = ` +
    + + + + +
    + `; var queryDialog = ` -

    Find

    - -
    -
    - - - +
    +
    -
    - - - +
    +
    +
    + +
    +
    +
    + + + +
    +
    + + + +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    +
    - -
    -
    -

    Replace

    -
    - - -
    `; CodeMirror.commands.findPersistent = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; From 4ad52d3955db5db8def74ae7de4632e83414d993 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Tue, 15 Sep 2020 23:48:57 +0530 Subject: [PATCH 10/32] Modified find popup design --- client/images/replace-all.svg | 23 ++++++++++++++++++++ client/images/replace.svg | 16 ++++++++++++++ client/styles/components/_editor.scss | 31 +++++++++++++++++++++------ client/utils/codemirror-search.js | 30 +++++++++++++------------- 4 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 client/images/replace-all.svg create mode 100644 client/images/replace.svg diff --git a/client/images/replace-all.svg b/client/images/replace-all.svg new file mode 100644 index 0000000000..d1bb46d139 --- /dev/null +++ b/client/images/replace-all.svg @@ -0,0 +1,23 @@ + + + + + + diff --git a/client/images/replace.svg b/client/images/replace.svg new file mode 100644 index 0000000000..69a506e641 --- /dev/null +++ b/client/images/replace.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 338b20376a..e4bf3e1fda 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -91,10 +91,10 @@ pre.CodeMirror-line { z-index: 10; - width: 552px; + width: 580px; font-family: Montserrat, sans-serif; - padding: #{7 / $base-font-size}rem #{10 / $base-font-size}rem #{7 / $base-font-size}rem #{9 / $base-font-size}rem; + padding: #{8 / $base-font-size}rem #{10 / $base-font-size}rem #{5 / $base-font-size}rem #{9 / $base-font-size}rem; border-radius: 2px; @@ -112,7 +112,6 @@ pre.CodeMirror-line { } .Toggle-replace-btn-div { height: 40px; - margin-right: 10px; padding: 0; } .Toggle-replace-btn-div > button { @@ -169,7 +168,7 @@ pre.CodeMirror-line { .CodeMirror-search-field { display: block; width: 100%; - margin-bottom: #{12 / $base-font-size}rem; + margin-bottom: #{4 / $base-font-size}rem; @include themify() { color: getThemifyVariable('input-text-color'); background-color: getThemifyVariable('input-secondary-background-color'); @@ -209,8 +208,8 @@ pre.CodeMirror-line { } } - width: #{25 / $base-font-size}rem; - height: #{25 / $base-font-size}rem; + width: #{35 / $base-font-size}rem; + height: #{35 / $base-font-size}rem; & + & { margin-left: #{3 / $base-font-size}rem; @@ -270,6 +269,26 @@ pre.CodeMirror-line { background-image: url(../images/down-arrow.svg?byUrl) } +// Replace button +.CodeMirror-replace-button::after { + display: block; + content: ' '; + + width: 20px; + height: 20px; + + @include icon(); + + background-repeat: no-repeat; + background-position: center; +} +.CodeMirror-replace-button.replace::after { + background-image: url(../images/replace.svg?byUrl); +} +.CodeMirror-replace-button.replace-all::after { + background-image: url(../images/replace-all.svg?byUrl); +} + /* Close button */ diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 8f7aefefa6..22c3a70766 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -146,25 +146,27 @@ export default function(CodeMirror) { return nextState; } - var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-button")[0]; + var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-toggle-button")[0]; var toggleReplaceBtnDiv = dialog.getElementsByClassName("Toggle-replace-btn-div")[0]; var replaceDiv = dialog.getElementsByClassName("CodeMirror-replace-div")[0]; + var replaceDivHeightOpened = "45px", replaceDivHeightClosed = "0px"; + var toggleButtonHeightOpened = "80px", toggleButtonHeightClosed = "40px"; if (replaceOpened) { - replaceDiv.style.height = "50px"; - toggleReplaceBtnDiv.style.height = "90px"; - showReplaceButton.style.height = "90px"; + replaceDiv.style.height = replaceDivHeightOpened; + toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; + showReplaceButton.style.height = toggleButtonHeightOpened; showReplaceButton.innerHTML = "▼"; } CodeMirror.on(showReplaceButton, "click", function () { if (replaceDiv.style.height === "0px") { - replaceDiv.style.height = "50px"; - toggleReplaceBtnDiv.style.height = "90px"; - showReplaceButton.style.height = "90px"; + replaceDiv.style.height = replaceDivHeightOpened; + toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; + showReplaceButton.style.height = toggleButtonHeightOpened; showReplaceButton.innerHTML = "▼"; } else { - replaceDiv.style.height = "0px"; - toggleReplaceBtnDiv.style.height = "40px"; - showReplaceButton.style.height = "40px"; + replaceDiv.style.height = replaceDivHeightClosed; + toggleReplaceBtnDiv.style.height = toggleButtonHeightClosed; + showReplaceButton.style.height = toggleButtonHeightClosed; showReplaceButton.innerHTML = "▶"; } }); @@ -465,7 +467,7 @@ export default function(CodeMirror) { title="Replace" aria-label="Replace" role="button" - class="CodeMirror-search-modifier-button CodeMirror-replace-button" + class="CodeMirror-search-modifier-button CodeMirror-replace-toggle-button" > @@ -534,18 +536,16 @@ export default function(CodeMirror) { aria-label="Replace" role="button" id="Btn-replace" - class="CodeMirror-search-modifier-button CodeMirror-replace-button" + class="CodeMirror-search-modifier-button CodeMirror-replace-button icon replace" > -
    From dc356b271009cc4498a59e4cc230ed4a309edbc9 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Thu, 17 Sep 2020 22:53:49 +0530 Subject: [PATCH 11/32] Added text-highlighting in replace --- client/utils/codemirror-search.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 22c3a70766..be1c220cbf 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -28,6 +28,22 @@ export default function(CodeMirror) { }}; } + function replaceOverlay(cursorFrom, cursorTo, flag) { + return {token: function(stream) { + if (!flag && cursorFrom.line == stream.lineOracle.line) { + if (stream.pos == 0) { + stream.pos = cursorFrom.ch; + } else { + stream.pos = cursorTo.ch; + flag = true; + return 'searching'; + } + } else { + stream.skipToEnd(); + } + }}; + } + function SearchState() { this.posFrom = this.posTo = this.lastQuery = this.query = null; this.overlay = null; @@ -227,12 +243,19 @@ export default function(CodeMirror) { buttons[nextButton].focus(); } if (e.keyCode === 27) { // esc + // Remove replace-overlay & focus on the editor. + const state = getSearchState(cm); + cm.removeOverlay(state.overlay, state.caseInsensitive); cm.focus(); } }); button.addEventListener("click", function () { if (index === buttons.length - 1) { // "done" lastSelectedIndex = 0; + // Remove replace-overlay & focus on the editor. + const state = getSearchState(cm); + cm.removeOverlay(state.overlay, state.caseInsensitive); + cm.focus(); } }) })(i); @@ -416,7 +439,13 @@ export default function(CodeMirror) { if (!(match = cursor.findNext()) || (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; } - cm.setSelection(cursor.from(), cursor.to()); + + // Add an overlay to the text to be replaced. + cm.removeOverlay(state.overlay, state.caseInsensitive); + var flag = false; + state.overlay = replaceOverlay(cursor.from(), cursor.to(), flag); + cm.addOverlay(state.overlay); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60); confirmDialog(cm, doReplaceConfirm, "Replace?", [function() {doReplace(match);}, advance, From 62c788c84832fd1a72c7a497252ba3d1b609d584 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Fri, 18 Sep 2020 17:16:10 -0400 Subject: [PATCH 12/32] [#1519] Minor styling changes --- client/styles/components/_editor.scss | 44 +++++++++++---------------- client/utils/codemirror-search.js | 12 +++++--- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index e4bf3e1fda..6d7d382e48 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -110,14 +110,17 @@ pre.CodeMirror-line { flex-wrap: wrap; justify-content: space-between; } + .Toggle-replace-btn-div { height: 40px; padding: 0; } + .Toggle-replace-btn-div > button { width: 100%; height: 100%; } + .CodeMirror-find-div { padding: 0px; display: flex; @@ -125,25 +128,29 @@ pre.CodeMirror-line { align-items: center; flex-wrap: nowrap; } + .CodeMirror-search-modifiers { margin-left: 10px; } + .CodeMirror-search-results { margin: 0px 20px; width: 75px; + font-size: #{12/$base-font-size}rem; } + .CodeMirror-find-controls { display: flex; } .CodeMirror-replace-div { display: flex; - width: 293px; - justify-content: center; + justify-content: flex-start; align-items: center; } .CodeMirror-replace-controls { display: flex; + margin-left: #{10 / $base-font-size}rem; } .CodeMirror-replace-options { @@ -168,6 +175,7 @@ pre.CodeMirror-line { .CodeMirror-search-field { display: block; width: 100%; + max-width: #{166/$base-font-size}rem; margin-bottom: #{4 / $base-font-size}rem; @include themify() { color: getThemifyVariable('input-text-color'); @@ -176,6 +184,11 @@ pre.CodeMirror-line { } } +.CodeMirror-search-nav { + display: flex; + align-items: center; +} + .CodeMirror-search-count { display: block; height: #{20 / $base-font-size}rem; @@ -187,9 +200,6 @@ pre.CodeMirror-line { justify-content: space-between; } -/* - -*/ .CodeMirror-search-modifiers { display: flex; justify-content: flex-end; @@ -269,32 +279,12 @@ pre.CodeMirror-line { background-image: url(../images/down-arrow.svg?byUrl) } -// Replace button -.CodeMirror-replace-button::after { - display: block; - content: ' '; - - width: 20px; - height: 20px; - - @include icon(); - - background-repeat: no-repeat; - background-position: center; -} -.CodeMirror-replace-button.replace::after { - background-image: url(../images/replace.svg?byUrl); -} -.CodeMirror-replace-button.replace-all::after { - background-image: url(../images/replace-all.svg?byUrl); -} - /* Close button */ -.CodeMirror-close-button { + .CodeMirror-close-button-container { display: flex; - flex-direction: row; + align-items: center; } // Visually hide button text diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index be1c220cbf..7cf0bb989d 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -534,7 +534,7 @@ export default function(CodeMirror) {
    - +

    No results

    -
    +
    -
    +
    From e89f2efba66aecbab063f5d6a610b7a63cba112d Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Sat, 26 Sep 2020 09:51:23 +0530 Subject: [PATCH 13/32] Improved replace UX --- client/utils/codemirror-search.js | 99 +++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 7cf0bb989d..7c5e839c44 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -50,6 +50,7 @@ export default function(CodeMirror) { this.regexp = false; this.caseInsensitive = true; this.wholeWord = false; + this.replaceStarted = false; } function getSearchState(cm) { @@ -89,6 +90,12 @@ export default function(CodeMirror) { CodeMirror.on(searchField, "keyup", function (e) { if (e.keyCode === 13) { // If enter is pressed, then shift focus to replace field + var state = getSearchState(cm); + startSearch(cm, state, searchField.value); + state.replaceStarted = true; + cm.focus(); + CodeMirror.commands.findNext(cm); + searchField.blur(); replaceField.focus(); } else if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search @@ -162,19 +169,11 @@ export default function(CodeMirror) { return nextState; } - var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-toggle-button")[0]; - var toggleReplaceBtnDiv = dialog.getElementsByClassName("Toggle-replace-btn-div")[0]; - var replaceDiv = dialog.getElementsByClassName("CodeMirror-replace-div")[0]; - var replaceDivHeightOpened = "45px", replaceDivHeightClosed = "0px"; - var toggleButtonHeightOpened = "80px", toggleButtonHeightClosed = "40px"; - if (replaceOpened) { - replaceDiv.style.height = replaceDivHeightOpened; - toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; - showReplaceButton.style.height = toggleButtonHeightOpened; - showReplaceButton.innerHTML = "▼"; - } - CodeMirror.on(showReplaceButton, "click", function () { - if (replaceDiv.style.height === "0px") { + function toggleReplace(open) { + var replaceDivHeightOpened = "45px", replaceDivHeightClosed = "0px"; + var toggleButtonHeightOpened = "80px", toggleButtonHeightClosed = "40px"; + + if (open) { replaceDiv.style.height = replaceDivHeightOpened; toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; showReplaceButton.style.height = toggleButtonHeightOpened; @@ -185,27 +184,88 @@ export default function(CodeMirror) { showReplaceButton.style.height = toggleButtonHeightClosed; showReplaceButton.innerHTML = "▶"; } + } + + var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-toggle-button")[0]; + var toggleReplaceBtnDiv = dialog.getElementsByClassName("Toggle-replace-btn-div")[0]; + var replaceDiv = dialog.getElementsByClassName("CodeMirror-replace-div")[0]; + if (replaceOpened) { + toggleReplace(true); + } + CodeMirror.on(showReplaceButton, "click", function () { + if (replaceDiv.style.height === "0px") { + toggleReplace(true); + } else { + toggleReplace(false); + } }); var replaceField = document.getElementById('Replace-input-field'); CodeMirror.on(replaceField, "keyup", function (e) { + if (!searchField.value) { + searchField.focus(); + return; + } + var state = getSearchState(cm); + var query = parseQuery(searchField.value, state); + var withText = parseString(replaceField.value); if (e.keyCode === 13) // if enter { - startSearch(cm, getSearchState(cm), searchField.value); - replace(cm, parseString(searchField.value), parseString(replaceField.value)); + var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var match = cursor.findNext(); + cm.setSelection(cursor.from(), cursor.to()); + doReplace(match, cursor, query, withText); } }) + function doReplace(match, cursor, query, withText) { + cursor.replace(typeof query == "string" ? withText : + withText.replace(/\$(\d)/g, function(_, i) {return match[i];})); + cursor.findNext(); + cm.focus(); + CodeMirror.commands.findNext(cm); + searchField.blur(); + }; + var doReplaceButton = document.getElementById('Btn-replace'); CodeMirror.on(doReplaceButton, "click", function(e) { - startSearch(cm, getSearchState(cm), searchField.value); - replace(cm, parseString(searchField.value), parseString(replaceField.value)); + if (!searchField.value) { + searchField.focus(); + return; + } + var state = getSearchState(cm); + var query = parseQuery(searchField.value, state); + var withText = parseString(replaceField.value); + if (state.replaceStarted) { + var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var match = cursor.findNext(); + cm.setSelection(cursor.from(), cursor.to()); + doReplace(match, cursor, query, withText); + } else { + startSearch(cm, state, searchField.value); + state.replaceStarted = true; + cm.focus(); + CodeMirror.commands.findNext(cm); + searchField.blur(); + } }) var doReplaceAllButton = document.getElementById('Btn-replace-all'); CodeMirror.on(doReplaceAllButton, "click", function(e) { - startSearch(cm, getSearchState(cm), searchField.value); - replace(cm, parseString(searchField.value), parseString(replaceField.value), true); + if (!searchField.value) { + searchField.focus(); + return; + } + var state = getSearchState(cm); + var query = parseQuery(searchField.value, state); + var withText = parseString(replaceField.value); + if (state.replaceStarted) { + replaceAll(cm, query, withText); + state.replaceStarted = false; + } else { + startSearch(cm, state, searchField.value); + state.replaceStarted = true; + } }) } else { @@ -399,6 +459,7 @@ export default function(CodeMirror) { function clearSearch(cm) {cm.operation(function() { var state = getSearchState(cm); state.lastQuery = state.queryText; + state.replaceStarted = false; if (!state.query) return; state.query = state.queryText = null; cm.removeOverlay(state.overlay); From 741fdaa8d529dbd6a0119127a7c077d7e8c917b0 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Sat, 26 Sep 2020 10:38:21 +0530 Subject: [PATCH 14/32] Removed redundant code --- client/utils/codemirror-search.js | 135 +----------------------------- 1 file changed, 1 insertion(+), 134 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 7c5e839c44..0e9821cb6c 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -28,22 +28,6 @@ export default function(CodeMirror) { }}; } - function replaceOverlay(cursorFrom, cursorTo, flag) { - return {token: function(stream) { - if (!flag && cursorFrom.line == stream.lineOracle.line) { - if (stream.pos == 0) { - stream.pos = cursorFrom.ch; - } else { - stream.pos = cursorTo.ch; - flag = true; - return 'searching'; - } - } else { - stream.skipToEnd(); - } - }}; - } - function SearchState() { this.posFrom = this.posTo = this.lastQuery = this.query = null; this.overlay = null; @@ -182,7 +166,7 @@ export default function(CodeMirror) { replaceDiv.style.height = replaceDivHeightClosed; toggleReplaceBtnDiv.style.height = toggleButtonHeightClosed; showReplaceButton.style.height = toggleButtonHeightClosed; - showReplaceButton.innerHTML = "▶"; + showReplaceButton.innerHTML = "►"; } } @@ -279,49 +263,6 @@ export default function(CodeMirror) { else f(prompt(shortText, deflt)); } - var lastSelectedIndex = 0; - function confirmDialog(cm, text, shortText, fs) { - if (cm.openConfirm) cm.openConfirm(text, fs); - else if (confirm(shortText)) fs[0](); - - var dialog = document.getElementsByClassName("CodeMirror-dialog")[0]; - var buttons = dialog.getElementsByTagName("button"); - buttons[lastSelectedIndex].focus(); - for (var i = 0; i < buttons.length; i += 1) { - (function (index) { - var button = buttons[index]; - button.addEventListener("focus", function (e) { - lastSelectedIndex = index === buttons.length - 1 ? 0 : index; - }); - button.addEventListener("keyup", function (e) { - if (e.keyCode === 37) { // arrow left - var prevButton = index === 0 ? buttons.length - 1 : index - 1; - buttons[prevButton].focus(); - } - if (e.keyCode === 39) { // arrow right - var nextButton = index === buttons.length - 1 ? 0 : index + 1; - buttons[nextButton].focus(); - } - if (e.keyCode === 27) { // esc - // Remove replace-overlay & focus on the editor. - const state = getSearchState(cm); - cm.removeOverlay(state.overlay, state.caseInsensitive); - cm.focus(); - } - }); - button.addEventListener("click", function () { - if (index === buttons.length - 1) { // "done" - lastSelectedIndex = 0; - // Remove replace-overlay & focus on the editor. - const state = getSearchState(cm); - cm.removeOverlay(state.overlay, state.caseInsensitive); - cm.focus(); - } - }) - })(i); - } - } - function parseString(string) { return string.replace(/\\(.)/g, function(_, ch) { if (ch == "n") return "\n" @@ -477,79 +418,6 @@ export default function(CodeMirror) { }); } - function replace(cm, queryText, withText, all) { - if (!queryText) return; - const state = getSearchState(cm); - var prevDialog = document.getElementsByClassName("CodeMirror-dialog")[0]; - if (prevDialog) { - clearSearch(cm); - } - prevDialog.parentNode.removeChild(prevDialog); - cm.focus(); - if (cm.getOption("readOnly")) return; - var query = parseQuery(queryText, state); - if (all) { - replaceAll(cm, query, withText) - } else { - clearSearch(cm); - var cursor = getSearchCursor(cm, query, cm.getCursor("from")); - var advance = function() { - var start = cursor.from(), match; - if (!(match = cursor.findNext())) { - cursor = getSearchCursor(cm, query); - if (!(match = cursor.findNext()) || - (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; - } - - // Add an overlay to the text to be replaced. - cm.removeOverlay(state.overlay, state.caseInsensitive); - var flag = false; - state.overlay = replaceOverlay(cursor.from(), cursor.to(), flag); - cm.addOverlay(state.overlay); - - cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60); - confirmDialog(cm, doReplaceConfirm, "Replace?", - [function() {doReplace(match);}, advance, - function() {replaceAll(cm, query, withText)}]); - }; - var doReplace = function(match) { - cursor.replace(typeof query == "string" ? withText : - withText.replace(/\$(\d)/g, function(_, i) {return match[i];})); - advance(); - }; - advance(); - } - } - - var doReplaceConfirm = ` -
    - - - - -
    - `; - var queryDialog = `
    @@ -652,5 +520,4 @@ export default function(CodeMirror) { CodeMirror.commands.findPrev = function(cm) {doFindAndReplace(cm, true);}; CodeMirror.commands.clearSearch = clearSearch; CodeMirror.commands.replace = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; - CodeMirror.commands.replaceAll = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; }; From f5f1c1b5eb299c056b2a570d1c8c4ec8c5ffae36 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Sat, 26 Sep 2020 22:20:36 +0530 Subject: [PATCH 15/32] Removed more redundant files --- client/components/__test__/Nav.test.jsx | 1 - client/images/replace-all.svg | 23 ------------------- client/images/replace.svg | 16 ------------- translations/locales/en-US/translations.json | 4 +--- translations/locales/es-419/translations.json | 4 +--- 5 files changed, 2 insertions(+), 46 deletions(-) delete mode 100644 client/images/replace-all.svg delete mode 100644 client/images/replace.svg diff --git a/client/components/__test__/Nav.test.jsx b/client/components/__test__/Nav.test.jsx index 32a00a82a5..e4dd0c0acd 100644 --- a/client/components/__test__/Nav.test.jsx +++ b/client/components/__test__/Nav.test.jsx @@ -37,7 +37,6 @@ describe('Nav', () => { findNext: jest.fn(), findPrev: jest.fn(), showReplace: jest.fn(), - showReplaceAll: jest.fn(), }, startSketch: jest.fn(), stopSketch: jest.fn(), diff --git a/client/images/replace-all.svg b/client/images/replace-all.svg deleted file mode 100644 index d1bb46d139..0000000000 --- a/client/images/replace-all.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/client/images/replace.svg b/client/images/replace.svg deleted file mode 100644 index 69a506e641..0000000000 --- a/client/images/replace.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index fa39f8ba62..3aeb6687dd 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -16,8 +16,7 @@ "Find": "Find", "FindNext": "Find Next", "FindPrevious": "Find Previous", - "Replace": "Replace", - "ReplaceAll": "Replace All" + "Replace": "Replace" }, "Sketch": { "Title": "Sketch", @@ -168,7 +167,6 @@ "FindNextMatch": "Find Next Match", "FindPrevMatch": "Find Previous Match", "ReplaceTextMatch": "Replace Text Match", - "ReplaceAllTextMatches": "Replace All Text Matches", "IndentCodeLeft": "Indent Code Left", "IndentCodeRight": "Indent Code Right", "CommentLine": "Comment Line", diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json index 81d536dd2e..2e7e1ba605 100644 --- a/translations/locales/es-419/translations.json +++ b/translations/locales/es-419/translations.json @@ -16,8 +16,7 @@ "Find": "Buscar", "FindNext": "Buscar siguiente", "FindPrevious": "Buscar anterior", - "Replace": "Reemplazar", - "ReplaceAll": "Reemplaza todo" + "Replace": "Reemplazar" }, "Sketch": { "Title": "Bosquejo", @@ -168,7 +167,6 @@ "FindNextMatch": "Encontrar siguiente ocurrencia", "FindPrevMatch": "Encontrar ocurrencia previa", "ReplaceTextMatch": "Reemplazar coincidencia de texto", - "ReplaceAllTextMatches": "Reemplazar todas las coincidencias de texto", "IndentCodeLeft": "Indentar código a la izquierda", "IndentCodeRight": "Indentar código a la derecha", "CommentLine": "Comentar línea de código", From 1812c2b5753f80f7d08daf815abd97184a27fe16 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Wed, 7 Oct 2020 23:59:07 +0530 Subject: [PATCH 16/32] Added translations to codemirror-search --- client/i18n.js | 2 +- client/utils/codemirror-search.js | 174 +++++++++--------- translations/locales/en-US/translations.json | 14 ++ translations/locales/es-419/translations.json | 14 ++ 4 files changed, 118 insertions(+), 86 deletions(-) diff --git a/client/i18n.js b/client/i18n.js index 6a20bc0e9a..b4692a593e 100644 --- a/client/i18n.js +++ b/client/i18n.js @@ -40,7 +40,7 @@ i18n // .use(LanguageDetector)// to detect the language from currentBrowser .use(Backend) // to fetch the data from server .init({ - lng: 'en-US', + lng: 'es-419', fallbackLng, // if user computer language is not on the list of available languages, than we will be using the fallback language specified earlier debug: false, backend: options, diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 0e9821cb6c..d59c4ecda1 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -9,6 +9,7 @@ // Ctrl-G (or whatever is bound to findNext) press. You prevent a // replace by making sure the match is no longer selected when hitting // Ctrl-G. +import i18n from '../i18n'; export default function(CodeMirror) { "use strict"; @@ -85,7 +86,7 @@ export default function(CodeMirror) { else if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search startSearch(cm, getSearchState(cm), searchField.value); } else if (searchField.value.length <= 1) { - cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = 'No results'; + cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); } }); @@ -329,6 +330,7 @@ export default function(CodeMirror) { return findNext(cm, rev); } var q = cm.getSelection() || state.lastQuery; + var queryDialog = getQueryDialog(); if (persistent && cm.openDialog) { var hiding = null; var searchNext = function(query, event) { @@ -382,7 +384,7 @@ export default function(CodeMirror) { if (!cursor.find(rev)) { cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); if (!cursor.find(rev)) { - cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = 'No results'; + cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); return; } } @@ -418,100 +420,102 @@ export default function(CodeMirror) { }); } - var queryDialog = ` -
    -
    - -
    -
    -
    -
    - -
    -
    -
    - - - + var getQueryDialog = function() { + return (` +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + + + +
    +
    +

    ${i18n.t('CodemirrorFindAndReplace.NoResults')}

    + + +
    +
    + +
    -
    -

    No results

    +
    +
    + +
    -
    -
    -
    -
    - -
    - - -
    -
    -
    - `; + `); + } CodeMirror.commands.findPersistent = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; CodeMirror.commands.findPersistentNext = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index 3aeb6687dd..b1960f4eea 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -49,6 +49,20 @@ "LogOut": "Log Out" } }, + "CodemirrorFindAndReplace": { + "Find": "Find", + "FindPlaceholder": "Find in files", + "Replace": "Replace", + "ReplaceAll": "Replace All", + "ReplacePlaceholder": "Text to replace", + "Regex": "Regular expression", + "CaseSensitive": "Case sensitive", + "WholeWords": "Whole words", + "Previous": "Previous", + "Next": "Next", + "NoResults": "No results", + "Close": "Close" + }, "LoginForm": { "UsernameOrEmail": "Email or Username", "UsernameOrEmailARIA": "Email or Username", diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json index 2e7e1ba605..e1434f5c2c 100644 --- a/translations/locales/es-419/translations.json +++ b/translations/locales/es-419/translations.json @@ -49,6 +49,20 @@ "LogOut": "Cerrar sesión" } }, + "CodemirrorFindAndReplace": { + "Find": "Buscar", + "FindPlaceholder": "Buscar en archivos", + "Replace": "Reemplazar", + "ReplaceAll": "Reemplaza todo", + "ReplacePlaceholder": "Texto para reemplazar", + "Regex": "Expresión regular", + "CaseSensitive": "Distingue mayúsculas y minúsculas", + "WholeWords": "Toda palabra", + "Previous": "Previo", + "Next": "Próximo", + "NoResults": "No hay resultados", + "Close": "Cerca" + }, "LoginForm": { "UsernameOrEmail": "Correo o Identificación", "UsernameOrEmailARIA": "Introduce Correo o Identificación", From 4813f88771451a70e24954034d5cb0a2b285fe84 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Thu, 8 Oct 2020 00:02:07 +0530 Subject: [PATCH 17/32] Minor fix --- client/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/i18n.js b/client/i18n.js index b4692a593e..6a20bc0e9a 100644 --- a/client/i18n.js +++ b/client/i18n.js @@ -40,7 +40,7 @@ i18n // .use(LanguageDetector)// to detect the language from currentBrowser .use(Backend) // to fetch the data from server .init({ - lng: 'es-419', + lng: 'en-US', fallbackLng, // if user computer language is not on the list of available languages, than we will be using the fallback language specified earlier debug: false, backend: options, From d65b432f8a09d7e12c8819a85c6147f435d396e1 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Thu, 8 Oct 2020 23:25:35 +0530 Subject: [PATCH 18/32] Updated spanish keys --- translations/locales/es-419/translations.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json index e1434f5c2c..c579d97866 100644 --- a/translations/locales/es-419/translations.json +++ b/translations/locales/es-419/translations.json @@ -54,14 +54,14 @@ "FindPlaceholder": "Buscar en archivos", "Replace": "Reemplazar", "ReplaceAll": "Reemplaza todo", - "ReplacePlaceholder": "Texto para reemplazar", + "ReplacePlaceholder": "Texto a reemplazar", "Regex": "Expresión regular", - "CaseSensitive": "Distingue mayúsculas y minúsculas", - "WholeWords": "Toda palabra", + "CaseSensitive": "Distinguir mayúsculas y minúsculas", + "WholeWords": "Palabra completa", "Previous": "Previo", "Next": "Próximo", "NoResults": "No hay resultados", - "Close": "Cerca" + "Close": "Cerrar" }, "LoginForm": { "UsernameOrEmail": "Correo o Identificación", From 6addf4b081769b118cae8aa6ca23bee0b87101b9 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Fri, 30 Oct 2020 15:20:15 -0400 Subject: [PATCH 19/32] [#1519] highlight find matches when focused on search --- client/modules/IDE/components/Editor.jsx | 6 +- client/styles/abstracts/_variables.scss | 2 + client/styles/components/_editor.scss | 8 + .../_p5-contrast-codemirror-theme.scss | 8 + .../components/_p5-dark-codemirror-theme.scss | 9 + .../_p5-light-codemirror-theme.scss | 8 + client/utils/codemirror-search.js | 924 ++++++++++-------- 7 files changed, 539 insertions(+), 426 deletions(-) diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index d86bc19634..14185889ea 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -22,6 +22,7 @@ import 'codemirror/addon/search/match-highlighter'; import 'codemirror/addon/search/jump-to-line'; import 'codemirror/addon/edit/matchbrackets'; import 'codemirror/addon/edit/closebrackets'; +import 'codemirror/addon/selection/mark-selection'; import { JSHINT } from 'jshint'; import { CSSLint } from 'csslint'; @@ -37,7 +38,7 @@ import Timer from '../components/Timer'; import EditorAccessibility from '../components/EditorAccessibility'; import { metaKey, } from '../../../utils/metaKey'; -import search from '../../../utils/codemirror-search'; +import '../../../utils/codemirror-search'; import beepUrl from '../../../sounds/audioAlert.mp3'; import UnsavedChangesDotIcon from '../../../images/unsaved-changes-dot.svg'; @@ -55,8 +56,6 @@ import * as UserActions from '../../User/actions'; import * as ToastActions from '../actions/toast'; import * as ConsoleActions from '../actions/console'; -search(CodeMirror); - const beautifyCSS = beautifyJS.css; const beautifyHTML = beautifyJS.html; @@ -107,6 +106,7 @@ class Editor extends React.Component { highlightSelectionMatches: true, // highlight current search match matchBrackets: true, autoCloseBrackets: this.props.autocloseBracketsQuotes, + styleSelectedText: true, lint: { onUpdateLinting: ((annotations) => { this.props.hideRuntimeErrorWarning(); diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index d356c68168..0c6e0ab56b 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -2,7 +2,9 @@ $base-font-size: 12; //colors $p5js-pink: #ed225d; +$p5js-pink-opacity: #ed225d66; $processing-blue: #007BBB; +$processing-blue-opacity: #007BBB66; $p5js-active-pink: #f10046; $white: #fff; $black: #000; diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 6d7d382e48..7681503141 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -279,6 +279,14 @@ pre.CodeMirror-line { background-image: url(../images/down-arrow.svg?byUrl) } +.CodeMirror-search-match { + background: gold; + border-top: 1px solid orange; + border-bottom: 1px solid orange; + box-sizing: border-box; + opacity: .5; +} + /* Close button */ diff --git a/client/styles/components/_p5-contrast-codemirror-theme.scss b/client/styles/components/_p5-contrast-codemirror-theme.scss index b9f837cc15..aba0723666 100644 --- a/client/styles/components/_p5-contrast-codemirror-theme.scss +++ b/client/styles/components/_p5-contrast-codemirror-theme.scss @@ -135,3 +135,11 @@ $p5-contrast-activeline: #999999; .cm-s-p5-contrast .CodeMirror-cursor { border-left: 1px solid $p5-contrast-white; } + +.cm-s-p5-contrast .cm-searching { + background-color: $processing-blue-opacity; +} + +.cm-s-p5-contrast .CodeMirror-selectedtext { + background-color: $medium-dark; +} diff --git a/client/styles/components/_p5-dark-codemirror-theme.scss b/client/styles/components/_p5-dark-codemirror-theme.scss index 51879fdfd9..68925469ac 100644 --- a/client/styles/components/_p5-dark-codemirror-theme.scss +++ b/client/styles/components/_p5-dark-codemirror-theme.scss @@ -142,3 +142,12 @@ $p5-dark-error: #df3a3d; .cm-s-p5-dark .CodeMirror-cursor { border-left: 1px solid $p5-dark-white; } + +.cm-s-p5-dark .cm-searching { + background-color: $p5js-pink-opacity; +} + +.cm-s-p5-dark .CodeMirror-selectedtext { + background-color: $medium-dark; +} + diff --git a/client/styles/components/_p5-light-codemirror-theme.scss b/client/styles/components/_p5-light-codemirror-theme.scss index 28345a34e0..ca30e2d854 100644 --- a/client/styles/components/_p5-light-codemirror-theme.scss +++ b/client/styles/components/_p5-light-codemirror-theme.scss @@ -135,3 +135,11 @@ $p5-light-activeline: rgb(207, 207, 207); .cm-s-p5-light .CodeMirror-cursor { border-left: 1px solid $p5-light-black; } + +.cm-s-p5-light .cm-searching { + background-color: $p5js-pink-opacity; +} + +.cm-s-p5-light .CodeMirror-selectedtext { + background-color: $medium-light; +} diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index d59c4ecda1..16f246f9ba 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -10,375 +10,421 @@ // replace by making sure the match is no longer selected when hitting // Ctrl-G. import i18n from '../i18n'; +import CodeMirror from 'codemirror'; -export default function(CodeMirror) { - "use strict"; +function searchOverlay(query, caseInsensitive) { + if (typeof query == 'string') query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), caseInsensitive ? 'gi' : 'g'); + else if (!query.global) query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g'); - function searchOverlay(query) { - return {token: function(stream) { + return { + token: function (stream) { query.lastIndex = stream.pos; var match = query.exec(stream.string); if (match && match.index == stream.pos) { stream.pos += match[0].length || 1; - return "searching"; + return 'searching'; } else if (match) { stream.pos = match.index; } else { stream.skipToEnd(); } - }}; - } + }, + }; +} + +function SearchState() { + this.posFrom = this.posTo = this.lastQuery = this.query = null; + this.overlay = null; + this.regexp = false; + this.caseInsensitive = true; + this.wholeWord = false; + this.replaceStarted = false; +} + +function getSearchState(cm) { + return cm.state.search || (cm.state.search = new SearchState()); +} + +function queryCaseInsensitive(query) { + return typeof query == 'string' && query == query.toLowerCase(); +} + +function getSearchCursor(cm, query, pos) { + // Heuristic: if the query string is all lowercase, do a case insensitive search. + return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); +} + +function isMouseClick(event) { + if(event.detail > 0) return true; + else return false; +} + +function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { + var replaceOpened = false; + var searchField = document.getElementsByClassName("CodeMirror-search-field")[0]; + if (!searchField) { + cm.openDialog(text, onEnter, { + value: deflt, + selectValueOnOpen: true, + closeOnEnter: false, + onClose: function () { + clearSearch(cm); + }, + onKeyDown: onKeyDown, + closeOnBlur: false + }); - function SearchState() { - this.posFrom = this.posTo = this.lastQuery = this.query = null; - this.overlay = null; - this.regexp = false; - this.caseInsensitive = true; - this.wholeWord = false; - this.replaceStarted = false; - } + searchField = document.getElementById("Find-input-field"); - function getSearchState(cm) { - return cm.state.search || (cm.state.search = new SearchState()); - } + var dialog = document.getElementsByClassName("CodeMirror-dialog")[0]; + var closeButton = dialog.getElementsByClassName("close")[0]; - function getSearchCursor(cm, query, pos) { - return cm.getSearchCursor(query, pos, getSearchState(cm).caseInsensitive); - } + var state = getSearchState(cm); - function isMouseClick(event) { - if(event.detail > 0) return true; - else return false; - } + CodeMirror.on(searchField, "keyup", function (e) { + if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search + startSearch(cm, getSearchState(cm), searchField.value); + } else if (searchField.value.length <= 1) { + cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); + } + }); - function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { - var searchField = document.getElementsByClassName("CodeMirror-search-field")[0]; - if (!searchField) { - cm.openDialog(text, onEnter, { - value: deflt, - selectValueOnOpen: true, - closeOnEnter: false, - onClose: function () { - clearSearch(cm); - }, - onKeyDown: onKeyDown, - closeOnBlur: false - }); + CodeMirror.on(closeButton, "click", function () { + clearSearch(cm); + dialog.parentNode.removeChild(dialog); + cm.focus(); + }); - searchField = document.getElementById("Find-input-field"); + var upArrow = dialog.getElementsByClassName("up-arrow")[0]; + CodeMirror.on(upArrow, "click", function () { + cm.focus(); + CodeMirror.commands.findPrev(cm); + searchField.blur(); + }); - var dialog = document.getElementsByClassName("CodeMirror-dialog")[0]; - var closeButton = dialog.getElementsByClassName("close")[0]; + var downArrow = dialog.getElementsByClassName("down-arrow")[0]; + CodeMirror.on(downArrow, "click", function () { + cm.focus(); + CodeMirror.commands.findNext(cm); + searchField.blur(); + }); + var regexpButton = dialog.getElementsByClassName("CodeMirror-regexp-button")[0]; + CodeMirror.on(regexpButton, "click", function (event) { var state = getSearchState(cm); + state.regexp = toggle(regexpButton); + startSearch(cm, getSearchState(cm), searchField.value); + if(isMouseClick(event)) searchField.focus(); + }); - CodeMirror.on(searchField, "keyup", function (e) { - if (e.keyCode === 13) { - // If enter is pressed, then shift focus to replace field - var state = getSearchState(cm); - startSearch(cm, state, searchField.value); - state.replaceStarted = true; - cm.focus(); - CodeMirror.commands.findNext(cm); - searchField.blur(); - replaceField.focus(); - } - else if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search - startSearch(cm, getSearchState(cm), searchField.value); - } else if (searchField.value.length <= 1) { - cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); - } - }); - - CodeMirror.on(closeButton, "click", function () { - clearSearch(cm); - dialog.parentNode.removeChild(dialog); - cm.focus(); - }); - - var upArrow = dialog.getElementsByClassName("up-arrow")[0]; - CodeMirror.on(upArrow, "click", function () { - cm.focus(); - CodeMirror.commands.findPrev(cm); - searchField.blur(); - }); - - var downArrow = dialog.getElementsByClassName("down-arrow")[0]; - CodeMirror.on(downArrow, "click", function () { - cm.focus(); - CodeMirror.commands.findNext(cm); - searchField.blur(); - }); - - var regexpButton = dialog.getElementsByClassName("CodeMirror-regexp-button")[0]; - CodeMirror.on(regexpButton, "click", function (event) { - var state = getSearchState(cm); - state.regexp = toggle(regexpButton); - startSearch(cm, getSearchState(cm), searchField.value); - if(isMouseClick(event)) searchField.focus(); - }); - - toggle(regexpButton, state.regexp); + toggle(regexpButton, state.regexp); - var caseSensitiveButton = dialog.getElementsByClassName("CodeMirror-case-button")[0]; - CodeMirror.on(caseSensitiveButton, "click", function (event) { - var state = getSearchState(cm); - state.caseInsensitive = !toggle(caseSensitiveButton); - startSearch(cm, getSearchState(cm), searchField.value); - if(isMouseClick(event)) searchField.focus(); - }); + var caseSensitiveButton = dialog.getElementsByClassName("CodeMirror-case-button")[0]; + CodeMirror.on(caseSensitiveButton, "click", function (event) { + var state = getSearchState(cm); + state.caseInsensitive = !toggle(caseSensitiveButton); + startSearch(cm, getSearchState(cm), searchField.value); + if(isMouseClick(event)) searchField.focus(); + }); - toggle(caseSensitiveButton, !state.caseInsensitive); + toggle(caseSensitiveButton, !state.caseInsensitive); - var wholeWordButton = dialog.getElementsByClassName("CodeMirror-word-button")[0]; - CodeMirror.on(wholeWordButton, "click", function (event) { - var state = getSearchState(cm); - state.wholeWord = toggle(wholeWordButton); - startSearch(cm, getSearchState(cm), searchField.value); - if(isMouseClick(event)) searchField.focus(); - }); + var wholeWordButton = dialog.getElementsByClassName("CodeMirror-word-button")[0]; + CodeMirror.on(wholeWordButton, "click", function (event) { + var state = getSearchState(cm); + state.wholeWord = toggle(wholeWordButton); + startSearch(cm, getSearchState(cm), searchField.value); + if(isMouseClick(event)) searchField.focus(); + }); - toggle(wholeWordButton, state.wholeWord); + toggle(wholeWordButton, state.wholeWord); - function toggle(el, initialState) { - var currentState, nextState; + function toggle(el, initialState) { + var currentState, nextState; - if (initialState == null) { - currentState = el.getAttribute('aria-checked') === 'true'; - nextState = !currentState; - } else { - nextState = initialState; - } - - el.setAttribute('aria-checked', nextState); - return nextState; + if (initialState == null) { + currentState = el.getAttribute('aria-checked') === 'true'; + nextState = !currentState; + } else { + nextState = initialState; } + + el.setAttribute('aria-checked', nextState); + return nextState; + } + + function toggleReplace(open) { + var replaceDivHeightOpened = "45px", replaceDivHeightClosed = "0px"; + var toggleButtonHeightOpened = "80px", toggleButtonHeightClosed = "40px"; - function toggleReplace(open) { - var replaceDivHeightOpened = "45px", replaceDivHeightClosed = "0px"; - var toggleButtonHeightOpened = "80px", toggleButtonHeightClosed = "40px"; - - if (open) { - replaceDiv.style.height = replaceDivHeightOpened; - toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; - showReplaceButton.style.height = toggleButtonHeightOpened; - showReplaceButton.innerHTML = "▼"; - } else { - replaceDiv.style.height = replaceDivHeightClosed; - toggleReplaceBtnDiv.style.height = toggleButtonHeightClosed; - showReplaceButton.style.height = toggleButtonHeightClosed; - showReplaceButton.innerHTML = "►"; - } + if (open) { + replaceDiv.style.height = replaceDivHeightOpened; + toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; + showReplaceButton.style.height = toggleButtonHeightOpened; + showReplaceButton.innerHTML = "▼"; + } else { + replaceDiv.style.height = replaceDivHeightClosed; + toggleReplaceBtnDiv.style.height = toggleButtonHeightClosed; + showReplaceButton.style.height = toggleButtonHeightClosed; + showReplaceButton.innerHTML = "►"; } + } - var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-toggle-button")[0]; - var toggleReplaceBtnDiv = dialog.getElementsByClassName("Toggle-replace-btn-div")[0]; - var replaceDiv = dialog.getElementsByClassName("CodeMirror-replace-div")[0]; - if (replaceOpened) { + var showReplaceButton = dialog.getElementsByClassName("CodeMirror-replace-toggle-button")[0]; + var toggleReplaceBtnDiv = dialog.getElementsByClassName("Toggle-replace-btn-div")[0]; + var replaceDiv = dialog.getElementsByClassName("CodeMirror-replace-div")[0]; + if (replaceOpened) { + toggleReplace(true); + } + CodeMirror.on(showReplaceButton, "click", function () { + if (replaceDiv.style.height === "0px") { toggleReplace(true); + } else { + toggleReplace(false); } - CodeMirror.on(showReplaceButton, "click", function () { - if (replaceDiv.style.height === "0px") { - toggleReplace(true); - } else { - toggleReplace(false); - } - }); + }); - var replaceField = document.getElementById('Replace-input-field'); - CodeMirror.on(replaceField, "keyup", function (e) { - if (!searchField.value) { - searchField.focus(); - return; - } - var state = getSearchState(cm); - var query = parseQuery(searchField.value, state); - var withText = parseString(replaceField.value); - if (e.keyCode === 13) // if enter - { - var cursor = getSearchCursor(cm, query, cm.getCursor("from")); - var match = cursor.findNext(); - cm.setSelection(cursor.from(), cursor.to()); - doReplace(match, cursor, query, withText); - } - }) + var replaceField = document.getElementById('Replace-input-field'); + CodeMirror.on(replaceField, "keyup", function (e) { + // if (e.keyCode === 13) { + // // If enter is pressed, then shift focus to replace field + // var state = getSearchState(cm); + // startSearch(cm, state, searchField.value); + // state.replaceStarted = true; + // cm.focus(); + // CodeMirror.commands.findNext(cm); + // searchField.blur(); + // replaceField.focus(); + // } + if (!searchField.value) { + searchField.focus(); + return; + } + var state = getSearchState(cm); + var query = parseQuery(searchField.value, state); + var withText = parseString(replaceField.value); + if (e.keyCode === 13) // if enter + { + var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var match = cursor.findNext(); + cm.setSelection(cursor.from(), cursor.to()); + state.replaceStarted = true; + doReplace(match, cursor, query, withText); + } + }) - function doReplace(match, cursor, query, withText) { - cursor.replace(typeof query == "string" ? withText : - withText.replace(/\$(\d)/g, function(_, i) {return match[i];})); - cursor.findNext(); + function doReplace(match, cursor, query, withText) { + cursor.replace(typeof query == "string" ? withText : + withText.replace(/\$(\d)/g, function(_, i) {return match[i];})); + cursor.findNext(); + cm.focus(); + CodeMirror.commands.findNext(cm); + searchField.blur(); + }; + + var doReplaceButton = document.getElementById('Btn-replace'); + CodeMirror.on(doReplaceButton, "click", function(e) { + if (!searchField.value) { + searchField.focus(); + return; + } + var state = getSearchState(cm); + var query = parseQuery(searchField.value, state); + var withText = parseString(replaceField.value); + if (state.replaceStarted) { + var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var match = cursor.findNext(); + cm.setSelection(cursor.from(), cursor.to()); + doReplace(match, cursor, query, withText); + } else { + startSearch(cm, state, searchField.value); + state.replaceStarted = true; cm.focus(); CodeMirror.commands.findNext(cm); searchField.blur(); - }; - - var doReplaceButton = document.getElementById('Btn-replace'); - CodeMirror.on(doReplaceButton, "click", function(e) { - if (!searchField.value) { - searchField.focus(); - return; - } - var state = getSearchState(cm); - var query = parseQuery(searchField.value, state); - var withText = parseString(replaceField.value); - if (state.replaceStarted) { - var cursor = getSearchCursor(cm, query, cm.getCursor("from")); - var match = cursor.findNext(); - cm.setSelection(cursor.from(), cursor.to()); - doReplace(match, cursor, query, withText); - } else { - startSearch(cm, state, searchField.value); - state.replaceStarted = true; - cm.focus(); - CodeMirror.commands.findNext(cm); - searchField.blur(); - } - }) - - var doReplaceAllButton = document.getElementById('Btn-replace-all'); - CodeMirror.on(doReplaceAllButton, "click", function(e) { - if (!searchField.value) { - searchField.focus(); - return; - } - var state = getSearchState(cm); - var query = parseQuery(searchField.value, state); - var withText = parseString(replaceField.value); - if (state.replaceStarted) { - replaceAll(cm, query, withText); - state.replaceStarted = false; - } else { - startSearch(cm, state, searchField.value); - state.replaceStarted = true; - } - }) - - } else { - searchField.focus(); - searchField.select(); - } - } - - function dialog(cm, text, shortText, deflt, f) { - if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); - else f(prompt(shortText, deflt)); - } - - function parseString(string) { - return string.replace(/\\(.)/g, function(_, ch) { - if (ch == "n") return "\n" - if (ch == "r") return "\r" - return ch + } }) - } - /* - Parses the query text and state and returns - a RegExp ready for searching - */ - function parseQuery(query, state) { - var emptyQuery = 'x^'; // matches nothing - - if (query === '') { // empty string matches nothing - query = emptyQuery; - } else { - if (state.regexp === false) { - query = parseString(query); - query = query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + var doReplaceAllButton = document.getElementById('Btn-replace-all'); + CodeMirror.on(doReplaceAllButton, "click", function(e) { + if (!searchField.value) { + searchField.focus(); + return; } - - if (state.wholeWord) { - query += '\\b'; + var state = getSearchState(cm); + var query = parseQuery(searchField.value, state); + var withText = parseString(replaceField.value); + if (state.replaceStarted) { + replaceAll(cm, query, withText); + state.replaceStarted = false; + } else { + startSearch(cm, state, searchField.value); + state.replaceStarted = true; } - } + }) - var regexp; + } else { + searchField.value = deflt; + searchField.focus(); + searchField.select(); + } +} + +function dialog(cm, text, shortText, deflt, f) { + if (cm.openDialog) cm.openDialog(text, f, { value: deflt, selectValueOnOpen: true }); + else f(prompt(shortText, deflt)); +} + +function parseString(string) { + return string.replace(/\\(.)/g, function (_, ch) { + if (ch == 'n') return '\n'; + if (ch == 'r') return '\r'; + return ch; + }); +} + +function parseQuery(query) { + var isRE = query.match(/^\/(.*)\/([a-z]*)$/); + if (isRE) { try { - regexp = new RegExp(query, state.caseInsensitive ? "gi" : "g"); - } catch (e) { - regexp = new RegExp(emptyQuery, 'g'); - } - - // If the resulting regexp will match everything, do not use it - if (regexp.test('')) { - return new RegExp(emptyQuery, 'g'); - } - - return regexp; + query = new RegExp(isRE[1], isRE[2].indexOf('i') == -1 ? '' : 'i'); + } catch (e) { } // Not a regular expression after all, do a string search + } else { + query = parseString(query); } - - function startSearch(cm, state, originalQuery) { - state.queryText = originalQuery; - state.query = parseQuery(originalQuery, state); - - cm.removeOverlay(state.overlay, state.caseInsensitive); - state.overlay = searchOverlay(state.query); - cm.addOverlay(state.overlay); - if (cm.showMatchesOnScrollbar) { - if (state.annotate) { state.annotate.clear(); state.annotate = null; } - state.annotate = cm.showMatchesOnScrollbar(state.query, state.caseInsensitive); - } - if (originalQuery) { - return findNext(cm, false); + if (typeof query == 'string' ? query == '' : query.test('')) query = /x^/; + return query; +} + +function startSearch(cm, state, query) { + state.queryText = query; + state.query = parseQuery(query); + cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); + state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); + cm.addOverlay(state.overlay); + if (cm.showMatchesOnScrollbar) { + if (state.annotate) { + state.annotate.clear(); + state.annotate = null; } + state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); } +} - function doFindAndReplace(cm, rev, persistent, immediate, ignoreQuery, replaceOpened) { - var state = getSearchState(cm); - if (!ignoreQuery && state.query) { - return findNext(cm, rev); - } - var q = cm.getSelection() || state.lastQuery; - var queryDialog = getQueryDialog(); - if (persistent && cm.openDialog) { - var hiding = null; - var searchNext = function(query, event) { - CodeMirror.e_stop(event); - if (!query) return; - if (query != state.queryText) { - startSearch(cm, state, query); - state.posFrom = state.posTo = cm.getCursor(); - } - if (hiding) hiding.style.opacity = 1 - findNext(cm, event.shiftKey, function(_, to) { - var dialog - if (to.line < 3 && document.querySelector && - (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && - dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) - (hiding = dialog).style.opacity = 1 - }) - }; - persistentDialog(cm, queryDialog, q, searchNext, replaceOpened, function(event, query) { - var keyName = CodeMirror.keyName(event) - var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName] - if (!cmd) cmd = cm.getOption('extraKeys')[keyName] - if (cmd == "findNext" || cmd == "findPrev" || - cmd == "findPersistentNext" || cmd == "findPersistentPrev") { - CodeMirror.e_stop(event); - startSearch(cm, getSearchState(cm), query); - cm.execCommand(cmd); - } else if (cmd == "find" || cmd == "findPersistent") { - CodeMirror.e_stop(event); - searchNext(query, event); - } +function doSearch(cm, rev, persistent, immediate, ignoreQuery) { + var state = getSearchState(cm); + if (!ignoreQuery && state.query) { + return findNext(cm, rev); + } + var q = cm.getSelection() || state.lastQuery; + var queryDialog = getQueryDialog(); + if (persistent && cm.openDialog) { + var hiding = null; + var searchNext = function (query, event) { + CodeMirror.e_stop(event); + if (!query) return; + if (query != state.queryText) { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + } + if (hiding) hiding.style.opacity = 1; + findNext(cm, event.shiftKey, function (_, to) { + var dialog; + if ( + to.line < 3 && + document.querySelector && + (dialog = cm.display.wrapper.querySelector('.CodeMirror-dialog')) && + dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, 'window').top + ) + (hiding = dialog).style.opacity = 0.4; }); - if (immediate && q) { - startSearch(cm, state, q); - findNext(cm, rev); + }; + persistentDialog(cm, queryDialog, q, searchNext, function (event, query) { + var keyName = CodeMirror.keyName(event); + var cmd = CodeMirror.keyMap[cm.getOption('keyMap')][keyName]; + if (!cmd) cmd = cm.getOption('extraKeys')[keyName]; + if (cmd == 'findNext' || cmd == 'findPrev' || cmd == 'findPersistentNext' || cmd == 'findPersistentPrev') { + CodeMirror.e_stop(event); + startSearch(cm, getSearchState(cm), query); + cm.execCommand(cmd); + } else if (cmd == 'find' || cmd == 'findPersistent') { + CodeMirror.e_stop(event); + searchNext(query, event); } - } else { - dialog(cm, queryDialog, "Search for:", q, function(query) { - if (query && !state.query) cm.operation(function() { + }); + if (immediate && q) { + startSearch(cm, state, q); + findNext(cm, rev); + } + } else { + dialog(cm, queryDialog, 'Search for:', q, function (query) { + if (query && !state.query) + cm.operation(function () { startSearch(cm, state, query); state.posFrom = state.posTo = cm.getCursor(); findNext(cm, rev); }); - }); + }); + } +} + +function doFindAndReplace(cm, rev, persistent, immediate, ignoreQuery, replaceOpened) { + var state = getSearchState(cm); + if (!ignoreQuery && state.query) { + return findNext(cm, rev); + } + var q = cm.getSelection() || state.lastQuery; + var queryDialog = getQueryDialog(); + if (persistent && cm.openDialog) { + var hiding = null; + var searchNext = function(query, event) { + CodeMirror.e_stop(event); + if (!query) return; + if (query != state.queryText) { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + } + if (hiding) hiding.style.opacity = 1 + findNext(cm, event.shiftKey, function(_, to) { + var dialog + if (to.line < 3 && document.querySelector && + (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) && + dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top) + (hiding = dialog).style.opacity = 1 + }) + }; + persistentDialog(cm, queryDialog, q, searchNext, replaceOpened, function(event, query) { + var keyName = CodeMirror.keyName(event) + var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName] + if (!cmd) cmd = cm.getOption('extraKeys')[keyName] + if (cmd == "findNext" || cmd == "findPrev" || + cmd == "findPersistentNext" || cmd == "findPersistentPrev") { + CodeMirror.e_stop(event); + startSearch(cm, getSearchState(cm), query); + cm.execCommand(cmd); + } else if (cmd == "find" || cmd == "findPersistent") { + CodeMirror.e_stop(event); + searchNext(query, event); + } + }); + if (immediate && q) { + startSearch(cm, state, q); + findNext(cm, rev); } + } else { + dialog(cm, queryDialog, "Search for:", q, function(query) { + if (query && !state.query) cm.operation(function() { + startSearch(cm, state, query); + state.posFrom = state.posTo = cm.getCursor(); + findNext(cm, rev); + }); + }); } +} - function findNext(cm, rev, callback) {cm.operation(function() { +function findNext(cm, rev, callback) { + cm.operation(function() { var state = getSearchState(cm); var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); if (!cursor.find(rev)) { @@ -389,139 +435,171 @@ export default function(CodeMirror) { } } cm.setSelection(cursor.from(), cursor.to()); - cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60); - state.posFrom = cursor.from(); state.posTo = cursor.to(); + cm.scrollIntoView({ from: cursor.from(), to: cursor.to() }, cm.getScrollInfo().clientHeight / 2); + state.posFrom = cursor.from(); + state.posTo = cursor.to(); var num_match = cm.state.search.annotate.matches.length; var next = cm.state.search.annotate.matches .findIndex(s => s.from.ch === cursor.from().ch && s.from.line === cursor.from().line) + 1; var text_match = next + '/' + num_match; cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = text_match; if (callback) callback(cursor.from(), cursor.to()) - });} + }); +} - function clearSearch(cm) {cm.operation(function() { +function clearSearch(cm) { + cm.operation(function() { var state = getSearchState(cm); - state.lastQuery = state.queryText; + state.lastQuery = state.query; state.replaceStarted = false; if (!state.query) return; state.query = state.queryText = null; cm.removeOverlay(state.overlay); - if (state.annotate) { state.annotate.clear(); state.annotate = null; } - });} - - function replaceAll(cm, query, text) { - cm.operation(function() { - for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { - if (typeof query != "string") { - var match = cm.getRange(cursor.from(), cursor.to()).match(query); - cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); - } else cursor.replace(text); - } - }); - } - - var getQueryDialog = function() { - return (` -
    -
    - -
    -
    -
    -
    - -
    -
    -
    - - - -
    -
    -

    ${i18n.t('CodemirrorFindAndReplace.NoResults')}

    - - -
    -
    - -
    -
    + if (state.annotate) { + state.annotate.clear(); + state.annotate = null; + } + }); +} + +function replaceAll(cm, query, text) { + cm.operation(function() { + for (var cursor = getSearchCursor(cm, query); cursor.findNext(); ) { + if (typeof query != "string") { + var match = cm.getRange(cursor.from(), cursor.to()).match(query); + cursor.replace( + text.replace(/\$(\d)/g, function(_, i) { + return match[i]; + }) + ); + } else cursor.replace(text); + } + }); +} + +var getQueryDialog = function() { + return (` +
    +
    + +
    +
    +
    +
    +
    -
    - -
    +
    +
    + + +
    +
    +

    ${i18n.t('CodemirrorFindAndReplace.NoResults')}

    + +
    +
    +
    +
    + +
    + + +
    +
    - `); - } - - CodeMirror.commands.findPersistent = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; - CodeMirror.commands.findPersistentNext = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; - CodeMirror.commands.findPersistentPrev = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; - CodeMirror.commands.findNext = doFindAndReplace; - CodeMirror.commands.findPrev = function(cm) {doFindAndReplace(cm, true);}; - CodeMirror.commands.clearSearch = clearSearch; - CodeMirror.commands.replace = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; +
    + `); +} + +// CodeMirror.commands.findPersistent = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; +// CodeMirror.commands.findPersistentNext = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; +// CodeMirror.commands.findPersistentPrev = function(cm) {doFindAndReplace(cm, false, true, false, true, false);}; +// CodeMirror.commands.findNext = doFindAndReplace; +// CodeMirror.commands.findPrev = function(cm) {doFindAndReplace(cm, true);}; +// CodeMirror.commands.clearSearch = clearSearch; +// CodeMirror.commands.replace = function(cm) { doFindAndReplace(cm, false, true, false, true, true); }; + +CodeMirror.commands.find = function (cm) { + doSearch(cm); +}; +CodeMirror.commands.findPersistent = function (cm) { + doSearch(cm, false, true, false, true); +}; +CodeMirror.commands.findPersistentNext = function (cm) { + doSearch(cm, false, true, true); +}; +CodeMirror.commands.findPersistentPrev = function (cm) { + doSearch(cm, true, true, true); +}; +CodeMirror.commands.findNext = doSearch; +CodeMirror.commands.findPrev = function (cm) { + doSearch(cm, true); +}; +CodeMirror.commands.clearSearch = clearSearch; +CodeMirror.commands.replace = doFindAndReplace; +CodeMirror.commands.replaceAll = function (cm) { + doFindAndReplace(cm, true); }; From d3500907052a548df3bce67bf115e23bd9b8d56d Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Fri, 30 Oct 2020 15:40:25 -0400 Subject: [PATCH 20/32] [#1519] Update replace UX to match VSC --- client/utils/codemirror-search.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 16f246f9ba..3ffbba8e22 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -186,16 +186,6 @@ function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { var replaceField = document.getElementById('Replace-input-field'); CodeMirror.on(replaceField, "keyup", function (e) { - // if (e.keyCode === 13) { - // // If enter is pressed, then shift focus to replace field - // var state = getSearchState(cm); - // startSearch(cm, state, searchField.value); - // state.replaceStarted = true; - // cm.focus(); - // CodeMirror.commands.findNext(cm); - // searchField.blur(); - // replaceField.focus(); - // } if (!searchField.value) { searchField.focus(); return; @@ -206,7 +196,12 @@ function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { if (e.keyCode === 13) // if enter { var cursor = getSearchCursor(cm, query, cm.getCursor("from")); + var start = cursor.from(); var match = cursor.findNext(); + if (!match) { + cursor = getSearchCursor(cm, query); + if (!(match = cursor.findNext()) || (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; + } cm.setSelection(cursor.from(), cursor.to()); state.replaceStarted = true; doReplace(match, cursor, query, withText); @@ -217,9 +212,9 @@ function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { cursor.replace(typeof query == "string" ? withText : withText.replace(/\$(\d)/g, function(_, i) {return match[i];})); cursor.findNext(); - cm.focus(); + // cm.focus(); CodeMirror.commands.findNext(cm); - searchField.blur(); + // searchField.blur(); }; var doReplaceButton = document.getElementById('Btn-replace'); From ba3f388e29710e2869e04a7481ba7deac76cc998 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Sat, 31 Oct 2020 16:28:57 +0530 Subject: [PATCH 21/32] bugfix: show replace-field when replace is clicked --- client/utils/codemirror-search.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 3ffbba8e22..bc99d53829 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -59,8 +59,7 @@ function isMouseClick(event) { else return false; } -function persistentDialog(cm, text, deflt, onEnter, onKeyDown) { - var replaceOpened = false; +function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { var searchField = document.getElementsByClassName("CodeMirror-search-field")[0]; if (!searchField) { cm.openDialog(text, onEnter, { @@ -335,7 +334,7 @@ function doSearch(cm, rev, persistent, immediate, ignoreQuery) { (hiding = dialog).style.opacity = 0.4; }); }; - persistentDialog(cm, queryDialog, q, searchNext, function (event, query) { + persistentDialog(cm, queryDialog, q, searchNext, false, function (event, query) { var keyName = CodeMirror.keyName(event); var cmd = CodeMirror.keyMap[cm.getOption('keyMap')][keyName]; if (!cmd) cmd = cm.getOption('extraKeys')[keyName]; @@ -594,7 +593,9 @@ CodeMirror.commands.findPrev = function (cm) { doSearch(cm, true); }; CodeMirror.commands.clearSearch = clearSearch; -CodeMirror.commands.replace = doFindAndReplace; +CodeMirror.commands.replace = function (cm) { + doFindAndReplace(cm, false, true, false, true, true); +}; CodeMirror.commands.replaceAll = function (cm) { doFindAndReplace(cm, true); }; From cd734d6e35642ea586640a4513cda1dab05b1200 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Sat, 31 Oct 2020 23:50:05 +0530 Subject: [PATCH 22/32] Fixed case-insensitive button functionaliy & Removed merge conflict --- client/styles/components/_keyboard-shortcuts.scss | 2 ++ client/utils/codemirror-search.js | 12 ++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/client/styles/components/_keyboard-shortcuts.scss b/client/styles/components/_keyboard-shortcuts.scss index 82ca7ccbc9..9e223ba6a1 100644 --- a/client/styles/components/_keyboard-shortcuts.scss +++ b/client/styles/components/_keyboard-shortcuts.scss @@ -1,5 +1,7 @@ .keyboard-shortcuts { padding: #{20 / $base-font-size}rem; + margin-right: #{20 / $base-font-size}rem; + padding-bottom: #{40 / $base-font-size}rem; width: #{450 / $base-font-size}rem; overflow-y: scroll; } diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index bc99d53829..1de5b35a25 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -45,13 +45,9 @@ function getSearchState(cm) { return cm.state.search || (cm.state.search = new SearchState()); } -function queryCaseInsensitive(query) { - return typeof query == 'string' && query == query.toLowerCase(); -} - function getSearchCursor(cm, query, pos) { // Heuristic: if the query string is all lowercase, do a case insensitive search. - return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); + return cm.getSearchCursor(query, pos, getSearchState(cm).caseInsensitive); } function isMouseClick(event) { @@ -294,15 +290,15 @@ function parseQuery(query) { function startSearch(cm, state, query) { state.queryText = query; state.query = parseQuery(query); - cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); - state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); + cm.removeOverlay(state.overlay, state.caseInsensitive); + state.overlay = searchOverlay(state.query, state.caseInsensitive); cm.addOverlay(state.overlay); if (cm.showMatchesOnScrollbar) { if (state.annotate) { state.annotate.clear(); state.annotate = null; } - state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); + state.annotate = cm.showMatchesOnScrollbar(state.query, state.caseInsensitive); } } From 41c65dbed8ef36c2d2b5cea51e6ff7423a0cd0f3 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Mon, 2 Nov 2020 20:17:02 +0530 Subject: [PATCH 23/32] Changed parseQuery to the older version --- client/utils/codemirror-search.js | 36 +++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 1de5b35a25..b9d47894a5 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -274,22 +274,37 @@ function parseString(string) { }); } -function parseQuery(query) { - var isRE = query.match(/^\/(.*)\/([a-z]*)$/); - if (isRE) { - try { - query = new RegExp(isRE[1], isRE[2].indexOf('i') == -1 ? '' : 'i'); - } catch (e) { } // Not a regular expression after all, do a string search +function parseQuery(query, state) { + var emptyQuery = 'x^'; // matches nothing + if (query === '') { // empty string matches nothing + query = emptyQuery; } else { - query = parseString(query); + if (state.regexp === false) { + query = parseString(query); + query = query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + if (state.wholeWord) { + query += '\\b'; + } + } + + var regexp; + try { + regexp = new RegExp(query, state.caseInsensitive ? "gi" : "g"); + } catch (e) { + regexp = new RegExp(emptyQuery, 'g'); + } + // If the resulting regexp will match everything, do not use it + if (regexp.test('')) { + return new RegExp(emptyQuery, 'g'); } - if (typeof query == 'string' ? query == '' : query.test('')) query = /x^/; - return query; + return regexp; } function startSearch(cm, state, query) { state.queryText = query; - state.query = parseQuery(query); + state.lastQuery = query; + state.query = parseQuery(query, state); cm.removeOverlay(state.overlay, state.caseInsensitive); state.overlay = searchOverlay(state.query, state.caseInsensitive); cm.addOverlay(state.overlay); @@ -440,7 +455,6 @@ function findNext(cm, rev, callback) { function clearSearch(cm) { cm.operation(function() { var state = getSearchState(cm); - state.lastQuery = state.query; state.replaceStarted = false; if (!state.query) return; state.query = state.queryText = null; From fca364fae706ea8dbd9d419a5960afee8a18abf2 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Mon, 2 Nov 2020 20:36:49 +0530 Subject: [PATCH 24/32] Minor bug-fix --- client/utils/codemirror-search.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index b9d47894a5..c0a58ef173 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -224,8 +224,10 @@ function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { if (state.replaceStarted) { var cursor = getSearchCursor(cm, query, cm.getCursor("from")); var match = cursor.findNext(); - cm.setSelection(cursor.from(), cursor.to()); - doReplace(match, cursor, query, withText); + if (match) { + cm.setSelection(cursor.from(), cursor.to()); + doReplace(match, cursor, query, withText); + } } else { startSearch(cm, state, searchField.value); state.replaceStarted = true; From 376f8f761e395e0b42537d3f042a59a11c8bd67b Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Mon, 2 Nov 2020 17:07:49 -0500 Subject: [PATCH 25/32] [#1519] Updating icons for search with theming --- client/styles/abstracts/_placeholders.scss | 6 ++ client/styles/components/_editor.scss | 45 +---------- client/utils/codemirror-search.js | 25 +++++- package-lock.json | 88 +++++++++++++++------- package.json | 1 + webpack/config.dev.js | 4 + webpack/config.prod.js | 4 + 7 files changed, 101 insertions(+), 72 deletions(-) diff --git a/client/styles/abstracts/_placeholders.scss b/client/styles/abstracts/_placeholders.scss index 1c793d4b77..a6016e122a 100644 --- a/client/styles/abstracts/_placeholders.scss +++ b/client/styles/abstracts/_placeholders.scss @@ -173,9 +173,15 @@ text-decoration: none; color: getThemifyVariable('inactive-text-color'); cursor: pointer; + & g, & path { + fill: getThemifyVariable('inactive-text-color'); + } &:hover { text-decoration: none; color: getThemifyVariable('heavy-text-color'); + & g, & path { + fill: getThemifyVariable('heavy-text-color'); + } } } } diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 7681503141..97f258430d 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -181,6 +181,9 @@ pre.CodeMirror-line { color: getThemifyVariable('input-text-color'); background-color: getThemifyVariable('input-secondary-background-color'); border: solid 0.5px getThemifyVariable('button-border-color'); + &::placeholder { + color: getThemifyVariable('inactive-text-color'); + } } } @@ -256,29 +259,6 @@ pre.CodeMirror-line { margin-right: #{10 / $base-font-size}rem; } -.CodeMirror-search-button::after { - display: block; - content: ' '; - - width: 14px; - height: 14px; - - @include icon(); - - background-repeat: no-repeat; - background-position: center; -} - -// Previous button -.CodeMirror-search-button.prev::after { - background-image: url(../images/up-arrow.svg?byUrl) -} - -// Next button -.CodeMirror-search-button.next::after { - background-image: url(../images/down-arrow.svg?byUrl) -} - .CodeMirror-search-match { background: gold; border-top: 1px solid orange; @@ -295,25 +275,6 @@ pre.CodeMirror-line { align-items: center; } -// Visually hide button text -.CodeMirror-close-button .label { - @extend %hidden-element; -} - -.CodeMirror-close-button:after { - display: block; - content: ' '; - - width: 16px; - height: 16px; - - margin-left: #{8 / $base-font-size}rem; - - @include icon(); - - background: transparent url(../images/exit.svg?byUrl) no-repeat; -} - // foldgutter .CodeMirror-foldmarker { text-shadow: -1px 0 #ed225d, 0 1px #ed225d, 1px 0 #ed225d, 0 -1px #ed225d; diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index c0a58ef173..a56d0f0ea6 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -11,6 +11,11 @@ // Ctrl-G. import i18n from '../i18n'; import CodeMirror from 'codemirror'; +import triangleArrowRight from '../images/triangle-arrow-right.svg?byContent'; +import triangleArrowDown from '../images/triangle-arrow-down.svg?byContent'; +import downArrow from '../images/down-arrow.svg?byContent'; +import upArrow from '../images/up-arrow.svg?byContent'; +import exitIcon from '../images/exit.svg?byContent'; function searchOverlay(query, caseInsensitive) { if (typeof query == 'string') query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), caseInsensitive ? 'gi' : 'g'); @@ -156,12 +161,12 @@ function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { replaceDiv.style.height = replaceDivHeightOpened; toggleReplaceBtnDiv.style.height = toggleButtonHeightOpened; showReplaceButton.style.height = toggleButtonHeightOpened; - showReplaceButton.innerHTML = "▼"; + showReplaceButton.innerHTML = triangleArrowDown; } else { replaceDiv.style.height = replaceDivHeightClosed; toggleReplaceBtnDiv.style.height = toggleButtonHeightClosed; showReplaceButton.style.height = toggleButtonHeightClosed; - showReplaceButton.innerHTML = "►"; + showReplaceButton.innerHTML = triangleArrowRight; } } @@ -493,7 +498,9 @@ var getQueryDialog = function() { role="button" class="CodeMirror-search-modifier-button CodeMirror-replace-toggle-button" > - +
    @@ -535,19 +542,29 @@ var getQueryDialog = function() { aria-label="${i18n.t('CodemirrorFindAndReplace.Previous')}" class="CodeMirror-search-button icon up-arrow prev" > +
    diff --git a/package-lock.json b/package-lock.json index ee1926df2e..1a556dd479 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6768,6 +6768,16 @@ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, + "raw-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.1.0.tgz", + "integrity": "sha512-lzUVMuJ06HF4rYveaz9Tv0WRlUMxJ0Y1hgSkkgg+50iEdaI0TthyEDe08KIHb0XsF6rn8WYTqPCaGTZg3sX+qA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^2.0.1" + } + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -11020,7 +11030,8 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "" + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -31889,20 +31900,23 @@ } }, "raw-loader": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.1.0.tgz", - "integrity": "sha512-lzUVMuJ06HF4rYveaz9Tv0WRlUMxJ0Y1hgSkkgg+50iEdaI0TthyEDe08KIHb0XsF6rn8WYTqPCaGTZg3sX+qA==", - "dev": true, + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^2.0.1" + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" }, "dependencies": { + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + }, "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "dev": true, + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -31911,25 +31925,46 @@ } }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } }, "schema-utils": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", - "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "requires": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } @@ -35204,7 +35239,8 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "" + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, diff --git a/package.json b/package.json index 4310384572..6678788d64 100644 --- a/package.json +++ b/package.json @@ -196,6 +196,7 @@ "prop-types": "^15.6.2", "q": "^1.4.1", "query-string": "^6.13.2", + "raw-loader": "^4.0.2", "react": "^16.12.0", "react-dom": "^16.12.0", "react-helmet": "^5.1.3", diff --git a/webpack/config.dev.js b/webpack/config.dev.js index 82a3e7414f..e8c57568c6 100644 --- a/webpack/config.dev.js +++ b/webpack/config.dev.js @@ -96,6 +96,10 @@ module.exports = { { test: /\.svg$/, oneOf: [ + { + resourceQuery: /byContent/, + use: 'raw-loader' + }, { resourceQuery: /byUrl/, use: 'file-loader' diff --git a/webpack/config.prod.js b/webpack/config.prod.js index 6b18f4ab3f..223a7f0c70 100644 --- a/webpack/config.prod.js +++ b/webpack/config.prod.js @@ -102,6 +102,10 @@ module.exports = [{ { test: /\.svg$/, oneOf: [ + { + resourceQuery: /byContent/, + use: 'raw-loader' + }, { resourceQuery: /byUrl/, use: 'file-loader' From 379973ea437b0938b52c8b4d5b59d6179f4ddcc5 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Wed, 4 Nov 2020 00:43:18 +0530 Subject: [PATCH 26/32] Minor bug fix in No result text --- client/utils/codemirror-search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index a56d0f0ea6..013d06a394 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -84,7 +84,7 @@ function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { CodeMirror.on(searchField, "keyup", function (e) { if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search startSearch(cm, getSearchState(cm), searchField.value); - } else if (searchField.value.length <= 1) { + } else if (searchField.value.length < 1) { cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = i18n.t('CodemirrorFindAndReplace.NoResults'); } }); From 8e8dcf9f699d232712049ee800d5d42accbfbb77 Mon Sep 17 00:00:00 2001 From: Sundeep Chand Date: Wed, 4 Nov 2020 00:58:34 +0530 Subject: [PATCH 27/32] Minor styling changes in high-contrast mode --- client/styles/abstracts/_variables.scss | 2 ++ client/styles/components/_p5-contrast-codemirror-theme.scss | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index 0c6e0ab56b..5d3323dd6c 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -3,8 +3,10 @@ $base-font-size: 12; //colors $p5js-pink: #ed225d; $p5js-pink-opacity: #ed225d66; +$pink-contrast-opacity: #ed225ba4; $processing-blue: #007BBB; $processing-blue-opacity: #007BBB66; +$orange-contrast-opacity: #ffee009a; $p5js-active-pink: #f10046; $white: #fff; $black: #000; diff --git a/client/styles/components/_p5-contrast-codemirror-theme.scss b/client/styles/components/_p5-contrast-codemirror-theme.scss index aba0723666..36bdb4a270 100644 --- a/client/styles/components/_p5-contrast-codemirror-theme.scss +++ b/client/styles/components/_p5-contrast-codemirror-theme.scss @@ -137,9 +137,9 @@ $p5-contrast-activeline: #999999; } .cm-s-p5-contrast .cm-searching { - background-color: $processing-blue-opacity; + background-color: $pink-contrast-opacity; } .cm-s-p5-contrast .CodeMirror-selectedtext { - background-color: $medium-dark; + background-color: $orange-contrast-opacity; } From 10ee0aff2ea53e6c50257876d10cf89e59642534 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 5 Nov 2020 17:53:05 -0500 Subject: [PATCH 28/32] [#1519] Update selection highlight colors --- client/styles/abstracts/_variables.scss | 5 +---- .../styles/components/_p5-contrast-codemirror-theme.scss | 8 +++++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index 5d3323dd6c..514c2c0146 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -2,11 +2,8 @@ $base-font-size: 12; //colors $p5js-pink: #ed225d; -$p5js-pink-opacity: #ed225d66; -$pink-contrast-opacity: #ed225ba4; +$p5js-pink-opacity: #ed225d80; $processing-blue: #007BBB; -$processing-blue-opacity: #007BBB66; -$orange-contrast-opacity: #ffee009a; $p5js-active-pink: #f10046; $white: #fff; $black: #000; diff --git a/client/styles/components/_p5-contrast-codemirror-theme.scss b/client/styles/components/_p5-contrast-codemirror-theme.scss index 36bdb4a270..b5ae00d991 100644 --- a/client/styles/components/_p5-contrast-codemirror-theme.scss +++ b/client/styles/components/_p5-contrast-codemirror-theme.scss @@ -137,9 +137,11 @@ $p5-contrast-activeline: #999999; } .cm-s-p5-contrast .cm-searching { - background-color: $pink-contrast-opacity; + // background-color: $p5js-pink-opacity; + background-color: $medium-dark; } -.cm-s-p5-contrast .CodeMirror-selectedtext { - background-color: $orange-contrast-opacity; +.cm-s-p5-contrast .cm-searching.CodeMirror-selectedtext { + // background-color: $medium-dark; + outline: #{1 / $base-font-size}rem solid $p5-contrast-white; } From 1756620c3d4505abc4c9645ce1ee09d4b3eba855 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 5 Nov 2020 18:31:10 -0500 Subject: [PATCH 29/32] [#1519] Update replace keyboard shortcut to match VSC --- client/modules/IDE/components/Editor.jsx | 3 ++- client/modules/IDE/components/KeyboardShortcutModal.jsx | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index 14185889ea..e4c6ab1d80 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -123,6 +123,7 @@ class Editor extends React.Component { delete this._cm.options.lint.options.errors; + const replaceCommand = metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`; this._cm.setOption('extraKeys', { Tab: (cm) => { // might need to specify and indent more? @@ -138,7 +139,7 @@ class Editor extends React.Component { [`${metaKey}-F`]: 'findPersistent', [`${metaKey}-G`]: 'findNext', [`Shift-${metaKey}-G`]: 'findPrev', - [`${metaKey}-R`]: 'replace', + replaceCommand: 'replace', }); this.initializeDocuments(this.props.files); diff --git a/client/modules/IDE/components/KeyboardShortcutModal.jsx b/client/modules/IDE/components/KeyboardShortcutModal.jsx index a99f1d1377..8977e4290f 100644 --- a/client/modules/IDE/components/KeyboardShortcutModal.jsx +++ b/client/modules/IDE/components/KeyboardShortcutModal.jsx @@ -1,9 +1,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { metaKeyName, } from '../../../utils/metaKey'; +import { metaKeyName, metaKey } from '../../../utils/metaKey'; function KeyboardShortcutModal() { const { t } = useTranslation(); + const replaceCommand = metaKey === 'Ctrl' ? `${metaKeyName} + H` : `${metaKeyName} + ⌥ + F`; return (

    {t('KeyboardShortcuts.CodeEditing.CodeEditing')}

    @@ -35,7 +36,7 @@ function KeyboardShortcutModal() {
  • - {metaKeyName} + R + {replaceCommand} {t('KeyboardShortcuts.CodeEditing.ReplaceTextMatch')}
  • From 900028cd6ebadc280f370cbd3febaf5c72d81e4e Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 5 Nov 2020 18:46:10 -0500 Subject: [PATCH 30/32] [#1519] Update replace keyboard shortcut in Nav --- client/components/Nav.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index 323e93001c..3fb367de38 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -13,7 +13,7 @@ import { setAllAccessibleOutput, setLanguage } from '../modules/IDE/actions/pref import { logoutUser } from '../modules/User/actions'; import getConfig from '../utils/getConfig'; -import { metaKeyName, } from '../utils/metaKey'; +import { metaKeyName, metaKey } from '../utils/metaKey'; import { getIsUserOwner } from '../modules/IDE/selectors/users'; import CaretLeftIcon from '../images/left-arrow.svg'; @@ -257,6 +257,7 @@ class Nav extends React.PureComponent { } renderProjectMenu(navDropdownState) { + const replaceCommand = metaKey === 'Ctrl' ? `${metaKeyName}+H` : `${metaKeyName}+⌥+F`; return (
    • @@ -429,7 +430,7 @@ class Nav extends React.PureComponent { onBlur={this.handleBlur} > {this.props.t('Nav.Edit.Replace')} - {metaKeyName}+R + {replaceCommand}
    From 0761e6db956c68810ca6578bb558d7fdba6df2e5 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Thu, 5 Nov 2020 18:48:18 -0500 Subject: [PATCH 31/32] [#1519] Update Nav snapshot --- client/components/__test__/__snapshots__/Nav.test.jsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/__test__/__snapshots__/Nav.test.jsx.snap b/client/components/__test__/__snapshots__/Nav.test.jsx.snap index fa0c11f10f..97f004d137 100644 --- a/client/components/__test__/__snapshots__/Nav.test.jsx.snap +++ b/client/components/__test__/__snapshots__/Nav.test.jsx.snap @@ -129,7 +129,7 @@ exports[`Nav renders correctly 1`] = ` - ⌃+R + ⌃+H From f0783d61f46fdd240107c7cf795715d58184d80b Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Fri, 6 Nov 2020 12:21:38 -0500 Subject: [PATCH 32/32] [#1519] Update pixel measurements to rems --- client/styles/components/_editor.scss | 20 ++++++++++---------- client/styles/components/_overlay.scss | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 97f258430d..a7d26f5a6a 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -112,7 +112,7 @@ pre.CodeMirror-line { } .Toggle-replace-btn-div { - height: 40px; + height: #{40 / $base-font-size}rem; padding: 0; } @@ -122,7 +122,7 @@ pre.CodeMirror-line { } .CodeMirror-find-div { - padding: 0px; + padding: 0; display: flex; justify-content: flex-start; align-items: center; @@ -130,12 +130,12 @@ pre.CodeMirror-line { } .CodeMirror-search-modifiers { - margin-left: 10px; + margin-left: #{10 / $base-font-size}rem; } .CodeMirror-search-results { - margin: 0px 20px; - width: 75px; + margin: 0 #{20 / $base-font-size}rem; + width: #{75 / $base-font-size}rem; font-size: #{12/$base-font-size}rem; } @@ -154,14 +154,14 @@ pre.CodeMirror-line { } .CodeMirror-replace-options { - width: 552px; - height: 65px; + width: #{552 / $base-font-size}rem; + height: #{65 / $base-font-size}rem; display: flex; justify-content: center; align-items: center; } .CodeMirror-replace-options button { - width: 200px; + width: #{200 / $base-font-size}rem; } .CodeMirror-search-title { @@ -261,8 +261,8 @@ pre.CodeMirror-line { .CodeMirror-search-match { background: gold; - border-top: 1px solid orange; - border-bottom: 1px solid orange; + border-top: #{1 / $base-font-size}rem solid orange; + border-bottom: #{1 / $base-font-size}rem solid orange; box-sizing: border-box; opacity: .5; } diff --git a/client/styles/components/_overlay.scss b/client/styles/components/_overlay.scss index 61cf0219c9..b33797f60d 100644 --- a/client/styles/components/_overlay.scss +++ b/client/styles/components/_overlay.scss @@ -25,7 +25,7 @@ max-height: 80%; max-width: 65%; position: relative; - padding-bottom: 25px; + padding-bottom: #{25 / $base-font-size}rem; } .overlay__header {