Commit de1f0214 authored by Anton Kovalyov's avatar Anton Kovalyov
Browse files

Bug 919709 - Make Debugger use CodeMirror. r=vporof

parent 2dc4ce8e
Loading
Loading
Loading
Loading
+21 −33
Original line number Diff line number Diff line
@@ -78,15 +78,18 @@ const EVENTS = {
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
Cu.import("resource:///modules/devtools/sourceeditor/source-editor.jsm");
Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
Cu.import("resource:///modules/devtools/VariablesView.jsm");
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");

const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
const Editor = require("devtools/sourceeditor/editor");
const promise = require("sdk/core/promise");
const DebuggerEditor = require("devtools/sourceeditor/debugger.js");

XPCOMUtils.defineLazyModuleGetter(this, "Parser",
  "resource:///modules/devtools/Parser.jsm");

@@ -608,7 +611,7 @@ StackFrames.prototype = {
   * Handler for the thread client's resumed notification.
   */
  _onResumed: function() {
    DebuggerView.editor.setDebugLocation(-1);
    DebuggerView.editor.clearDebugLocation();

    // Prepare the watch expression evaluation string for the next pause.
    if (!this._isWatchExpressionsEvaluation) {
@@ -1434,7 +1437,6 @@ EventListeners.prototype = {
 * Handles all the breakpoints in the current debugger.
 */
function Breakpoints() {
  this._onEditorBreakpointChange = this._onEditorBreakpointChange.bind(this);
  this._onEditorBreakpointAdd = this._onEditorBreakpointAdd.bind(this);
  this._onEditorBreakpointRemove = this._onEditorBreakpointRemove.bind(this);
  this.addBreakpoint = this.addBreakpoint.bind(this);
@@ -1457,8 +1459,8 @@ Breakpoints.prototype = {
   *         A promise that is resolved when the breakpoints finishes initializing.
   */
  initialize: function() {
    DebuggerView.editor.addEventListener(
      SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
    DebuggerView.editor.on("breakpointAdded", this._onEditorBreakpointAdd);
    DebuggerView.editor.on("breakpointRemoved", this._onEditorBreakpointRemove);

    // Initialization is synchronous, for now.
    return promise.resolve(null);
@@ -1471,34 +1473,21 @@ Breakpoints.prototype = {
   *         A promise that is resolved when the breakpoints finishes destroying.
   */
  destroy: function() {
    DebuggerView.editor.removeEventListener(
      SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
    DebuggerView.editor.off("breakpointAdded", this._onEditorBreakpointAdd);
    DebuggerView.editor.off("breakpointRemoved", this._onEditorBreakpointRemove);

    return this.removeAllBreakpoints();
  },

  /**
   * Event handler for breakpoint changes that happen in the editor. This
   * function syncs the breakpoints in the editor to those in the debugger.
   *
   * @param object aEvent
   *        The SourceEditor.EVENTS.BREAKPOINT_CHANGE event object.
   */
  _onEditorBreakpointChange: function(aEvent) {
    aEvent.added.forEach(this._onEditorBreakpointAdd, this);
    aEvent.removed.forEach(this._onEditorBreakpointRemove, this);
  },

  /**
   * Event handler for new breakpoints that come from the editor.
   *
   * @param object aEditorBreakpoint
   *        The breakpoint object coming from the editor.
   * @param number aLine
   *        Line number where breakpoint was set.
   */
  _onEditorBreakpointAdd: function(aEditorBreakpoint) {
  _onEditorBreakpointAdd: function(_, aLine) {
    let url = DebuggerView.Sources.selectedValue;
    let line = aEditorBreakpoint.line + 1;
    let location = { url: url, line: line };
    let location = { url: url, line: aLine + 1 };

    // Initialize the breakpoint, but don't update the editor, since this
    // callback is invoked because a breakpoint was added in the editor itself.
@@ -1511,26 +1500,25 @@ Breakpoints.prototype = {
        DebuggerView.editor.addBreakpoint(aBreakpointClient.location.line - 1);
      }
      // Notify that we've shown a breakpoint in the source editor.
      window.emit(EVENTS.BREAKPOINT_SHOWN, aEditorBreakpoint);
      window.emit(EVENTS.BREAKPOINT_SHOWN);
    });
  },

  /**
   * Event handler for breakpoints that are removed from the editor.
   *
   * @param object aEditorBreakpoint
   *        The breakpoint object that was removed from the editor.
   * @param number aLine
   *        Line number where breakpoint was removed.
   */
  _onEditorBreakpointRemove: function(aEditorBreakpoint) {
  _onEditorBreakpointRemove: function(_, aLine) {
    let url = DebuggerView.Sources.selectedValue;
    let line = aEditorBreakpoint.line + 1;
    let location = { url: url, line: line };
    let location = { url: url, line: aLine + 1 };

    // Destroy the breakpoint, but don't update the editor, since this callback
    // is invoked because a breakpoint was removed from the editor itself.
    this.removeBreakpoint(location, { noEditorUpdate: true }).then(() => {
      // Notify that we've hidden a breakpoint in the source editor.
      window.emit(EVENTS.BREAKPOINT_HIDDEN, aEditorBreakpoint);
      window.emit(EVENTS.BREAKPOINT_HIDDEN);
    });
  },

@@ -1644,7 +1632,7 @@ Breakpoints.prototype = {
      // after the target navigated). Note that this will get out of sync
      // if the source text contents change.
      let line = aBreakpointClient.location.line - 1;
      aBreakpointClient.text = DebuggerView.getEditorLineText(line).trim();
      aBreakpointClient.text = DebuggerView.editor.getText(line).trim();

      // Show the breakpoint in the editor and breakpoints pane, and resolve.
      this._showBreakpoint(aBreakpointClient, aOptions);
+28 −26
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@ function SourcesView() {
  this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
  this._onEditorLoad = this._onEditorLoad.bind(this);
  this._onEditorUnload = this._onEditorUnload.bind(this);
  this._onEditorSelection = this._onEditorSelection.bind(this);
  this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
  this._onEditorContextMenu = this._onEditorContextMenu.bind(this);
  this._onSourceSelect = this._onSourceSelect.bind(this);
  this._onSourceClick = this._onSourceClick.bind(this);
@@ -479,7 +479,12 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
   */
  _hideConditionalPopup: function() {
    this._cbPanel.hidden = true;

    // Sometimes this._cbPanel doesn't have hidePopup method which doesn't
    // break anything but simply outputs an exception to the console.
    if (this._cbPanel.hidePopup) {
      this._cbPanel.hidePopup();
    }
  },

  /**
@@ -645,30 +650,30 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
   * The load listener for the source editor.
   */
  _onEditorLoad: function(aName, aEditor) {
    aEditor.addEventListener(SourceEditor.EVENTS.SELECTION, this._onEditorSelection, false);
    aEditor.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU, this._onEditorContextMenu, false);
    aEditor.on("cursorActivity", this._onEditorCursorActivity);
    aEditor.on("contextMenu", this._onEditorContextMenu);
  },

  /**
   * The unload listener for the source editor.
   */
  _onEditorUnload: function(aName, aEditor) {
    aEditor.removeEventListener(SourceEditor.EVENTS.SELECTION, this._onEditorSelection, false);
    aEditor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU, this._onEditorContextMenu, false);
    aEditor.off("cursorActivity", this._onEditorCursorActivity);
    aEditor.off("contextMenu", this._onEditorContextMenu);
  },

  /**
   * The selection listener for the source editor.
   */
  _onEditorSelection: function(e) {
    let { start, end } = e.newValue;

  _onEditorCursorActivity: function(e) {
    let editor = DebuggerView.editor;
    let start  = editor.getCursor("start").line + 1;
    let end    = editor.getCursor().line + 1;
    let url    = this.selectedValue;
    let lineStart = DebuggerView.editor.getLineAtOffset(start) + 1;
    let lineEnd = DebuggerView.editor.getLineAtOffset(end) + 1;
    let location = { url: url, line: lineStart };

    if (this.getBreakpoint(location) && lineStart == lineEnd) {
    let location = { url: url, line: start };

    if (this.getBreakpoint(location) && start == end) {
      this.highlightBreakpoint(location, { noEditorUpdate: true });
    } else {
      this.unhighlightBreakpoint();
@@ -679,9 +684,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
   * The context menu listener for the source editor.
   */
  _onEditorContextMenu: function({ x, y }) {
    let offset = DebuggerView.editor.getOffsetAtLocation(x, y);
    let line = DebuggerView.editor.getLineAtOffset(offset);
    this._editorContextMenuLineNumber = line;
    this._editorContextMenuLineNumber = DebuggerView.editor.getPositionFromCoords(x, y).line;
  },

  /**
@@ -869,13 +872,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
    // If this command was executed via the context menu, add the breakpoint
    // on the currently hovered line in the source editor.
    if (this._editorContextMenuLineNumber >= 0) {
      DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
      DebuggerView.editor.setCursor({ line: this._editorContextMenuLineNumber, ch: 0 });
    }

    // Avoid placing breakpoints incorrectly when using key shortcuts.
    this._editorContextMenuLineNumber = -1;

    let url = DebuggerView.Sources.selectedValue;
    let line = DebuggerView.editor.getCaretPosition().line + 1;
    let line = DebuggerView.editor.getCursor().line + 1;
    let location = { url: url, line: line };
    let breakpointItem = this.getBreakpoint(location);

@@ -896,13 +900,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
    // If this command was executed via the context menu, add the breakpoint
    // on the currently hovered line in the source editor.
    if (this._editorContextMenuLineNumber >= 0) {
      DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
      DebuggerView.editor.setCursor({ line: this._editorContextMenuLineNumber, ch: 0 });
    }

    // Avoid placing breakpoints incorrectly when using key shortcuts.
    this._editorContextMenuLineNumber = -1;

    let url =  DebuggerView.Sources.selectedValue;
    let line = DebuggerView.editor.getCaretPosition().line + 1;
    let line = DebuggerView.editor.getCursor().line + 1;
    let location = { url: url, line: line };
    let breakpointItem = this.getBreakpoint(location);

@@ -1456,7 +1461,7 @@ WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
  _onCmdAddExpression: function(aText) {
    // Only add a new expression if there's no pending input.
    if (this.getAllStrings().indexOf("") == -1) {
      this.addExpression(aText || DebuggerView.editor.getSelectedText());
      this.addExpression(aText || DebuggerView.editor.getSelection());
    }
  },

@@ -2100,12 +2105,9 @@ GlobalSearchView.prototype = Heritage.extend(WidgetMethods, {

    let url = sourceResultsItem.instance.url;
    let line = lineResultsItem.instance.line;
    DebuggerView.setEditorLocation(url, line + 1, { noDebug: true });

    let editor = DebuggerView.editor;
    let offset = editor.getCaretOffset();
    let { start, length } = lineResultsItem.lineData.range;
    editor.setSelection(offset + start, offset + start + length);
    DebuggerView.setEditorLocation(url, line + 1, { noDebug: true });
    DebuggerView.editor.extendSelection(lineResultsItem.lineData.range);
  },

  /**
+8 −17
Original line number Diff line number Diff line
@@ -861,10 +861,9 @@ FilterView.prototype = {
   */
  _performLineSearch: function(aLine) {
    // Make sure we're actually searching for a valid line.
    if (!aLine) {
      return;
    if (aLine) {
      DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 });
    }
    DebuggerView.editor.setCaretPosition(aLine - 1);
  },

  /**
@@ -879,10 +878,8 @@ FilterView.prototype = {
    if (!aToken) {
      return;
    }
    let offset = DebuggerView.editor.find(aToken, { ignoreCase: true });
    if (offset > -1) {
      DebuggerView.editor.setSelection(offset, offset + aToken.length)
    }

    DebuggerView.editor.find(aToken);
  },

  /**
@@ -1043,14 +1040,8 @@ FilterView.prototype = {

    // Jump to the next/previous instance of the currently searched token.
    if (isTokenSearch) {
      let [, token] = args;
      let methods = { selectNext: "findNext", selectPrev: "findPrevious" };

      // Search for the token and select it.
      let offset = DebuggerView.editor[methods[actionToPerform]](true);
      if (offset > -1) {
        DebuggerView.editor.setSelection(offset, offset + token.length)
      }
      let methods = { selectNext: "findNext", selectPrev: "findPrev" };
      DebuggerView.editor[methods[actionToPerform]]();
      return;
    }

@@ -1061,7 +1052,7 @@ FilterView.prototype = {

      // Modify the line number and jump to it.
      line += !isReturnKey ? amounts[actionToPerform] : 0;
      let lineCount = DebuggerView.editor.getLineCount();
      let lineCount = DebuggerView.editor.lineCount();
      let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
      this._doSearch(SEARCH_LINE_FLAG, lineTarget);
      return;
@@ -1084,7 +1075,7 @@ FilterView.prototype = {
  _doSearch: function(aOperator = "", aText = "") {
    this._searchbox.focus();
    this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
    this._searchbox.value = aOperator + (aText || DebuggerView.editor.getSelectedText());
    this._searchbox.value = aOperator + (aText || DebuggerView.editor.getSelection());
  },

  /**
+56 −44
Original line number Diff line number Diff line
@@ -28,13 +28,6 @@ const SEARCH_FUNCTION_FLAG = "@";
const SEARCH_TOKEN_FLAG = "#";
const SEARCH_LINE_FLAG = ":";
const SEARCH_VARIABLE_FLAG = "*";
const DEFAULT_EDITOR_CONFIG = {
  mode: SourceEditor.MODES.TEXT,
  readOnly: true,
  showLineNumbers: true,
  showAnnotationRuler: true,
  showOverviewRuler: true
};

/**
 * Object defining the debugger view components.
@@ -187,7 +180,7 @@ let DebuggerView = {
  },

  /**
   * Initializes the SourceEditor instance.
   * Initializes the Editor instance.
   *
   * @param function aCallback
   *        Called after the editor finishes initializing.
@@ -195,11 +188,35 @@ let DebuggerView = {
  _initializeEditor: function(aCallback) {
    dumpn("Initializing the DebuggerView editor");

    this.editor = new SourceEditor();
    this.editor.init(document.getElementById("editor"), DEFAULT_EDITOR_CONFIG, () => {
    // This needs to be more localizable: see bug 929234.
    let extraKeys = {};
    extraKeys[(Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + "F"] = (cm) => {
      DebuggerView.Filtering._doTokenSearch();
    };

    this.editor = new Editor({
      mode: Editor.modes.text,
      readOnly: true,
      lineNumbers: true,
      showAnnotationRuler: true,
      gutters: [ "breakpoints" ],
      extraKeys: extraKeys,
      contextMenu: "sourceEditorContextMenu"
    });

    this.editor.appendTo(document.getElementById("editor")).then(() => {
      this.editor.extend(DebuggerEditor);
      this._loadingText = L10N.getStr("loadingText");
      this._onEditorLoad(aCallback);
    });

    this.editor.on("gutterClick", (ev, line) => {
      if (this.editor.hasBreakpoint(line)) {
        this.editor.removeBreakpoint(line);
      } else {
        this.editor.addBreakpoint(line);
      }
    });
  },

  /**
@@ -219,7 +236,7 @@ let DebuggerView = {
  },

  /**
   * Destroys the SourceEditor instance and also executes any necessary
   * Destroys the Editor instance and also executes any necessary
   * post-unload operations.
   *
   * @param function aCallback
@@ -276,9 +293,9 @@ let DebuggerView = {
   *        The source text content.
   */
  _setEditorText: function(aTextContent = "") {
    this.editor.setMode(SourceEditor.MODES.TEXT);
    this.editor.setMode(Editor.modes.text);
    this.editor.setText(aTextContent);
    this.editor.resetUndo();
    this.editor.clearHistory();
  },

  /**
@@ -294,22 +311,24 @@ let DebuggerView = {
   */
  _setEditorMode: function(aUrl, aContentType = "", aTextContent = "") {
    // Avoid setting the editor mode for very large files.
    // Is this still necessary? See bug 929225.
    if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
      this.editor.setMode(SourceEditor.MODES.TEXT);
      return void this.editor.setMode(Editor.modes.text);
    }

    // Use JS mode for files with .js and .jsm extensions.
    else if (SourceUtils.isJavaScript(aUrl, aContentType)) {
      this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
    if (SourceUtils.isJavaScript(aUrl, aContentType)) {
      return void this.editor.setMode(Editor.modes.js);
    }

    // Use HTML mode for files in which the first non whitespace character is
    // &lt;, regardless of extension.
    else if (aTextContent.match(/^\s*</)) {
      this.editor.setMode(SourceEditor.MODES.HTML);
    }
    // Unknown languange, use plain text.
    else {
      this.editor.setMode(SourceEditor.MODES.TEXT);
    if (aTextContent.match(/^\s*</)) {
      return void this.editor.setMode(Editor.modes.html);
    }

    // Unknown language, use text.
    this.editor.setMode(Editor.modes.text);
  },

  /**
@@ -415,6 +434,11 @@ let DebuggerView = {
    let sourceItem = this.Sources.getItemByValue(aUrl);
    let sourceForm = sourceItem.attachment.source;

    // Once we change the editor location, it replaces editor's contents.
    // This means that the debug location information is now obsolete, so
    // we need to clear it. We set a new location below, in this function.
    this.editor.clearDebugLocation();

    // Make sure the requested source client is shown in the editor, then
    // update the source editor's caret position and debug location.
    return this._setEditorSource(sourceForm, aFlags).then(() => {
@@ -423,35 +447,23 @@ let DebuggerView = {
      if (aLine < 1) {
        return;
      }

      if (aFlags.charOffset) {
        aLine += this.editor.getLineAtOffset(aFlags.charOffset);
        aLine += this.editor.getPosition(aFlags.charOffset).line;
      }

      if (aFlags.lineOffset) {
        aLine += aFlags.lineOffset;
      }

      if (!aFlags.noCaret) {
        this.editor.setCaretPosition(aLine - 1, aFlags.columnOffset);
        this.editor.setCursor({ line: aLine -1, ch: aFlags.columnOffset || 0 });
      }

      if (!aFlags.noDebug) {
        this.editor.setDebugLocation(aLine - 1, aFlags.columnOffset);
        this.editor.setDebugLocation(aLine - 1);
      }
    });
  },

  /**
   * Gets the text in the source editor's specified line.
   *
   * @param number aLine [optional]
   *        The line to get the text from. If unspecified, it defaults to
   *        the current caret position.
   * @return string
   *         The specified line's text.
   */
  getEditorLineText: function(aLine) {
    let line = aLine || this.editor.getCaretPosition().line;
    let start = this.editor.getLineStart(line);
    let end = this.editor.getLineEnd(line);
    return this.editor.getText(start, end);
    }).then(null, console.error);
  },

  /**
@@ -599,9 +611,9 @@ let DebuggerView = {
    this.EventListeners.empty();

    if (this.editor) {
      this.editor.setMode(SourceEditor.MODES.TEXT);
      this.editor.setMode(Editor.modes.text);
      this.editor.setText("");
      this.editor.resetUndo();
      this.editor.clearHistory();
      this._editorSource = {};
    }
  },
+3 −5
Original line number Diff line number Diff line
@@ -13,7 +13,6 @@
  %debuggerDTD;
]>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/devtools/source-editor-overlay.xul"?>

<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        macanimationtype="document"
@@ -29,7 +28,6 @@
  <script type="text/javascript" src="debugger-panes.js"/>

  <commandset id="editMenuCommands"/>
  <commandset id="sourceEditorCommands"/>

  <commandset id="debuggerCommands">
    <command id="prettyPrintCommand"
@@ -86,7 +84,7 @@

  <popupset id="debuggerPopupset">
    <menupopup id="sourceEditorContextMenu"
               onpopupshowing="goUpdateSourceEditorMenuItems()">
               onpopupshowing="goUpdateGlobalEditMenuItems()">
      <menuitem id="se-dbg-cMenu-addBreakpoint"
                label="&debuggerUI.seMenuBreak;"
                key="addBreakpointKey"
@@ -100,9 +98,9 @@
                key="addWatchExpressionKey"
                command="addWatchExpressionCommand"/>
      <menuseparator/>
      <menuitem id="se-cMenu-copy"/>
      <menuitem id="cMenu_copy"/>
      <menuseparator/>
      <menuitem id="se-cMenu-selectAll"/>
      <menuitem id="cMenu_selectAll"/>
      <menuseparator/>
      <menuitem id="se-dbg-cMenu-findFile"
                label="&debuggerUI.searchFile;"
Loading