tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

README (13416B)


      1 This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
      2 is a JavaScript component that provides a code editor in the browser. When
      3 a mode is available for the language you are coding in, it will color your
      4 code, and optionally help with indentation.
      5 
      6 # CodeMirror 6
      7 
      8 We're currently migrating to CodeMirror 6, which means we have bundle for version 6 _and_ 5,
      9 until we successfully migrated all consumers to CodeMirror 6.
     10 
     11 For version 6, we're generating a bundle (codemirror6/codemirror6.bundle.mjs) using rollup.
     12 The entry point for the bundle is codemirror6/index.mjs, where we export all the classes
     13 and functions that the editor needs. When adding new exported item, the bundle needs to
     14 be updated, which can be done by running:
     15  > cd devtools/client/shared/sourceeditor
     16  > npm install
     17  > npm run build-cm6
     18 
     19 This will produced a minified bundle, which might not be ideal if you're debugging an issue or profiling.
     20 You can get an unminified bundle by running:
     21 > npm run build-cm6-unminified
     22 
     23 The generated bundle can be configurated in rollup.config.mjs.
     24 
     25 # CodeMirror 5
     26 
     27 ## CodeMirror 5 Upgrade
     28 
     29 Currently used version is 5.58.1. To upgrade: download a new version of
     30 CodeMirror from the project's page [1] and replace all JavaScript and
     31 CSS files inside the codemirror directory [2].
     32 
     33 Then to recreate codemirror.bundle.js:
     34  > cd devtools/client/shared/sourceeditor
     35  > npm install
     36  > npm run build
     37 
     38 When investigating an issue in CodeMirror, you might want to have a non-minified bundle.
     39 You can do this by running `npm run build-unminified` instead of `npm run build`.
     40 
     41 To confirm the functionality run mochitests for the following components:
     42 
     43  * sourceeditor
     44  * debugger
     45  * styleditor
     46  * netmonitor
     47  * webconsole
     48 
     49 The sourceeditor component contains imported CodeMirror tests [3].
     50 
     51  * Some tests were commented out because we don't use that functionality
     52    within Firefox (for example Ruby editing mode). Be careful when updating
     53    files test/codemirror.html and test/vimemacs.html; they were modified to
     54    co-exist with Mozilla's testing infrastructure. Basically, vimemacs.html
     55    is a copy of codemirror.html but only with VIM and Emacs mode tests
     56    enabled.
     57  * In cm_comment_test.js comment out fallbackToBlock and fallbackToLine
     58    tests.
     59  * The search addon (search.js) was slightly modified to make search
     60    UI localizable (see patch below).
     61 
     62 Other than that, we don't have any Mozilla-specific patches applied to
     63 CodeMirror itself.
     64 
     65 ## Addons
     66 
     67 To install a new CodeMirror 5 addon add it to the codemirror directory,
     68 jar.mn [4] file and editor.js [5]. Also, add it to the License section
     69 below.
     70 
     71 ## License
     72 
     73 The following files in this directory and devtools/client/shared/sourceeditor/test/codemirror/
     74 are licensed according to the contents in the LICENSE file.
     75 
     76 ## Localization patches
     77 
     78 diff --git a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
     79 --- a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
     80 +++ b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
     81 @@ -93,32 +93,47 @@
     82      } else {
     83        query = parseString(query)
     84      }
     85      if (typeof query == "string" ? query == "" : query.test(""))
     86        query = /x^/;
     87      return query;
     88    }
     89 
     90 -  var queryDialog =
     91 -    '<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>';
     92 
     93    function startSearch(cm, state, query) {
     94      state.queryText = query;
     95      state.query = parseQuery(query);
     96      cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
     97      state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
     98      cm.addOverlay(state.overlay);
     99      if (cm.showMatchesOnScrollbar) {
    100        if (state.annotate) { state.annotate.clear(); state.annotate = null; }
    101        state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
    102      }
    103    }
    104 
    105    function doSearch(cm, rev, persistent, immediate) {
    106 +    // We used to only build this input the first time the search was triggered and
    107 +    // reuse it again on subsequent search.
    108 +    // Unfortunately, this doesn't play well with the `persistent` parameter;
    109 +    // new event listeners are added to the input each time `persistentDialog` is called,
    110 +    // which would make a single `Enter` key trigger multiple "findNext" actions, making
    111 +    // it look like the search would skip some results.
    112 +    const doc = cm.getWrapperElement().ownerDocument;
    113 +    const inp = doc.createElement("input");
    114 +
    115 +    inp.type = "search";
    116 +    inp.classList.add("cm5-search-input");
    117 +    inp.placeholder = cm.l10n("findCmd.promptMessage");
    118 +    inp.addEventListener("focus", () => inp.select());
    119 +
    120 +    const queryDialog = doc.createElement("div");
    121 +    queryDialog.classList.add("cm5-search-container");
    122 +    queryDialog.appendChild(inp);
    123 
    124      var state = getSearchState(cm);
    125      if (state.query) return findNext(cm, rev);
    126      var q = cm.getSelection() || state.lastQuery;
    127      if (q instanceof RegExp && q.source == "x^") q = null
    128      if (persistent && cm.openDialog) {
    129        var hiding = null
    130        var searchNext = function(query, event) {
    131          CodeMirror.e_stop(event);
    132 @@ -181,56 +196,110 @@
    133      var state = getSearchState(cm);
    134      state.lastQuery = state.query;
    135      if (!state.query) return;
    136      state.query = state.queryText = null;
    137      cm.removeOverlay(state.overlay);
    138      if (state.annotate) { state.annotate.clear(); state.annotate = null; }
    139    });}
    140 
    141 -  var replaceQueryDialog =
    142 -    ' <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>';
    143 -  var replacementQueryDialog = '<span class="CodeMirror-search-label">With:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
    144 -  var doReplaceConfirm = '<span class="CodeMirror-search-label">Replace?</span> <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>';
    145 -
    146    function replaceAll(cm, query, text) {
    147      cm.operation(function() {
    148        for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
    149          if (typeof query != "string") {
    150            var match = cm.getRange(cursor.from(), cursor.to()).match(query);
    151            cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
    152          } else cursor.replace(text);
    153        }
    154      });
    155    }
    156 
    157    function replace(cm, all) {
    158      if (cm.getOption("readOnly")) return;
    159      var query = cm.getSelection() || getSearchState(cm).lastQuery;
    160 -    var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Replace all:' : 'Replace:') + '</span>';
    161 -    dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
    162 +
    163 +    let doc = cm.getWrapperElement().ownerDocument;
    164 +
    165 +    // `searchLabel` is used as part of `replaceQueryFragment` and as a separate
    166 +    // argument by itself, so it should be cloned.
    167 +    let searchLabel = doc.createElement("span");
    168 +    searchLabel.classList.add("CodeMirror-search-label");
    169 +    searchLabel.textContent = all ? "Replace all:" : "Replace:";
    170 +
    171 +    let replaceQueryFragment = doc.createDocumentFragment();
    172 +    replaceQueryFragment.appendChild(searchLabel.cloneNode(true));
    173 +
    174 +    let searchField = doc.createElement("input");
    175 +    searchField.setAttribute("type", "text");
    176 +    searchField.classList.add("cm5-search-replace-input");
    177 +    replaceQueryFragment.appendChild(searchField);
    178 +
    179 +    let searchHint = doc.createElement("span");
    180 +    searchHint.classList.add("cm5-search-replace-hint");
    181 +    searchHint.textContent = "(Use /re/ syntax for regexp search)";
    182 +    replaceQueryFragment.appendChild(searchHint);
    183 +
    184 +    dialog(cm, replaceQueryFragment, searchLabel, query, function(query) {
    185        if (!query) return;
    186        query = parseQuery(query);
    187 -      dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
    188 +
    189 +      let replacementQueryFragment = doc.createDocumentFragment();
    190 +
    191 +      let replaceWithLabel = searchLabel.cloneNode(false);
    192 +      replaceWithLabel.textContent = "With:";
    193 +      replacementQueryFragment.appendChild(replaceWithLabel);
    194 +
    195 +      let replaceField = doc.createElement("input");
    196 +      replaceField.setAttribute("type", "text");
    197 +      replaceField.classList.add("cm5-search-replace-input");
    198 +      replacementQueryFragment.appendChild(replaceField);
    199 +
    200 +      dialog(cm, replacementQueryFragment, "Replace with:", "", function(text) {
    201          text = parseString(text)
    202          if (all) {
    203            replaceAll(cm, query, text)
    204          } else {
    205            clearSearch(cm);
    206            var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
    207            var advance = function() {
    208              var start = cursor.from(), match;
    209              if (!(match = cursor.findNext())) {
    210                cursor = getSearchCursor(cm, query);
    211                if (!(match = cursor.findNext()) ||
    212                    (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
    213              }
    214              cm.setSelection(cursor.from(), cursor.to());
    215 -            cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
    216 -            confirmDialog(cm, doReplaceConfirm, "Replace?",
    217 +            cm.scrollIntoView({ from: cursor.from(), to: cursor.to() });
    218 +
    219 +            let replaceConfirmFragment = doc.createDocumentFragment();
    220 +
    221 +            let replaceConfirmLabel = searchLabel.cloneNode(false);
    222 +            replaceConfirmLabel.textContent = "Replace?";
    223 +            replaceConfirmFragment.appendChild(replaceConfirmLabel);
    224 +
    225 +            let yesButton = doc.createElement("button");
    226 +            yesButton.textContent = "Yes";
    227 +            replaceConfirmFragment.appendChild(yesButton);
    228 +
    229 +            let noButton = doc.createElement("button");
    230 +            noButton.textContent = "No";
    231 +            replaceConfirmFragment.appendChild(noButton);
    232 +
    233 +            let allButton = doc.createElement("button");
    234 +            allButton.textContent = "All";
    235 +            replaceConfirmFragment.appendChild(allButton);
    236 +
    237 +            let stopButton = doc.createElement("button");
    238 +            stopButton.textContent = "Stop";
    239 +            replaceConfirmFragment.appendChild(stopButton);
    240 +
    241 +            confirmDialog(cm, replaceConfirmFragment, "Replace?",
    242                            [function() {doReplace(match);}, advance,
    243                             function() {replaceAll(cm, query, text)}]);
    244            };
    245            var doReplace = function(match) {
    246              cursor.replace(typeof query == "string" ? text :
    247                             text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
    248              advance();
    249            };
    250 
    251 ## Other patches
    252 
    253 ```diff
    254 diff --git a/devtools/client/shared/sourceeditor/codemirror/keymap/vim.js b/devtools/client/shared/sourceeditor/codemirror/keymap/vim.js
    255 --- a/devtools/client/shared/sourceeditor/codemirror/keymap/vim.js
    256 +++ b/devtools/client/shared/sourceeditor/codemirror/keymap/vim.js
    257 @@ -4144,23 +4144,41 @@
    258      }
    259      function showConfirm(cm, text) {
    260        if (cm.openNotification) {
    261 -        cm.openNotification('<span style="color: red">' + text + '</span>',
    262 +        cm.openNotification('<span class="cm5-vim-notification-error">' + text + '</span>',
    263                              {bottom: true, duration: 5000});
    264        } else {
    265          alert(text);
    266        }
    267      }
    268 -    function makePrompt(prefix, desc) {
    269 -      var raw = '<span style="font-family: monospace; white-space: pre">' +
    270 -          (prefix || "") + '<input type="text"></span>';
    271 -      if (desc)
    272 -        raw += ' <span style="color: #888">' + desc + '</span>';
    273 -      return raw;
    274 +    function makePrompt(cm, prefix, desc) {
    275 +      const doc = cm.getWrapperElement().ownerDocument;
    276 +      const fragment = doc.createDocumentFragment();
    277 +      const promptEl = doc.createElement("span");
    278 +      promptEl.classList.add("cm5-vim-prompt");
    279 +
    280 +      let inputParent = promptEl;
    281 +      if (prefix) {
    282 +        const labelEl = doc.createElement("label");
    283 +        labelEl.append(doc.createTextNode(prefix));
    284 +        promptEl.append(labelEl);
    285 +        inputParent = labelEl;
    286 +      }
    287 +      const inputEl = doc.createElement("input");
    288 +      inputParent.append(inputEl);
    289 +      fragment.append(promptEl);
    290 +
    291 +      if (desc) {
    292 +        const descriptionEl = doc.createElement("span");
    293 +        descriptionEl.classList.add("cm5-vim-prompt-description");
    294 +        descriptionEl.append(doc.createTextNode(desc));
    295 +        fragment.append(descriptionEl);
    296 +      }
    297 +      return fragment;
    298      }
    299      var searchPromptDesc = '(Javascript regexp)';
    300      function showPrompt(cm, options) {
    301        var shortText = (options.prefix || '') + ' ' + (options.desc || '');
    302 -      var prompt = makePrompt(options.prefix, options.desc);
    303 +      var prompt = makePrompt(cm, options.prefix, options.desc);
    304        dialog(cm, prompt, shortText, options.onClose, options);
    305      }
    306      function regexEqual(r1, r2) {
    307 ```
    308 
    309 # Footnotes
    310 
    311 [1] http://codemirror.net
    312 [2] devtools/client/shared/sourceeditor/codemirror
    313 [3] devtools/client/shared/sourceeditor/test/browser_codemirror.js
    314 [4] devtools/client/jar.mn
    315 [5] devtools/client/shared/sourceeditor/editor.js