summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/sourceeditor/README
blob: e4a88f6d4eb7c4606d67e4f0df40387885a1aa5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
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.

# CodeMirror 6

We're currently migrating to CodeMirror 6, which means we have bundle for version 6 _and_ 5,
until we successfully migrated all consumers to CodeMirror 6.

For version 6, we're generating a bundle (codemirror6/codemirror6.bundle.js) using rollup.
The entry point for the bundle is codemirror6/index.mjs, where we export all the classes
and functions that the editor needs. When adding new exported item, the bundle needs to
be updated, which can be done by running:
 > cd devtools/client/shared/sourceeditor
 > npm install
 > npm run build-cm6

This will produced a minified bundle, which might not be ideal if you're debugging an issue or profiling.
You can get an unminified bundle by running:
> npm run build-cm6-unminified

The generated bundle can be configurated in rollup.config.mjs.

# CodeMirror 5

## CodeMirror 5 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 5 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