1This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
2is a JavaScript component that provides a code editor in the browser. When
3a mode is available for the language you are coding in, it will color your
4code, and optionally help with indentation.
5
6# Upgrade
7
8Currently used version is 5.58.1. To upgrade: download a new version of
9CodeMirror from the project's page [1] and replace all JavaScript and
10CSS files inside the codemirror directory [2].
11
12Then to recreate codemirror.bundle.js:
13 > cd devtools/client/shared/sourceeditor
14 > npm install
15 > npm run build
16
17When investigating an issue in CodeMirror, you might want to have a non-minified bundle.
18You can do this by running `npm run build-unminified` instead of `npm run build`.
19
20To confirm the functionality run mochitests for the following components:
21
22 * sourceeditor
23 * debugger
24 * styleditor
25 * netmonitor
26 * webconsole
27
28The sourceeditor component contains imported CodeMirror tests [3].
29
30 * Some tests were commented out because we don't use that functionality
31 within Firefox (for example Ruby editing mode). Be careful when updating
32 files test/codemirror.html and test/vimemacs.html; they were modified to
33 co-exist with Mozilla's testing infrastructure. Basically, vimemacs.html
34 is a copy of codemirror.html but only with VIM and Emacs mode tests
35 enabled.
36 * In cm_comment_test.js comment out fallbackToBlock and fallbackToLine
37 tests.
38 * The search addon (search.js) was slightly modified to make search
39 UI localizable (see patch below).
40
41Other than that, we don't have any Mozilla-specific patches applied to
42CodeMirror itself.
43
44# Addons
45
46To install a new CodeMirror addon add it to the codemirror directory,
47jar.mn [4] file and editor.js [5]. Also, add it to the License section
48below.
49
50# License
51
52The following files in this directory and devtools/client/shared/sourceeditor/test/codemirror/
53are licensed according to the contents in the LICENSE file.
54
55# Localization patches
56
57diff --git a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
58--- a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
59+++ b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
60@@ -93,32 +93,47 @@
61 } else {
62 query = parseString(query)
63 }
64 if (typeof query == "string" ? query == "" : query.test(""))
65 query = /x^/;
66 return query;
67 }
68
69- var queryDialog =
70- '<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>';
71+ var queryDialog;
72
73 function startSearch(cm, state, query) {
74 state.queryText = query;
75 state.query = parseQuery(query);
76 cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
77 state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
78 cm.addOverlay(state.overlay);
79 if (cm.showMatchesOnScrollbar) {
80 if (state.annotate) { state.annotate.clear(); state.annotate = null; }
81 state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
82 }
83 }
84
85 function doSearch(cm, rev, persistent, immediate) {
86+ if (!queryDialog) {
87+ let doc = cm.getWrapperElement().ownerDocument;
88+ let inp = doc.createElement("input");
89+
90+ inp.type = "search";
91+ inp.placeholder = cm.l10n("findCmd.promptMessage");
92+ inp.style.marginInlineStart = "1em";
93+ inp.style.marginInlineEnd = "1em";
94+ inp.style.flexGrow = "1";
95+ inp.addEventListener("focus", () => inp.select());
96+
97+ queryDialog = doc.createElement("div");
98+ queryDialog.appendChild(inp);
99+ queryDialog.style.display = "flex";
100+ }
101+
102 var state = getSearchState(cm);
103 if (state.query) return findNext(cm, rev);
104 var q = cm.getSelection() || state.lastQuery;
105 if (q instanceof RegExp && q.source == "x^") q = null
106 if (persistent && cm.openDialog) {
107 var hiding = null
108 var searchNext = function(query, event) {
109 CodeMirror.e_stop(event);
110@@ -181,56 +196,110 @@
111 var state = getSearchState(cm);
112 state.lastQuery = state.query;
113 if (!state.query) return;
114 state.query = state.queryText = null;
115 cm.removeOverlay(state.overlay);
116 if (state.annotate) { state.annotate.clear(); state.annotate = null; }
117 });}
118
119- var replaceQueryDialog =
120- ' <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>';
121- var replacementQueryDialog = '<span class="CodeMirror-search-label">With:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
122- var doReplaceConfirm = '<span class="CodeMirror-search-label">Replace?</span> <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>';
123-
124 function replaceAll(cm, query, text) {
125 cm.operation(function() {
126 for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
127 if (typeof query != "string") {
128 var match = cm.getRange(cursor.from(), cursor.to()).match(query);
129 cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
130 } else cursor.replace(text);
131 }
132 });
133 }
134
135 function replace(cm, all) {
136 if (cm.getOption("readOnly")) return;
137 var query = cm.getSelection() || getSearchState(cm).lastQuery;
138- var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Replace all:' : 'Replace:') + '</span>';
139- dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
140+
141+ let doc = cm.getWrapperElement().ownerDocument;
142+
143+ // `searchLabel` is used as part of `replaceQueryFragment` and as a separate
144+ // argument by itself, so it should be cloned.
145+ let searchLabel = doc.createElement("span");
146+ searchLabel.classList.add("CodeMirror-search-label");
147+ searchLabel.textContent = all ? "Replace all:" : "Replace:";
148+
149+ let replaceQueryFragment = doc.createDocumentFragment();
150+ replaceQueryFragment.appendChild(searchLabel.cloneNode(true));
151+
152+ let searchField = doc.createElement("input");
153+ searchField.setAttribute("type", "text");
154+ searchField.setAttribute("style", "width: 10em");
155+ searchField.classList.add("CodeMirror-search-field");
156+ replaceQueryFragment.appendChild(searchField);
157+
158+ let searchHint = doc.createElement("span");
159+ searchHint.setAttribute("style", "color: #888");
160+ searchHint.classList.add("CodeMirror-search-hint");
161+ searchHint.textContent = "(Use /re/ syntax for regexp search)";
162+ replaceQueryFragment.appendChild(searchHint);
163+
164+ dialog(cm, replaceQueryFragment, searchLabel, query, function(query) {
165 if (!query) return;
166 query = parseQuery(query);
167- dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
168+
169+ let replacementQueryFragment = doc.createDocumentFragment();
170+
171+ let replaceWithLabel = searchLabel.cloneNode(false);
172+ replaceWithLabel.textContent = "With:";
173+ replacementQueryFragment.appendChild(replaceWithLabel);
174+
175+ let replaceField = doc.createElement("input");
176+ replaceField.setAttribute("type", "text");
177+ replaceField.setAttribute("style", "width: 10em");
178+ replaceField.classList.add("CodeMirror-search-field");
179+ replacementQueryFragment.appendChild(replaceField);
180+
181+ dialog(cm, replacementQueryFragment, "Replace with:", "", function(text) {
182 text = parseString(text)
183 if (all) {
184 replaceAll(cm, query, text)
185 } else {
186 clearSearch(cm);
187 var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
188 var advance = function() {
189 var start = cursor.from(), match;
190 if (!(match = cursor.findNext())) {
191 cursor = getSearchCursor(cm, query);
192 if (!(match = cursor.findNext()) ||
193 (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
194 }
195 cm.setSelection(cursor.from(), cursor.to());
196- cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
197- confirmDialog(cm, doReplaceConfirm, "Replace?",
198+ cm.scrollIntoView({ from: cursor.from(), to: cursor.to() });
199+
200+ let replaceConfirmFragment = doc.createDocumentFragment();
201+
202+ let replaceConfirmLabel = searchLabel.cloneNode(false);
203+ replaceConfirmLabel.textContent = "Replace?";
204+ replaceConfirmFragment.appendChild(replaceConfirmLabel);
205+
206+ let yesButton = doc.createElement("button");
207+ yesButton.textContent = "Yes";
208+ replaceConfirmFragment.appendChild(yesButton);
209+
210+ let noButton = doc.createElement("button");
211+ noButton.textContent = "No";
212+ replaceConfirmFragment.appendChild(noButton);
213+
214+ let allButton = doc.createElement("button");
215+ allButton.textContent = "All";
216+ replaceConfirmFragment.appendChild(allButton);
217+
218+ let stopButton = doc.createElement("button");
219+ stopButton.textContent = "Stop";
220+ replaceConfirmFragment.appendChild(stopButton);
221+
222+ confirmDialog(cm, replaceConfirmFragment, "Replace?",
223 [function() {doReplace(match);}, advance,
224 function() {replaceAll(cm, query, text)}]);
225 };
226 var doReplace = function(match) {
227 cursor.replace(typeof query == "string" ? text :
228 text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
229 advance();
230 };
231
232# Footnotes
233
234[1] http://codemirror.net
235[2] devtools/client/shared/sourceeditor/codemirror
236[3] devtools/client/shared/sourceeditor/test/browser_codemirror.js
237[4] devtools/client/jar.mn
238[5] devtools/client/shared/sourceeditor/editor.js
239