summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/sourceeditor/README
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/sourceeditor/README')
-rw-r--r--devtools/client/shared/sourceeditor/README238
1 files changed, 238 insertions, 0 deletions
diff --git a/devtools/client/shared/sourceeditor/README b/devtools/client/shared/sourceeditor/README
new file mode 100644
index 0000000000..651c2057cd
--- /dev/null
+++ b/devtools/client/shared/sourceeditor/README
@@ -0,0 +1,238 @@
+This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
+is a JavaScript component that provides a code editor in the browser. When
+a mode is available for the language you are coding in, it will color your
+code, and optionally help with indentation.
+
+# Upgrade
+
+Currently used version is 5.58.1. To upgrade: download a new version of
+CodeMirror from the project's page [1] and replace all JavaScript and
+CSS files inside the codemirror directory [2].
+
+Then to recreate codemirror.bundle.js:
+ > cd devtools/client/shared/sourceeditor
+ > npm install
+ > npm run build
+
+When investigating an issue in CodeMirror, you might want to have a non-minified bundle.
+You can do this by running `npm run build-unminified` instead of `npm run build`.
+
+To confirm the functionality run mochitests for the following components:
+
+ * sourceeditor
+ * debugger
+ * styleditor
+ * netmonitor
+ * webconsole
+
+The sourceeditor component contains imported CodeMirror tests [3].
+
+ * Some tests were commented out because we don't use that functionality
+ within Firefox (for example Ruby editing mode). Be careful when updating
+ files test/codemirror.html and test/vimemacs.html; they were modified to
+ co-exist with Mozilla's testing infrastructure. Basically, vimemacs.html
+ is a copy of codemirror.html but only with VIM and Emacs mode tests
+ enabled.
+ * In cm_comment_test.js comment out fallbackToBlock and fallbackToLine
+ tests.
+ * The search addon (search.js) was slightly modified to make search
+ UI localizable (see patch below).
+
+Other than that, we don't have any Mozilla-specific patches applied to
+CodeMirror itself.
+
+# Addons
+
+To install a new CodeMirror addon add it to the codemirror directory,
+jar.mn [4] file and editor.js [5]. Also, add it to the License section
+below.
+
+# License
+
+The following files in this directory and devtools/client/shared/sourceeditor/test/codemirror/
+are licensed according to the contents in the LICENSE file.
+
+# Localization patches
+
+diff --git a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
+--- a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
++++ b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
+@@ -93,32 +93,47 @@
+ } else {
+ query = parseString(query)
+ }
+ if (typeof query == "string" ? query == "" : query.test(""))
+ query = /x^/;
+ return query;
+ }
+
+- var queryDialog =
+- '<span class="CodeMirror-search-label">Search:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
++ var queryDialog;
+
+ 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 doSearch(cm, rev, persistent, immediate) {
++ if (!queryDialog) {
++ let doc = cm.getWrapperElement().ownerDocument;
++ let inp = doc.createElement("input");
++
++ inp.type = "search";
++ inp.placeholder = cm.l10n("findCmd.promptMessage");
++ inp.style.marginInlineStart = "1em";
++ inp.style.marginInlineEnd = "1em";
++ inp.style.flexGrow = "1";
++ inp.addEventListener("focus", () => inp.select());
++
++ queryDialog = doc.createElement("div");
++ queryDialog.appendChild(inp);
++ queryDialog.style.display = "flex";
++ }
++
+ var state = getSearchState(cm);
+ if (state.query) return findNext(cm, rev);
+ var q = cm.getSelection() || state.lastQuery;
+ if (q instanceof RegExp && q.source == "x^") q = null
+ if (persistent && cm.openDialog) {
+ var hiding = null
+ var searchNext = function(query, event) {
+ CodeMirror.e_stop(event);
+@@ -181,56 +196,110 @@
+ var state = getSearchState(cm);
+ state.lastQuery = state.query;
+ if (!state.query) return;
+ state.query = state.queryText = null;
+ cm.removeOverlay(state.overlay);
+ if (state.annotate) { state.annotate.clear(); state.annotate = null; }
+ });}
+
+- var replaceQueryDialog =
+- ' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
+- var replacementQueryDialog = '<span class="CodeMirror-search-label">With:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
+- var doReplaceConfirm = '<span class="CodeMirror-search-label">Replace?</span> <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>';
+-
+ 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);
+ }
+ });
+ }
+
+ function replace(cm, all) {
+ if (cm.getOption("readOnly")) return;
+ var query = cm.getSelection() || getSearchState(cm).lastQuery;
+- var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Replace all:' : 'Replace:') + '</span>';
+- dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
++
++ let doc = cm.getWrapperElement().ownerDocument;
++
++ // `searchLabel` is used as part of `replaceQueryFragment` and as a separate
++ // argument by itself, so it should be cloned.
++ let searchLabel = doc.createElement("span");
++ searchLabel.classList.add("CodeMirror-search-label");
++ searchLabel.textContent = all ? "Replace all:" : "Replace:";
++
++ let replaceQueryFragment = doc.createDocumentFragment();
++ replaceQueryFragment.appendChild(searchLabel.cloneNode(true));
++
++ let searchField = doc.createElement("input");
++ searchField.setAttribute("type", "text");
++ searchField.setAttribute("style", "width: 10em");
++ searchField.classList.add("CodeMirror-search-field");
++ replaceQueryFragment.appendChild(searchField);
++
++ let searchHint = doc.createElement("span");
++ searchHint.setAttribute("style", "color: #888");
++ searchHint.classList.add("CodeMirror-search-hint");
++ searchHint.textContent = "(Use /re/ syntax for regexp search)";
++ replaceQueryFragment.appendChild(searchHint);
++
++ dialog(cm, replaceQueryFragment, searchLabel, query, function(query) {
+ if (!query) return;
+ query = parseQuery(query);
+- dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
++
++ let replacementQueryFragment = doc.createDocumentFragment();
++
++ let replaceWithLabel = searchLabel.cloneNode(false);
++ replaceWithLabel.textContent = "With:";
++ replacementQueryFragment.appendChild(replaceWithLabel);
++
++ let replaceField = doc.createElement("input");
++ replaceField.setAttribute("type", "text");
++ replaceField.setAttribute("style", "width: 10em");
++ replaceField.classList.add("CodeMirror-search-field");
++ replacementQueryFragment.appendChild(replaceField);
++
++ dialog(cm, replacementQueryFragment, "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()});
+- confirmDialog(cm, doReplaceConfirm, "Replace?",
++ cm.scrollIntoView({ from: cursor.from(), to: cursor.to() });
++
++ let replaceConfirmFragment = doc.createDocumentFragment();
++
++ let replaceConfirmLabel = searchLabel.cloneNode(false);
++ replaceConfirmLabel.textContent = "Replace?";
++ replaceConfirmFragment.appendChild(replaceConfirmLabel);
++
++ let yesButton = doc.createElement("button");
++ yesButton.textContent = "Yes";
++ replaceConfirmFragment.appendChild(yesButton);
++
++ let noButton = doc.createElement("button");
++ noButton.textContent = "No";
++ replaceConfirmFragment.appendChild(noButton);
++
++ let allButton = doc.createElement("button");
++ allButton.textContent = "All";
++ replaceConfirmFragment.appendChild(allButton);
++
++ let stopButton = doc.createElement("button");
++ stopButton.textContent = "Stop";
++ replaceConfirmFragment.appendChild(stopButton);
++
++ confirmDialog(cm, replaceConfirmFragment, "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();
+ };
+
+# Footnotes
+
+[1] http://codemirror.net
+[2] devtools/client/shared/sourceeditor/codemirror
+[3] devtools/client/shared/sourceeditor/test/browser_codemirror.js
+[4] devtools/client/jar.mn
+[5] devtools/client/shared/sourceeditor/editor.js