summaryrefslogtreecommitdiffstats
path: root/devtools/client/projecteditor/lib/editors.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/projecteditor/lib/editors.js')
-rw-r--r--devtools/client/projecteditor/lib/editors.js303
1 files changed, 303 insertions, 0 deletions
diff --git a/devtools/client/projecteditor/lib/editors.js b/devtools/client/projecteditor/lib/editors.js
new file mode 100644
index 000000000..7d0150cf7
--- /dev/null
+++ b/devtools/client/projecteditor/lib/editors.js
@@ -0,0 +1,303 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+const { Cu } = require("chrome");
+const { Class } = require("sdk/core/heritage");
+const { EventTarget } = require("sdk/event/target");
+const { emit } = require("sdk/event/core");
+const promise = require("promise");
+const Editor = require("devtools/client/sourceeditor/editor");
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+/**
+ * ItchEditor is extended to implement an editor, which is the main view
+ * that shows up when a file is selected. This object should not be used
+ * directly - use TextEditor for a basic code editor.
+ */
+var ItchEditor = Class({
+ extends: EventTarget,
+
+ /**
+ * A boolean specifying if the toolbar above the editor should be hidden.
+ */
+ hidesToolbar: false,
+
+ /**
+ * A boolean specifying whether the editor can be edited / saved.
+ * For instance, a 'save' doesn't make sense on an image.
+ */
+ isEditable: false,
+
+ toString: function () {
+ return this.label || "";
+ },
+
+ emit: function (name, ...args) {
+ emit(this, name, ...args);
+ },
+
+ /* Does the editor not have any unsaved changes? */
+ isClean: function () {
+ return true;
+ },
+
+ /**
+ * Initialize the editor with a single host. This should be called
+ * by objects extending this object with:
+ * ItchEditor.prototype.initialize.apply(this, arguments)
+ */
+ initialize: function (host) {
+ this.host = host;
+ this.doc = host.document;
+ this.label = "";
+ this.elt = this.doc.createElement("vbox");
+ this.elt.setAttribute("flex", "1");
+ this.elt.editor = this;
+ this.toolbar = this.doc.querySelector("#projecteditor-toolbar");
+ this.projectEditorKeyset = host.projectEditorKeyset;
+ this.projectEditorCommandset = host.projectEditorCommandset;
+ },
+
+ /**
+ * Sets the visibility of the element that shows up above the editor
+ * based on the this.hidesToolbar property.
+ */
+ setToolbarVisibility: function () {
+ if (this.hidesToolbar) {
+ this.toolbar.setAttribute("hidden", "true");
+ } else {
+ this.toolbar.removeAttribute("hidden");
+ }
+ },
+
+
+ /**
+ * Load a single resource into the editor.
+ *
+ * @param Resource resource
+ * The single file / item that is being dealt with (see stores/base)
+ * @returns Promise
+ * A promise that is resolved once the editor has loaded the contents
+ * of the resource.
+ */
+ load: function (resource) {
+ return promise.resolve();
+ },
+
+ /**
+ * Clean up the editor. This can have different meanings
+ * depending on the type of editor.
+ */
+ destroy: function () {
+
+ },
+
+ /**
+ * Give focus to the editor. This can have different meanings
+ * depending on the type of editor.
+ *
+ * @returns Promise
+ * A promise that is resolved once the editor has been focused.
+ */
+ focus: function () {
+ return promise.resolve();
+ }
+});
+exports.ItchEditor = ItchEditor;
+
+/**
+ * The main implementation of the ItchEditor class. The TextEditor is used
+ * when editing any sort of plain text file, and can be created with different
+ * modes for syntax highlighting depending on the language.
+ */
+var TextEditor = Class({
+ extends: ItchEditor,
+
+ isEditable: true,
+
+ /**
+ * Extra keyboard shortcuts to use with the editor. Shortcuts defined
+ * within projecteditor should be triggered when they happen in the editor, and
+ * they would usually be swallowed without registering them.
+ * See "devtools/sourceeditor/editor" for more information.
+ */
+ get extraKeys() {
+ let extraKeys = {};
+
+ // Copy all of the registered keys into extraKeys object, to notify CodeMirror
+ // that it should be ignoring these keys
+ [...this.projectEditorKeyset.querySelectorAll("key")].forEach((key) => {
+ let keyUpper = key.getAttribute("key").toUpperCase();
+ let toolModifiers = key.getAttribute("modifiers");
+ let modifiers = {
+ alt: toolModifiers.includes("alt"),
+ shift: toolModifiers.includes("shift")
+ };
+
+ // On the key press, we will dispatch the event within projecteditor.
+ extraKeys[Editor.accel(keyUpper, modifiers)] = () => {
+ let doc = this.projectEditorCommandset.ownerDocument;
+ let event = doc.createEvent("Event");
+ event.initEvent("command", true, true);
+ let command = this.projectEditorCommandset.querySelector("#" + key.getAttribute("command"));
+ command.dispatchEvent(event);
+ };
+ });
+
+ return extraKeys;
+ },
+
+ isClean: function () {
+ if (!this.editor.isAppended()) {
+ return true;
+ }
+ return this.editor.getText() === this._savedResourceContents;
+ },
+
+ 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,
+ autocomplete: true,
+ contextMenu: this.host.textEditorContextMenuPopup
+ });
+
+ // Trigger a few editor specific events on `this`.
+ this.editor.on("change", (...args) => {
+ this.emit("change", ...args);
+ });
+ this.editor.on("cursorActivity", (...args) => {
+ this.emit("cursorActivity", ...args);
+ });
+ this.editor.on("focus", (...args) => {
+ this.emit("focus", ...args);
+ });
+ this.editor.on("saveRequested", (...args) => {
+ this.emit("saveRequested", ...args);
+ });
+
+ this.appended = this.editor.appendTo(this.elt);
+ },
+
+ /**
+ * Clean up the editor. This can have different meanings
+ * depending on the type of editor.
+ */
+ destroy: function () {
+ this.editor.destroy();
+ this.editor = null;
+ },
+
+ /**
+ * Load a single resource into the text editor.
+ *
+ * @param Resource resource
+ * The single file / item that is being dealt with (see stores/base)
+ * @returns Promise
+ * A promise that is resolved once the text editor has loaded the
+ * contents of the resource.
+ */
+ load: function (resource) {
+ // Wait for the editor.appendTo and resource.load before proceeding.
+ // They can run in parallel.
+ return promise.all([
+ resource.load(),
+ this.appended
+ ]).then(([resourceContents])=> {
+ if (!this.editor) {
+ return;
+ }
+ this._savedResourceContents = resourceContents;
+ this.editor.setText(resourceContents);
+ this.editor.clearHistory();
+ this.editor.setClean();
+ this.emit("load");
+ }, console.error);
+ },
+
+ /**
+ * Save the resource based on the current state of the editor
+ *
+ * @param Resource resource
+ * The single file / item to be saved
+ * @returns Promise
+ * A promise that is resolved once the resource has been
+ * saved.
+ */
+ save: function (resource) {
+ let newText = this.editor.getText();
+ return resource.save(newText).then(() => {
+ this._savedResourceContents = newText;
+ this.emit("save", resource);
+ });
+ },
+
+ /**
+ * Give focus to the code editor.
+ *
+ * @returns Promise
+ * A promise that is resolved once the editor has been focused.
+ */
+ focus: function () {
+ return this.appended.then(() => {
+ if (this.editor) {
+ this.editor.focus();
+ }
+ });
+ }
+});
+
+/**
+ * Wrapper for TextEditor using JavaScript syntax highlighting.
+ */
+function JSEditor(host) {
+ return TextEditor(host, Editor.modes.js);
+}
+
+/**
+ * Wrapper for TextEditor using CSS syntax highlighting.
+ */
+function CSSEditor(host) {
+ return TextEditor(host, Editor.modes.css);
+}
+
+/**
+ * Wrapper for TextEditor using HTML syntax highlighting.
+ */
+function HTMLEditor(host) {
+ return TextEditor(host, Editor.modes.html);
+}
+
+/**
+ * Get the type of editor that can handle a particular resource.
+ * @param Resource resource
+ * The single file that is going to be opened.
+ * @returns Type:Editor
+ * The type of editor that can handle this resource. The
+ * return value is a constructor function.
+ */
+function EditorTypeForResource(resource) {
+ const categoryMap = {
+ "txt": TextEditor,
+ "html": HTMLEditor,
+ "xml": HTMLEditor,
+ "css": CSSEditor,
+ "js": JSEditor,
+ "json": JSEditor
+ };
+ return categoryMap[resource.contentCategory] || TextEditor;
+}
+
+exports.TextEditor = TextEditor;
+exports.JSEditor = JSEditor;
+exports.CSSEditor = CSSEditor;
+exports.HTMLEditor = HTMLEditor;
+exports.EditorTypeForResource = EditorTypeForResource;