Bug 1022084 - Project Editor: turn on auto completion for JS & CSS files;r=harth
authorBrian Grinstead <bgrinstead@mozilla.com>
Wed, 11 Jun 2014 13:14:56 -0700
changeset 188314 e8cf4d699fa725a35e8e853746239fb23e8db373
parent 188313 729f6c1413c7def708a58aeaab9068b20b0103ff
child 188315 155b952193bb423d67b1683f98ec71af67fe0f25
push id26951
push useremorley@mozilla.com
push dateThu, 12 Jun 2014 14:07:43 +0000
treeherdermozilla-central@4f98802de6ce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersharth
bugs1022084
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1022084 - Project Editor: turn on auto completion for JS & CSS files;r=harth
browser/devtools/projecteditor/lib/editors.js
browser/devtools/projecteditor/lib/plugins/delete/delete.js
browser/devtools/projecteditor/lib/projecteditor.js
browser/devtools/projecteditor/lib/shells.js
browser/devtools/projecteditor/lib/stores/resource.js
browser/devtools/projecteditor/lib/tree.js
browser/devtools/styleeditor/StyleSheetEditor.jsm
--- a/browser/devtools/projecteditor/lib/editors.js
+++ b/browser/devtools/projecteditor/lib/editors.js
@@ -136,28 +136,34 @@ var TextEditor = Class({
 
   initialize: function(document, mode=Editor.modes.text) {
     ItchEditor.prototype.initialize.apply(this, arguments);
     this.label = mode.name;
     this.editor = new Editor({
       mode: mode,
       lineNumbers: true,
       extraKeys: this.extraKeys,
-      themeSwitching: false
+      themeSwitching: false,
+      autocomplete: true
     });
 
     // Trigger editor specific events on `this`
     this.editor.on("change", (...args) => {
       this.emit("change", ...args);
     });
     this.editor.on("cursorActivity", (...args) => {
       this.emit("cursorActivity", ...args);
     });
 
     this.appended = this.editor.appendTo(this.elt);
+    this.appended.then(() => {
+      if (this.editor) {
+        this.editor.setupAutoCompletion();
+      }
+    });
   },
 
   /**
    * Clean up the editor.  This can have different meanings
    * depending on the type of editor.
    */
   destroy: function() {
     this.editor.destroy();
--- a/browser/devtools/projecteditor/lib/plugins/delete/delete.js
+++ b/browser/devtools/projecteditor/lib/plugins/delete/delete.js
@@ -22,17 +22,17 @@ var DeletePlugin = Class({
     });
   },
 
   onCommand: function(cmd) {
     if (cmd === "cmd-delete") {
       let tree = this.host.projectTree;
       let resource = tree.getSelectedResource();
       let parent = resource.parent;
-      tree.deleteResource(resource).then(() => {
+      resource.delete().then(() => {
         this.host.project.refresh();
       })
     }
   }
 });
 
 exports.DeletePlugin = DeletePlugin;
 registerPlugin(DeletePlugin);
--- a/browser/devtools/projecteditor/lib/projecteditor.js
+++ b/browser/devtools/projecteditor/lib/projecteditor.js
@@ -74,16 +74,17 @@ var ProjectEditor = Class({
    *
    * @param Iframe iframe
    *        The iframe to inject the DOM into.  If this is not
    *        specified, then this.load(frame) will need to be called
    *        before accessing ProjectEditor.
    */
   initialize: function(iframe) {
     this._onTreeSelected = this._onTreeSelected.bind(this);
+    this._onTreeResourceRemoved = this._onTreeResourceRemoved.bind(this);
     this._onEditorCreated = this._onEditorCreated.bind(this);
     this._onEditorActivated = this._onEditorActivated.bind(this);
     this._onEditorDeactivated = this._onEditorDeactivated.bind(this);
     this._updateEditorMenuItems = this._updateEditorMenuItems.bind(this);
 
     if (iframe) {
       this.load(iframe);
     }
@@ -162,17 +163,18 @@ var ProjectEditor = Class({
   /**
    * Create the project tree sidebar that lists files.
    */
   _buildSidebar: function() {
     this.projectTree = new ProjectTreeView(this.document, {
       resourceVisible: this.resourceVisible.bind(this),
       resourceFormatter: this.resourceFormatter.bind(this)
     });
-    this.projectTree.on("selection", this._onTreeSelected);
+    on(this, this.projectTree, "selection", this._onTreeSelected);
+    on(this, this.projectTree, "resource-removed", this._onTreeResourceRemoved);
 
     let sourcesBox = this.document.querySelector("#sources");
     sourcesBox.appendChild(this.projectTree.elt);
   },
 
   /**
    * Set up listeners for commands to dispatch to all of the plugins
    */
@@ -213,28 +215,25 @@ var ProjectEditor = Class({
   },
 
   /**
    * Destroy all objects on the iframe unload event.
    */
   destroy: function() {
     this._plugins.forEach(plugin => { plugin.destroy(); });
 
-    this.project.allResources().forEach((resource) => {
-      let editor = this.editorFor(resource);
-      if (editor) {
-        editor.destroy();
-      }
-    });
+    forget(this, this.projectTree);
+    this.projectTree.destroy();
+    this.projectTree = null;
+
+    this.shells.destroy();
 
     forget(this, this.project);
     this.project.destroy();
     this.project = null;
-    this.projectTree.destroy();
-    this.projectTree = null;
   },
 
   /**
    * Set the current project viewed by the projecteditor.
    *
    * @param Project project
    *        The project to set.
    */
@@ -244,20 +243,17 @@ var ProjectEditor = Class({
     }
     this.project = project;
     this.projectTree.setProject(project);
 
     // Whenever a store gets removed, clean up any editors that
     // exist for resources within it.
     on(this, project, "store-removed", (store) => {
       store.allResources().forEach((resource) => {
-        let editor = this.editorFor(resource);
-        if (editor) {
-          editor.destroy();
-        }
+        this.shells.removeResource(resource);
       });
     });
   },
 
   /**
    * Set the current project viewed by the projecteditor to a single path,
    * used by the app manager.
    *
@@ -312,16 +308,26 @@ var ProjectEditor = Class({
     if (resource.isDir && resource.parent) {
       return;
     }
     this.pluginDispatch("onTreeSelected", resource);
     this.openResource(resource);
   },
 
   /**
+   * When a node is removed, destroy it and its associated editor.
+   *
+   * @param Resource resource
+   *                 The resource being removed
+   */
+  _onTreeResourceRemoved: function(resource) {
+    this.shells.removeResource(resource);
+  },
+
+  /**
    * Create an xul element with options
    *
    * @param string type
    *               The tag name of the element to create.
    * @param Object options
    *               "command": DOMNode or string ID of a command element.
    *               "parent": DOMNode or selector of parent to append child to.
    *               anything other keys are set as an attribute as the element.
--- a/browser/devtools/projecteditor/lib/shells.js
+++ b/browser/devtools/projecteditor/lib/shells.js
@@ -55,16 +55,24 @@ var Shell = Class({
    * a result of this function, so any listeners to 'editorAppended'
    * need to be added before calling this.
    */
   load: function() {
     this.editor.load(this.resource);
   },
 
   /**
+   * Destroy the shell and its associated editor
+   */
+  destroy: function() {
+    this.editor.destroy();
+    this.resource.destroy();
+  },
+
+  /**
    * Make sure the correct editor is selected for the resource.
    * @returns Type:Editor
    */
   _editorTypeForResource: function() {
     let resource = this.resource;
     let constructor = EditorTypeForResource(resource);
 
     if (this.host.plugins) {
@@ -146,16 +154,36 @@ var ShellDeck = Class({
     });
 
     shell.load();
     this.deck.appendChild(shell.elt);
     return shell;
   },
 
   /**
+   * Remove the shell for a given resource.
+   *
+   * @param Resource resource
+   */
+  removeResource: function(resource) {
+    let shell = this.shellFor(resource);
+    if (shell) {
+      this.shells.delete(resource);
+      shell.destroy();
+    }
+  },
+
+  destroy: function() {
+    for (let [resource, shell] of this.shells.entries()) {
+      this.shells.delete(resource);
+      shell.destroy();
+    }
+  },
+
+  /**
    * Select a given shell and open its editor.
    * Will fire editor-deactivated on the old selected Shell (if any),
    * and editor-activated on the new one once it is ready
    *
    * @param Shell shell
    */
   selectShell: function(shell) {
     // Don't fire another activate if this is already the active shell
--- a/browser/devtools/projecteditor/lib/stores/resource.js
+++ b/browser/devtools/projecteditor/lib/stores/resource.js
@@ -26,21 +26,24 @@ const gEncoder = new TextEncoder();
  * as a file for ProjectEditor.
  *
  * The Resource class is not exported, and should not be instantiated
  * Instead, you should use the FileResource class that extends it.
  *
  * This object emits the following events:
  *   - "children-changed": When a child has been added or removed.
  *                         See setChildren.
+ *   - "deleted": When the resource has been deleted.
  */
 var Resource = Class({
   extends: EventTarget,
 
-  refresh: function() { return promise.resolve(this) },
+  refresh: function() { return promise.resolve(this); },
+  destroy: function() { },
+  delete: function() { },
 
   setURI: function(uri) {
     if (typeof(uri) === "string") {
       uri = URL.URL(uri);
     }
     this.uri = uri;
   },
 
@@ -240,16 +243,31 @@ var FileResource = Class({
    */
   load: function() {
     return OS.File.read(this.path).then(bytes => {
       return gDecoder.decode(bytes);
     });
   },
 
   /**
+   * Delete the file from the filesystem
+   *
+   * @returns Promise
+   *          Resolves when the file is deleted
+   */
+  delete: function() {
+    emit(this, "deleted", this);
+    if (this.isDir) {
+      return OS.File.removeDir(this.path);
+    } else {
+      return OS.File.remove(this.path);
+    }
+  },
+
+  /**
    * Add a text file as a child of this FileResource.
    * This instance must be a directory.
    *
    * @param string name
    *               The filename (path will be generated based on this.path).
    *        string initial
    *               The content to write to the new file.
    * @returns Promise
--- a/browser/devtools/projecteditor/lib/tree.js
+++ b/browser/devtools/projecteditor/lib/tree.js
@@ -229,16 +229,17 @@ var TreeView = Class({
     this.elt.setAttribute("with-arrows", "true");
     this.elt.setAttribute("theme", "dark");
     this.elt.setAttribute("flex", "1");
 
     this.children = document.createElementNS(HTML_NS, "ul");
     this.elt.appendChild(this.children);
 
     this.resourceChildrenChanged = this.resourceChildrenChanged.bind(this);
+    this.removeResource = this.removeResource.bind(this);
     this.updateResource = this.updateResource.bind(this);
   },
 
   destroy: function() {
     this._destroyed = true;
     this.elt.remove();
   },
 
@@ -394,35 +395,22 @@ var TreeView = Class({
       return this._containers.get(resource);
     }
     var container = ResourceContainer(this, resource);
     this._containers.set(resource, container);
     this._updateChildren(container);
 
     on(this, resource, "children-changed", this.resourceChildrenChanged);
     on(this, resource, "label-change", this.updateResource);
+    on(this, resource, "deleted", this.removeResource);
 
     return container;
   },
 
   /**
-   * Delete a Resource from the FileSystem.  XXX: This should
-   * definitely be moved away from here, maybe to the store?
-   *
-   * @param Resource resource
-   */
-  deleteResource: function(resource) {
-    if (resource.isDir) {
-      return OS.File.removeDir(resource.path);
-    } else {
-      return OS.File.remove(resource.path);
-    }
-  },
-
-  /**
    * Remove a Resource (including children) from the view.
    *
    * @param Resource resource
    */
   removeResource: function(resource) {
     let toRemove = resource.allDescendants();
     toRemove.add(resource);
     for (let remove of toRemove) {
@@ -431,22 +419,22 @@ var TreeView = Class({
   },
 
   /**
    * Remove an individual Resource (but not children) from the view.
    *
    * @param Resource resource
    */
   _removeResource: function(resource) {
-    resource.off("children-changed", this.resourceChildrenChanged);
-    resource.off("label-change", this.updateResource);
+    forget(this, resource);
     if (this._containers.get(resource)) {
       this._containers.get(resource).destroy();
       this._containers.delete(resource);
     }
+    emit(this, "resource-removed", resource);
   },
 
   /**
    * Listener for when a resource has new children.
    * This can happen as files are being loaded in from FileSystem, for example.
    *
    * @param Resource resource
    */
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -10,17 +10,16 @@ this.EXPORTED_SYMBOLS = ["StyleSheetEdit
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const Editor  = require("devtools/sourceeditor/editor");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const {CssLogic} = require("devtools/styleinspector/css-logic");
-const AutoCompleter = require("devtools/sourceeditor/autocomplete");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/devtools/event-emitter.js");
 Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");