Bug 1058183 - Expose code folding as an option for all editor instances. r=bgrins
authorAdrian Aichner <adrian.aichner@gmail.com>
Fri, 12 Sep 2014 13:36:00 +0200
changeset 205305 02648d3811fe6989a304a42534b7babfd532e5d3
parent 205304 449fb0bd623fd7cc7174216f935c7108a516e404
child 205306 454e5c070bc14f40a26b079c85e89e5c3d76e20f
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbgrins
bugs1058183
milestone35.0a1
Bug 1058183 - Expose code folding as an option for all editor instances. r=bgrins
browser/app/profile/firefox.js
browser/devtools/debugger/debugger-view.js
browser/devtools/scratchpad/scratchpad.js
browser/devtools/sourceeditor/editor.js
browser/devtools/sourceeditor/test/browser_editor_prefs.js
browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1402,17 +1402,16 @@ pref("devtools.tilt.outro_transition", t
 //                  stored. Setting this preference to 0 will not
 //                  clear any recent files, but rather hide the
 //                  'Open Recent'-menu.
 // - showTrailingSpace: Whether to highlight trailing space or not.
 // - enableCodeFolding: Whether to enable code folding or not.
 // - enableAutocompletion: Whether to enable JavaScript autocompletion.
 pref("devtools.scratchpad.recentFilesMax", 10);
 pref("devtools.scratchpad.showTrailingSpace", false);
-pref("devtools.scratchpad.enableCodeFolding", true);
 pref("devtools.scratchpad.enableAutocompletion", true);
 
 // Enable the Storage Inspector
 pref("devtools.storage.enabled", false);
 
 // Enable the Style Editor.
 pref("devtools.styleeditor.enabled", true);
 pref("devtools.styleeditor.source-maps-enabled", false);
@@ -1508,16 +1507,17 @@ pref("devtools.eyedropper.zoom", 6);
 // - keymap: which keymap to use (can be 'default', 'emacs' or 'vim')
 // - autoclosebrackets: whether to permit automatic bracket/quote closing.
 // - detectindentation: whether to detect the indentation from the file
 pref("devtools.editor.tabsize", 2);
 pref("devtools.editor.expandtab", true);
 pref("devtools.editor.keymap", "default");
 pref("devtools.editor.autoclosebrackets", true);
 pref("devtools.editor.detectindentation", true);
+pref("devtools.editor.enableCodeFolding", true);
 pref("devtools.editor.autocomplete", true);
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Pref to store the browser version at the time of a telemetry ping for an
 // opened developer tool. This allows us to ping telemetry just once per browser
 // version for each user.
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -228,17 +228,18 @@ let DebuggerView = {
 
     this.editor = new Editor({
       mode: Editor.modes.text,
       readOnly: true,
       lineNumbers: true,
       showAnnotationRuler: true,
       gutters: gutters,
       extraKeys: extraKeys,
-      contextMenu: "sourceEditorContextMenu"
+      contextMenu: "sourceEditorContextMenu",
+      enableCodeFolding: false
     });
 
     this.editor.appendTo(document.getElementById("editor")).then(() => {
       this.editor.extend(DebuggerEditor);
       this._loadingText = L10N.getStr("loadingText");
       this._onEditorLoad(aCallback);
     });
 
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -29,18 +29,18 @@ const EVAL_FUNCTION_TIMEOUT      = 1000;
 const MAXIMUM_FONT_SIZE = 96;
 const MINIMUM_FONT_SIZE = 6;
 const NORMAL_FONT_SIZE = 12;
 
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
 const SHOW_TRAILING_SPACE = "devtools.scratchpad.showTrailingSpace";
-const ENABLE_CODE_FOLDING = "devtools.scratchpad.enableCodeFolding";
 const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
+const TAB_SIZE = "devtools.editor.tabsize";
 
 const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
 
 const require   = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 
 const Telemetry = require("devtools/shared/telemetry");
 const Editor    = require("devtools/sourceeditor/editor");
 const TargetFactory = require("devtools/framework/target").TargetFactory;
@@ -650,17 +650,17 @@ var Scratchpad = {
    * Pretty print the source text inside the scratchpad.
    *
    * @return Promise
    *         A promise resolved with the pretty printed code, or rejected with
    *         an error.
    */
   prettyPrint: function SP_prettyPrint() {
     const uglyText = this.getText();
-    const tabsize = Services.prefs.getIntPref("devtools.editor.tabsize");
+    const tabsize = Services.prefs.getIntPref(TAB_SIZE);
     const id = Math.random();
     const deferred = promise.defer();
 
     const onReply = ({ data }) => {
       if (data.id !== id) {
         return;
       }
       this.prettyPrintWorker.removeEventListener("message", onReply, false);
@@ -1599,17 +1599,16 @@ var Scratchpad = {
     }
 
     let config = {
       mode: Editor.modes.js,
       value: initialText,
       lineNumbers: true,
       contextMenu: "scratchpad-text-popup",
       showTrailingSpace: Services.prefs.getBoolPref(SHOW_TRAILING_SPACE),
-      enableCodeFolding: Services.prefs.getBoolPref(ENABLE_CODE_FOLDING),
       autocomplete: Services.prefs.getBoolPref(ENABLE_AUTOCOMPLETION),
     };
 
     this.editor = new Editor(config);
     let editorElement = document.querySelector("#scratchpad-editor");
     this.editor.appendTo(editorElement).then(() => {
       var lines = initialText.split("\n");
 
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cu, Cc, Ci, components } = require("chrome");
 
 const TAB_SIZE    = "devtools.editor.tabsize";
+const ENABLE_CODE_FOLDING = "devtools.editor.enableCodeFolding";
 const EXPAND_TAB  = "devtools.editor.expandtab";
 const KEYMAP      = "devtools.editor.keymap";
 const AUTO_CLOSE  = "devtools.editor.autoclosebrackets";
 const AUTOCOMPLETE  = "devtools.editor.autocomplete";
 const DETECT_INDENT = "devtools.editor.detectindentation";
 const DETECT_INDENT_MAX_LINES = 500;
 const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
 const XUL_NS      = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@@ -183,24 +184,22 @@ function Editor(config) {
     if (!config.extraKeys)
       return;
 
     Object.keys(config.extraKeys).forEach((key) => {
       this.config.extraKeys[key] = config.extraKeys[key];
     });
   });
 
-  // Set the code folding gutter, if needed.
-  if (this.config.enableCodeFolding) {
-    this.config.foldGutter = true;
-
-    if (!this.config.gutters) {
-      this.config.gutters = this.config.lineNumbers ? ["CodeMirror-linenumbers"] : [];
-      this.config.gutters.push("CodeMirror-foldgutter");
-    }
+  if (!this.config.gutters) {
+    this.config.gutters = [];
+  }
+  if (this.config.lineNumbers
+      && this.config.gutters.indexOf("CodeMirror-linenumbers") === -1) {
+    this.config.gutters.push("CodeMirror-linenumbers");
   }
 
   // Remember the initial value of autoCloseBrackets.
   this.config.autoCloseBracketsSaved = this.config.autoCloseBrackets;
 
   // Overwrite default tab behavior. If something is selected,
   // indent those lines. If nothing is selected and we're
   // indenting with tabs, insert one tab. Otherwise insert N
@@ -328,16 +327,17 @@ Editor.prototype = {
       this.reloadPreferences = this.reloadPreferences.bind(this);
       this._prefObserver = new PrefObserver("devtools.editor.");
       this._prefObserver.on(TAB_SIZE, this.reloadPreferences);
       this._prefObserver.on(EXPAND_TAB, this.reloadPreferences);
       this._prefObserver.on(KEYMAP, this.reloadPreferences);
       this._prefObserver.on(AUTO_CLOSE, this.reloadPreferences);
       this._prefObserver.on(AUTOCOMPLETE, this.reloadPreferences);
       this._prefObserver.on(DETECT_INDENT, this.reloadPreferences);
+      this._prefObserver.on(ENABLE_CODE_FOLDING, this.reloadPreferences);
 
       this.reloadPreferences();
       def.resolve();
     };
 
     env.addEventListener("load", onLoad, true);
     env.setAttribute("src", CM_IFRAME);
     el.appendChild(env);
@@ -425,16 +425,17 @@ Editor.prototype = {
       useAutoClose ? this.config.autoCloseBracketsSaved : false);
 
     // If alternative keymap is provided, use it.
     const keyMap = Services.prefs.getCharPref(KEYMAP);
     if (VALID_KEYMAPS.has(keyMap))
       this.setOption("keyMap", keyMap)
     else
       this.setOption("keyMap", "default");
+    this.updateCodeFoldingGutter();
 
     this.resetIndentUnit();
     this.setupAutoCompletion();
   },
 
   /**
    * Set the editor's indentation based on the current prefs and
    * re-detect indentation if we should.
@@ -950,16 +951,22 @@ Editor.prototype = {
     }
 
     if (o === "autocomplete") {
       this.config.autocomplete = v;
       this.setupAutoCompletion();
     } else {
       cm.setOption(o, v);
     }
+
+    if (o === "enableCodeFolding") {
+      // The new value maybe explicitly force foldGUtter on or off, ignoring
+      // the prefs service.
+      this.updateCodeFoldingGutter();
+    }
   },
 
   /**
    * Gets an option for the editor.  For most options it just defers to
    * CodeMirror.getOption, but certain ones are maintained within the editor
    * instance.
    */
   getOption: function(o) {
@@ -1031,20 +1038,56 @@ Editor.prototype = {
 
     if (this._prefObserver) {
       this._prefObserver.off(TAB_SIZE, this.reloadPreferences);
       this._prefObserver.off(EXPAND_TAB, this.reloadPreferences);
       this._prefObserver.off(KEYMAP, this.reloadPreferences);
       this._prefObserver.off(AUTO_CLOSE, this.reloadPreferences);
       this._prefObserver.off(AUTOCOMPLETE, this.reloadPreferences);
       this._prefObserver.off(DETECT_INDENT, this.reloadPreferences);
+      this._prefObserver.off(ENABLE_CODE_FOLDING, this.reloadPreferences);
       this._prefObserver.destroy();
     }
 
     this.emit("destroy");
+  },
+
+  updateCodeFoldingGutter: function () {
+    let shouldFoldGutter = this.config.enableCodeFolding,
+        foldGutterIndex = this.config.gutters.indexOf("CodeMirror-foldgutter"),
+        cm = editors.get(this);
+
+    if (shouldFoldGutter === undefined) {
+      shouldFoldGutter = Services.prefs.getBoolPref(ENABLE_CODE_FOLDING);
+    }
+
+    if (shouldFoldGutter) {
+      // Add the gutter before enabling foldGutter
+      if (foldGutterIndex === -1) {
+        let gutters = this.config.gutters.slice();
+        gutters.push("CodeMirror-foldgutter");
+        this.setOption("gutters", gutters);
+      }
+
+      this.setOption("foldGutter", true);
+    } else {
+      // No code should remain folded when folding is off.
+      if (cm) {
+        cm.execCommand("unfoldAll");
+      }
+
+      // Remove the gutter so it doesn't take up space
+      if (foldGutterIndex !== -1) {
+        let gutters = this.config.gutters.slice();
+        gutters.splice(foldGutterIndex, 1);
+        this.setOption("gutters", gutters);
+      }
+
+      this.setOption("foldGutter", false);
+    }
   }
 };
 
 // Since Editor is a thin layer over CodeMirror some methods
 // are mapped directly—without any changes.
 
 CM_MAPPING.forEach(function (name) {
   Editor.prototype[name] = function (...args) {
--- a/browser/devtools/sourceeditor/test/browser_editor_prefs.js
+++ b/browser/devtools/sourceeditor/test/browser_editor_prefs.js
@@ -2,63 +2,103 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test to make sure that the editor reacts to preference changes
 
 const TAB_SIZE    = "devtools.editor.tabsize";
+const ENABLE_CODE_FOLDING = "devtools.editor.enableCodeFolding";
 const EXPAND_TAB  = "devtools.editor.expandtab";
 const KEYMAP      = "devtools.editor.keymap";
 const AUTO_CLOSE  = "devtools.editor.autoclosebrackets";
 const AUTOCOMPLETE  = "devtools.editor.autocomplete";
 const DETECT_INDENT = "devtools.editor.detectindentation";
 
 function test() {
   waitForExplicitFinish();
   setup((ed, win) => {
 
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints",
+      "CodeMirror-foldgutter"], "gutters is correct");
+
     ed.setText("Checking preferences.");
 
     info ("Turning prefs off");
 
     ed.setOption("autocomplete", true);
 
     Services.prefs.setIntPref(TAB_SIZE, 2);
+    Services.prefs.setBoolPref(ENABLE_CODE_FOLDING, false);
     Services.prefs.setBoolPref(EXPAND_TAB, false);
     Services.prefs.setCharPref(KEYMAP, "default");
     Services.prefs.setBoolPref(AUTO_CLOSE, false);
     Services.prefs.setBoolPref(AUTOCOMPLETE, false);
     Services.prefs.setBoolPref(DETECT_INDENT, false);
 
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints"], "gutters is correct");
+
     is(ed.getOption("tabSize"), 2, "tabSize is correct");
     is(ed.getOption("indentUnit"), 2, "indentUnit is correct");
+    is(ed.getOption("foldGutter"), false, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), undefined, "enableCodeFolding is correct");
     is(ed.getOption("indentWithTabs"), true, "indentWithTabs is correct");
     is(ed.getOption("keyMap"), "default", "keyMap is correct");
     is(ed.getOption("autoCloseBrackets"), "", "autoCloseBrackets is correct");
     is(ed.getOption("autocomplete"), true, "autocomplete is correct");
     ok(!ed.isAutocompletionEnabled(), "Autocompletion is not enabled");
 
     info ("Turning prefs on");
 
     Services.prefs.setIntPref(TAB_SIZE, 4);
+    Services.prefs.setBoolPref(ENABLE_CODE_FOLDING, true);
     Services.prefs.setBoolPref(EXPAND_TAB, true);
     Services.prefs.setCharPref(KEYMAP, "sublime");
     Services.prefs.setBoolPref(AUTO_CLOSE, true);
     Services.prefs.setBoolPref(AUTOCOMPLETE, true);
 
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints",
+      "CodeMirror-foldgutter"], "gutters is correct");
+
     is(ed.getOption("tabSize"), 4, "tabSize is correct");
     is(ed.getOption("indentUnit"), 4, "indentUnit is correct");
+    is(ed.getOption("foldGutter"), true, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), undefined, "enableCodeFolding is correct");
     is(ed.getOption("indentWithTabs"), false, "indentWithTabs is correct");
     is(ed.getOption("keyMap"), "sublime", "keyMap is correct");
     is(ed.getOption("autoCloseBrackets"), "()[]{}''\"\"", "autoCloseBrackets is correct");
     is(ed.getOption("autocomplete"), true, "autocomplete is correct");
     ok(ed.isAutocompletionEnabled(), "Autocompletion is enabled");
 
+    info ("Forcing foldGutter off using enableCodeFolding");
+    ed.setOption("enableCodeFolding", false);
+
+    is(ed.getOption("foldGutter"), false, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), false, "enableCodeFolding is correct");
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints"], "gutters is correct");
+
+    info ("Forcing foldGutter on using enableCodeFolding");
+    ed.setOption("enableCodeFolding", true);
+
+    is(ed.getOption("foldGutter"), true, "foldGutter is correct");
+    is(ed.getOption("enableCodeFolding"), true, "enableCodeFolding is correct");
+    Assert.deepEqual(ed.getOption("gutters"), [
+      "CodeMirror-linenumbers",
+      "breakpoints",
+      "CodeMirror-foldgutter"], "gutters is correct");
+
     info ("Checking indentation detection");
 
     Services.prefs.setBoolPref(DETECT_INDENT, true);
 
     ed.setText("Detecting\n\tTabs");
     is(ed.getOption("indentWithTabs"), true, "indentWithTabs is correct");
     is(ed.getOption("indentUnit"), 4, "indentUnit is correct");
 
--- a/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js
@@ -30,19 +30,19 @@ let test = asyncTest(function*() {
       this.isShown = false;
     }
   };
 
   info("Expecting a node-highlighted event");
   let onHighlighted = editor.once("node-highlighted");
 
   info("Simulate a mousemove event on the div selector");
-  editor._onMouseMove({clientX: 40, clientY: 10});
+  editor._onMouseMove({clientX: 56, clientY: 10});
   yield onHighlighted;
 
   ok(editor.highlighter.isShown, "The highlighter is now shown");
   is(editor.highlighter.options.selector, "div", "The selector is correct");
 
   info("Simulate a mousemove event elsewhere in the editor");
-  editor._onMouseMove({clientX: 0, clientY: 0});
+  editor._onMouseMove({clientX: 16, clientY: 0});
 
   ok(!editor.highlighter.isShown, "The highlighter is now hidden");
 });