mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central and mozilla-inbound
This commit is contained in:
commit
00c1c060f7
@ -58,7 +58,7 @@ $(NSINSTALL) -D $(dir) && \
|
||||
$(PYTHON) $(MOZILLA_DIR)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $(srcdir)/$(dir)/install.rdf.in > $(dir)/install.rdf && \
|
||||
cd $(dir) && \
|
||||
$(ZIP) -r9XD $(DISTROEXT)/$(dir).xpi install.rdf && \
|
||||
cd $(srcdir)/$(dir) && \
|
||||
cd $(call core_abspath,$(srcdir)/$(dir)) && \
|
||||
$(ZIP) -r9XD $(DISTROEXT)/$(dir).xpi * -x install.rdf.in
|
||||
|
||||
endef # do not remove the blank line!
|
||||
|
@ -1045,6 +1045,17 @@ pref("devtools.hud.loglimit.console", 200);
|
||||
pref("devtools.editor.tabsize", 4);
|
||||
pref("devtools.editor.expandtab", true);
|
||||
|
||||
// Tells which component you want to use for source editing in developer tools.
|
||||
//
|
||||
// Available components:
|
||||
// "textarea" - this is a basic text editor, like an HTML <textarea>.
|
||||
//
|
||||
// "orion" - this is the Orion source code editor from the Eclipse project. It
|
||||
// provides programmer-specific editor features such as syntax highlighting,
|
||||
// indenting and bracket recognition. It may not be appropriate for all
|
||||
// locales (esp. RTL) or a11y situations.
|
||||
pref("devtools.editor.component", "textarea");
|
||||
|
||||
// Whether the character encoding menu is under the main Firefox button. This
|
||||
// preference is a string so that localizers can alter it.
|
||||
pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
|
||||
|
@ -48,6 +48,7 @@ include $(topsrcdir)/config/config.mk
|
||||
DIRS = \
|
||||
webconsole \
|
||||
scratchpad \
|
||||
sourceeditor \
|
||||
$(NULL)
|
||||
|
||||
ifdef ENABLE_TESTS
|
||||
|
@ -2,3 +2,5 @@ browser.jar:
|
||||
content/browser/NetworkPanel.xhtml (webconsole/NetworkPanel.xhtml)
|
||||
* content/browser/scratchpad.xul (scratchpad/scratchpad.xul)
|
||||
* content/browser/scratchpad.js (scratchpad/scratchpad.js)
|
||||
content/browser/orion.js (sourceeditor/orion/orion.js)
|
||||
content/browser/orion.css (sourceeditor/orion/orion.css)
|
||||
|
@ -48,6 +48,8 @@
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=653934
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
@ -56,6 +58,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource:///modules/PropertyPanel.jsm");
|
||||
Cu.import("resource:///modules/source-editor.jsm");
|
||||
|
||||
const SCRATCHPAD_CONTEXT_CONTENT = 1;
|
||||
const SCRATCHPAD_CONTEXT_BROWSER = 2;
|
||||
@ -64,9 +67,6 @@ const SCRATCHPAD_L10N = "chrome://browser/locale/scratchpad.properties";
|
||||
const SCRATCHPAD_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
|
||||
const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
|
||||
|
||||
const PREF_TABSIZE = "devtools.editor.tabsize";
|
||||
const PREF_EXPANDTAB = "devtools.editor.expandtab";
|
||||
|
||||
/**
|
||||
* The scratchpad object handles the Scratchpad window functionality.
|
||||
*/
|
||||
@ -83,12 +83,6 @@ var Scratchpad = {
|
||||
*/
|
||||
executionContext: SCRATCHPAD_CONTEXT_CONTENT,
|
||||
|
||||
/**
|
||||
* Retrieve the xul:textbox DOM element. This element holds the source code
|
||||
* the user writes and executes.
|
||||
*/
|
||||
get textbox() document.getElementById("scratchpad-textbox"),
|
||||
|
||||
/**
|
||||
* Retrieve the xul:statusbarpanel DOM element. The status bar tells the
|
||||
* current code execution context.
|
||||
@ -96,12 +90,46 @@ var Scratchpad = {
|
||||
get statusbarStatus() document.getElementById("scratchpad-status"),
|
||||
|
||||
/**
|
||||
* Get the selected text from the textbox.
|
||||
* Get the selected text from the editor.
|
||||
*
|
||||
* @return string
|
||||
* The selected text.
|
||||
*/
|
||||
get selectedText()
|
||||
get selectedText() this.editor.getSelectedText(),
|
||||
|
||||
/**
|
||||
* Get the editor content, in the given range. If no range is given you get
|
||||
* the entire editor content.
|
||||
*
|
||||
* @param number [aStart=0]
|
||||
* Optional, start from the given offset.
|
||||
* @param number [aEnd=content char count]
|
||||
* Optional, end offset for the text you want. If this parameter is not
|
||||
* given, then the text returned goes until the end of the editor
|
||||
* content.
|
||||
* @return string
|
||||
* The text in the given range.
|
||||
*/
|
||||
getText: function SP_getText(aStart, aEnd)
|
||||
{
|
||||
return this.textbox.value.substring(this.textbox.selectionStart,
|
||||
this.textbox.selectionEnd);
|
||||
return this.editor.getText(aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace text in the source editor with the given text, in the given range.
|
||||
*
|
||||
* @param string aText
|
||||
* The text you want to put into the editor.
|
||||
* @param number [aStart=0]
|
||||
* Optional, the start offset, zero based, from where you want to start
|
||||
* replacing text in the editor.
|
||||
* @param number [aEnd=char count]
|
||||
* Optional, the end offset, zero based, where you want to stop
|
||||
* replacing text in the editor.
|
||||
*/
|
||||
setText: function SP_setText(aText, aStart, aEnd)
|
||||
{
|
||||
this.editor.setText(aText, aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -124,11 +152,6 @@ var Scratchpad = {
|
||||
return recentWin ? recentWin.gBrowser : null;
|
||||
},
|
||||
|
||||
insertIntro: function SP_insertIntro()
|
||||
{
|
||||
this.textbox.value = this.strings.GetStringFromName("scratchpadIntro");
|
||||
},
|
||||
|
||||
/**
|
||||
* Cached Cu.Sandbox object for the active tab content window object.
|
||||
*/
|
||||
@ -197,15 +220,15 @@ var Scratchpad = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Drop the textbox selection.
|
||||
* Drop the editor selection.
|
||||
*/
|
||||
deselect: function SP_deselect()
|
||||
{
|
||||
this.textbox.selectionEnd = this.textbox.selectionStart;
|
||||
this.editor.dropSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a specific range in the Scratchpad xul:textbox.
|
||||
* Select a specific range in the Scratchpad editor.
|
||||
*
|
||||
* @param number aStart
|
||||
* Selection range start.
|
||||
@ -214,8 +237,19 @@ var Scratchpad = {
|
||||
*/
|
||||
selectRange: function SP_selectRange(aStart, aEnd)
|
||||
{
|
||||
this.textbox.selectionStart = aStart;
|
||||
this.textbox.selectionEnd = aEnd;
|
||||
this.editor.setSelection(aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current selection range.
|
||||
*
|
||||
* @return object
|
||||
* An object with two properties, start and end, that give the
|
||||
* selection range (zero based offsets).
|
||||
*/
|
||||
getSelectionRange: function SP_getSelection()
|
||||
{
|
||||
return this.editor.getSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -293,19 +327,19 @@ var Scratchpad = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute the selected text (if any) or the entire textbox content in the
|
||||
* Execute the selected text (if any) or the entire editor content in the
|
||||
* current context.
|
||||
*/
|
||||
run: function SP_run()
|
||||
{
|
||||
let selection = this.selectedText || this.textbox.value;
|
||||
let selection = this.selectedText || this.getText();
|
||||
let result = this.evalForContext(selection);
|
||||
this.deselect();
|
||||
return [selection, result];
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute the selected text (if any) or the entire textbox content in the
|
||||
* Execute the selected text (if any) or the entire editor content in the
|
||||
* current context. The resulting object is opened up in the Property Panel
|
||||
* for inspection.
|
||||
*/
|
||||
@ -319,34 +353,29 @@ var Scratchpad = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute the selected text (if any) or the entire textbox content in the
|
||||
* current context. The evaluation result is inserted into the textbox after
|
||||
* the selected text, or at the end of the textbox value if there is no
|
||||
* Execute the selected text (if any) or the entire editor content in the
|
||||
* current context. The evaluation result is inserted into the editor after
|
||||
* the selected text, or at the end of the editor content if there is no
|
||||
* selected text.
|
||||
*/
|
||||
display: function SP_display()
|
||||
{
|
||||
let selectionStart = this.textbox.selectionStart;
|
||||
let selectionEnd = this.textbox.selectionEnd;
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionEnd = this.textbox.value.length;
|
||||
}
|
||||
let selection = this.getSelectionRange();
|
||||
let insertionPoint = selection.start != selection.end ?
|
||||
selection.end : // after selected text
|
||||
this.editor.getCharCount(); // after text end
|
||||
|
||||
let [selection, result] = this.run();
|
||||
let [selectedText, result] = this.run();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
let firstPiece = this.textbox.value.slice(0, selectionEnd);
|
||||
let lastPiece = this.textbox.value.
|
||||
slice(selectionEnd, this.textbox.value.length);
|
||||
|
||||
let newComment = "/*\n" + result.toString() + "\n*/";
|
||||
|
||||
this.textbox.value = firstPiece + newComment + lastPiece;
|
||||
this.setText(newComment, insertionPoint, insertionPoint);
|
||||
|
||||
// Select the added comment.
|
||||
this.selectRange(firstPiece.length, firstPiece.length + newComment.length);
|
||||
// Select the new comment.
|
||||
this.selectRange(insertionPoint, insertionPoint + newComment.length);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -442,12 +471,12 @@ var Scratchpad = {
|
||||
let fs = Cc["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Ci.nsIFileOutputStream);
|
||||
let modeFlags = 0x02 | 0x08 | 0x20;
|
||||
fs.init(aFile, modeFlags, 0644, fs.DEFER_OPEN);
|
||||
fs.init(aFile, modeFlags, 420 /* 0644 */, fs.DEFER_OPEN);
|
||||
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let input = converter.convertToInputStream(this.textbox.value);
|
||||
let input = converter.convertToInputStream(this.getText());
|
||||
|
||||
let self = this;
|
||||
NetUtil.asyncCopy(input, fs, function(aStatus) {
|
||||
@ -488,7 +517,7 @@ var Scratchpad = {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
content = NetUtil.readInputStreamToString(aInputStream,
|
||||
aInputStream.available());
|
||||
self.textbox.value = content;
|
||||
self.setText(content);
|
||||
}
|
||||
else if (!aSilentError) {
|
||||
window.alert(self.strings.GetStringFromName("openFile.failed"));
|
||||
@ -615,10 +644,17 @@ var Scratchpad = {
|
||||
},
|
||||
|
||||
/**
|
||||
* The Scratchpad window DOMContentLoaded event handler.
|
||||
* The Scratchpad window DOMContentLoaded event handler. This method
|
||||
* initializes the Scratchpad window and source editor.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
*/
|
||||
onLoad: function SP_onLoad()
|
||||
onLoad: function SP_onLoad(aEvent)
|
||||
{
|
||||
if (aEvent.target != document) {
|
||||
return;
|
||||
}
|
||||
|
||||
let chromeContextMenu = document.getElementById("sp-menu-browser");
|
||||
let errorConsoleMenu = document.getElementById("sp-menu-errorConsole");
|
||||
let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
|
||||
@ -632,55 +668,107 @@ var Scratchpad = {
|
||||
chromeContextCommand.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
let tabsize = Services.prefs.getIntPref(PREF_TABSIZE);
|
||||
if (tabsize < 1) {
|
||||
// tabsize is invalid, clear back to the default value.
|
||||
Services.prefs.clearUserPref(PREF_TABSIZE);
|
||||
tabsize = Services.prefs.getIntPref(PREF_TABSIZE);
|
||||
}
|
||||
this.editor = new SourceEditor();
|
||||
|
||||
let expandtab = Services.prefs.getBoolPref(PREF_EXPANDTAB);
|
||||
this._tabCharacter = expandtab ? (new Array(tabsize + 1)).join(" ") : "\t";
|
||||
this.textbox.style.MozTabSize = tabsize;
|
||||
let config = {
|
||||
mode: SourceEditor.MODES.JAVASCRIPT,
|
||||
showLineNumbers: true,
|
||||
placeholderText: this.strings.GetStringFromName("scratchpadIntro"),
|
||||
};
|
||||
|
||||
// Force LTR direction (otherwise the textbox inherits the locale direction)
|
||||
this.textbox.style.direction = "ltr";
|
||||
|
||||
this.insertIntro();
|
||||
|
||||
// Make the Tab key work.
|
||||
this.textbox.addEventListener("keypress", this.onKeypress.bind(this), false);
|
||||
|
||||
this.textbox.focus();
|
||||
let editorPlaceholder = document.getElementById("scratchpad-editor");
|
||||
this.editor.init(editorPlaceholder, config, this.onEditorLoad.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* The textbox keypress event handler which allows users to indent code using
|
||||
* the Tab key.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
* The load event handler for the source editor. This method does post-load
|
||||
* editor initialization.
|
||||
*/
|
||||
onKeypress: function SP_onKeypress(aEvent)
|
||||
onEditorLoad: function SP_onEditorLoad()
|
||||
{
|
||||
if (aEvent.keyCode == aEvent.DOM_VK_TAB) {
|
||||
this.insertTextAtCaret(this._tabCharacter);
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
this.editor.addEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
|
||||
this.onContextMenu);
|
||||
this.editor.focus();
|
||||
this.editor.setCaretOffset(this.editor.getCharCount());
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert text at the current caret location.
|
||||
*
|
||||
* @param string aText
|
||||
* The text you want to insert.
|
||||
*/
|
||||
insertTextAtCaret: function SP_insertTextAtCaret(aText)
|
||||
{
|
||||
let firstPiece = this.textbox.value.substring(0, this.textbox.selectionStart);
|
||||
let lastPiece = this.textbox.value.substring(this.textbox.selectionEnd);
|
||||
this.textbox.value = firstPiece + aText + lastPiece;
|
||||
let caretOffset = this.editor.getCaretOffset();
|
||||
this.setText(aText, caretOffset, caretOffset);
|
||||
this.editor.setCaretOffset(caretOffset + aText.length);
|
||||
},
|
||||
|
||||
let newCaretPosition = firstPiece.length + aText.length;
|
||||
this.selectRange(newCaretPosition, newCaretPosition);
|
||||
/**
|
||||
* The contextmenu event handler for the source editor. This method opens the
|
||||
* Scratchpad context menu popup at the pointer location.
|
||||
*
|
||||
* @param object aEvent
|
||||
* An event object coming from the SourceEditor. This object needs to
|
||||
* hold the screenX and screenY properties.
|
||||
*/
|
||||
onContextMenu: function SP_onContextMenu(aEvent)
|
||||
{
|
||||
let menu = document.getElementById("scratchpad-text-popup");
|
||||
if (menu.state == "closed") {
|
||||
menu.openPopupAtScreen(aEvent.screenX, aEvent.screenY, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The popupshowing event handler for the Edit menu. This method updates the
|
||||
* enabled/disabled state of the Undo and Redo commands, based on the editor
|
||||
* state such that the menu items render correctly for the user when the menu
|
||||
* shows.
|
||||
*/
|
||||
onEditPopupShowing: function SP_onEditPopupShowing()
|
||||
{
|
||||
let undo = document.getElementById("sp-cmd-undo");
|
||||
undo.setAttribute("disabled", !this.editor.canUndo());
|
||||
|
||||
let redo = document.getElementById("sp-cmd-redo");
|
||||
redo.setAttribute("disabled", !this.editor.canRedo());
|
||||
},
|
||||
|
||||
/**
|
||||
* Undo the last action of the user.
|
||||
*/
|
||||
undo: function SP_undo()
|
||||
{
|
||||
this.editor.undo();
|
||||
},
|
||||
|
||||
/**
|
||||
* Redo the previously undone action.
|
||||
*/
|
||||
redo: function SP_redo()
|
||||
{
|
||||
this.editor.redo();
|
||||
},
|
||||
|
||||
/**
|
||||
* The Scratchpad window unload event handler. This method unloads/destroys
|
||||
* the source editor.
|
||||
*
|
||||
* @param nsIDOMEvent aEvent
|
||||
*/
|
||||
onUnload: function SP_onUnload(aEvent)
|
||||
{
|
||||
if (aEvent.target != document) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetContext();
|
||||
this.editor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
|
||||
this.onContextMenu);
|
||||
this.editor.destroy();
|
||||
this.editor = null;
|
||||
},
|
||||
};
|
||||
|
||||
@ -689,4 +777,4 @@ XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
|
||||
});
|
||||
|
||||
addEventListener("DOMContentLoaded", Scratchpad.onLoad.bind(Scratchpad), false);
|
||||
|
||||
addEventListener("unload", Scratchpad.onUnload.bind(Scratchpad), false);
|
||||
|
@ -78,6 +78,8 @@
|
||||
<command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/>
|
||||
<command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
|
||||
<command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
|
||||
<command id="sp-cmd-undo" oncommand="Scratchpad.undo();" disabled="true"/>
|
||||
<command id="sp-cmd-redo" oncommand="Scratchpad.redo();" disabled="true"/>
|
||||
</commandset>
|
||||
|
||||
<keyset id="sp-keyset">
|
||||
@ -116,8 +118,10 @@
|
||||
key="&pasteCmd.key;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
|
||||
<key id="key_undo" key="&undoCmd.key;" modifiers="accel"/>
|
||||
<key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
|
||||
<key id="key_undo" key="&undoCmd.key;" modifiers="accel"
|
||||
oncommand="Scratchpad.undo();"/>
|
||||
<key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"
|
||||
oncommand="Scratchpad.redo();"/>
|
||||
<key id="sp-key-run"
|
||||
key="&run.key;"
|
||||
command="sp-cmd-run"
|
||||
@ -185,19 +189,18 @@
|
||||
|
||||
<menu id="sp-edit-menu" label="&editMenu.label;"
|
||||
accesskey="&editMenu.accesskey;">
|
||||
<menupopup id="sp-menu_editpopup">
|
||||
<menupopup id="sp-menu_editpopup"
|
||||
onpopupshowing="Scratchpad.onEditPopupShowing()">
|
||||
<menuitem id="sp-menu-undo"
|
||||
label="&undoCmd.label;"
|
||||
key="key_undo"
|
||||
accesskey="&undoCmd.accesskey;"
|
||||
disabled="true"
|
||||
command="cmd_undo"/>
|
||||
command="sp-cmd-undo"/>
|
||||
<menuitem id="sp-menu-redo"
|
||||
label="&redoCmd.label;"
|
||||
key="key_redo"
|
||||
disabled="true"
|
||||
accesskey="&redoCmd.accesskey;"
|
||||
command="cmd_redo"/>
|
||||
command="sp-cmd-redo"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="sp-menu-cut"
|
||||
label="&cutCmd.label;"
|
||||
@ -329,12 +332,8 @@
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<textbox id="scratchpad-textbox"
|
||||
class="monospace"
|
||||
multiline="true"
|
||||
flex="1"
|
||||
context="scratchpad-text-popup"
|
||||
placeholder="&textbox.placeholder1;" />
|
||||
<hbox id="scratchpad-editor" flex="1" context="scratchpad-text-popup" />
|
||||
|
||||
<statusbar id="scratchpad-statusbar" align="end">
|
||||
<statusbarpanel id="scratchpad-status"
|
||||
label="&contentContext.label;"
|
||||
|
@ -31,34 +31,33 @@ function runTests()
|
||||
let sp = gScratchpadWindow.Scratchpad;
|
||||
ok(sp, "Scratchpad object exists in new window");
|
||||
|
||||
is(gScratchpadWindow.document.activeElement, sp.textbox.inputField,
|
||||
"The textbox has focus");
|
||||
ok(sp.editor.hasFocus(), "the editor has focus");
|
||||
|
||||
is(sp.textbox.style.MozTabSize, 5, "-moz-tab-size is correct");
|
||||
|
||||
sp.textbox.value = "window.foo;";
|
||||
sp.selectRange(1, 3);
|
||||
sp.setText("window.foo;");
|
||||
sp.editor.setCaretOffset(0);
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
|
||||
|
||||
is(sp.textbox.value, "w dow.foo;",
|
||||
"Tab key added 5 spaces");
|
||||
is(sp.getText(), " window.foo;", "Tab key added 5 spaces");
|
||||
|
||||
is(sp.textbox.selectionStart, 6, "caret location is correct");
|
||||
is(sp.editor.getCaretOffset(), 5, "caret location is correct");
|
||||
|
||||
is(sp.textbox.selectionStart, sp.textbox.selectionEnd,
|
||||
"caret location is correct, confirmed");
|
||||
sp.editor.setCaretOffset(6);
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
|
||||
|
||||
is(sp.getText(), " w indow.foo;",
|
||||
"Tab key added 4 spaces");
|
||||
|
||||
is(sp.editor.getCaretOffset(), 10, "caret location is correct");
|
||||
|
||||
// Test the new insertTextAtCaret() method.
|
||||
|
||||
sp.insertTextAtCaret("omg");
|
||||
|
||||
is(sp.textbox.value, "w omgdow.foo;", "insertTextAtCaret() works");
|
||||
is(sp.getText(), " w omgindow.foo;", "insertTextAtCaret() works");
|
||||
|
||||
is(sp.textbox.selectionStart, 9, "caret location is correct after update");
|
||||
|
||||
is(sp.textbox.selectionStart, sp.textbox.selectionEnd,
|
||||
"caret location is correct, confirmed");
|
||||
is(sp.editor.getCaretOffset(), 13, "caret location is correct after update");
|
||||
|
||||
gScratchpadWindow.close();
|
||||
|
||||
@ -74,36 +73,14 @@ function runTests2()
|
||||
|
||||
let sp = gScratchpadWindow.Scratchpad;
|
||||
|
||||
is(sp.textbox.style.MozTabSize, 6, "-moz-tab-size is correct");
|
||||
|
||||
sp.textbox.value = "window.foo;";
|
||||
sp.selectRange(1, 3);
|
||||
sp.setText("window.foo;");
|
||||
sp.editor.setCaretOffset(0);
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
|
||||
|
||||
is(sp.textbox.value, "w\tdow.foo;", "Tab key added the tab character");
|
||||
is(sp.getText(), "\twindow.foo;", "Tab key added the tab character");
|
||||
|
||||
is(sp.textbox.selectionStart, 2, "caret location is correct");
|
||||
|
||||
is(sp.textbox.selectionStart, sp.textbox.selectionEnd,
|
||||
"caret location is correct, confirmed");
|
||||
|
||||
gScratchpadWindow.close();
|
||||
|
||||
// check with an invalid tabsize value.
|
||||
Services.prefs.setIntPref("devtools.editor.tabsize", 0);
|
||||
Services.prefs.setBoolPref("devtools.editor.expandtab", true);
|
||||
gScratchpadWindow = Scratchpad.openScratchpad();
|
||||
gScratchpadWindow.addEventListener("load", runTests3, false);
|
||||
}
|
||||
|
||||
function runTests3()
|
||||
{
|
||||
gScratchpadWindow.removeEventListener("load", arguments.callee, false);
|
||||
|
||||
let sp = gScratchpadWindow.Scratchpad;
|
||||
|
||||
is(sp.textbox.style.MozTabSize, 4, "-moz-tab-size is correct");
|
||||
is(sp.editor.getCaretOffset(), 1, "caret location is correct");
|
||||
|
||||
Services.prefs.clearUserPref("devtools.editor.tabsize");
|
||||
Services.prefs.clearUserPref("devtools.editor.expandtab");
|
||||
|
@ -48,8 +48,7 @@ function runTests()
|
||||
is(statusbar.getAttribute("label"), contentMenu.getAttribute("label"),
|
||||
"statusbar label is correct");
|
||||
|
||||
ok(sp.textbox, "textbox exists");
|
||||
sp.textbox.value = "window.foobarBug636725 = 'aloha';";
|
||||
sp.setText("window.foobarBug636725 = 'aloha';");
|
||||
|
||||
ok(!content.wrappedJSObject.foobarBug636725,
|
||||
"no content.foobarBug636725");
|
||||
@ -73,7 +72,10 @@ function runTests()
|
||||
is(statusbar.getAttribute("label"), chromeMenu.getAttribute("label"),
|
||||
"statusbar label is correct");
|
||||
|
||||
sp.textbox.value = "window.foobarBug636725 = 'aloha2';";
|
||||
sp.setText("2'", 31, 33);
|
||||
|
||||
ok(sp.getText(), "window.foobarBug636725 = 'aloha2';",
|
||||
"setText() worked");
|
||||
|
||||
ok(!window.foobarBug636725, "no window.foobarBug636725");
|
||||
|
||||
@ -81,20 +83,23 @@ function runTests()
|
||||
|
||||
is(window.foobarBug636725, "aloha2", "window.foobarBug636725 has been set");
|
||||
|
||||
sp.textbox.value = "window.gBrowser";
|
||||
sp.setText("gBrowser", 7);
|
||||
|
||||
ok(sp.getText(), "window.gBrowser",
|
||||
"setText() worked with no end for the replace range");
|
||||
|
||||
is(typeof sp.run()[1].addTab, "function",
|
||||
"chrome context has access to chrome objects");
|
||||
|
||||
// Check that the sandbox is cached.
|
||||
|
||||
sp.textbox.value = "typeof foobarBug636725cache;";
|
||||
sp.setText("typeof foobarBug636725cache;");
|
||||
is(sp.run()[1], "undefined", "global variable does not exist");
|
||||
|
||||
sp.textbox.value = "var foobarBug636725cache = 'foo';";
|
||||
sp.setText("var foobarBug636725cache = 'foo';");
|
||||
sp.run();
|
||||
|
||||
sp.textbox.value = "typeof foobarBug636725cache;";
|
||||
sp.setText("typeof foobarBug636725cache;");
|
||||
is(sp.run()[1], "string",
|
||||
"global variable exists across two different executions");
|
||||
|
||||
@ -103,10 +108,10 @@ function runTests()
|
||||
is(sp.run()[1], "undefined",
|
||||
"global variable no longer exists after calling resetContext()");
|
||||
|
||||
sp.textbox.value = "var foobarBug636725cache2 = 'foo';";
|
||||
sp.setText("var foobarBug636725cache2 = 'foo';");
|
||||
sp.run();
|
||||
|
||||
sp.textbox.value = "typeof foobarBug636725cache2;";
|
||||
sp.setText("typeof foobarBug636725cache2;");
|
||||
is(sp.run()[1], "string",
|
||||
"global variable exists across two different executions");
|
||||
|
||||
|
@ -28,85 +28,103 @@ function runTests()
|
||||
|
||||
content.wrappedJSObject.foobarBug636725 = 1;
|
||||
|
||||
ok(sp.textbox, "textbox exists");
|
||||
sp.textbox.value = "++window.foobarBug636725";
|
||||
sp.setText("++window.foobarBug636725");
|
||||
|
||||
let exec = sp.run();
|
||||
is(exec[0], sp.textbox.value, "execute()[0] is correct");
|
||||
is(exec[0], sp.getText(), "run()[0] is correct");
|
||||
is(exec[1], content.wrappedJSObject.foobarBug636725,
|
||||
"execute()[1] is correct");
|
||||
"run()[1] is correct");
|
||||
|
||||
is(sp.textbox.value, "++window.foobarBug636725",
|
||||
"execute() does not change the textbox value");
|
||||
is(sp.getText(), "++window.foobarBug636725",
|
||||
"run() does not change the editor content");
|
||||
|
||||
is(content.wrappedJSObject.foobarBug636725, 2,
|
||||
"execute() updated window.foobarBug636725");
|
||||
"run() updated window.foobarBug636725");
|
||||
|
||||
sp.display();
|
||||
|
||||
is(content.wrappedJSObject.foobarBug636725, 3,
|
||||
"print() updated window.foobarBug636725");
|
||||
"display() updated window.foobarBug636725");
|
||||
|
||||
is(sp.textbox.value, "++window.foobarBug636725/*\n3\n*/",
|
||||
"print() shows evaluation result in the textbox");
|
||||
is(sp.getText(), "++window.foobarBug636725/*\n3\n*/",
|
||||
"display() shows evaluation result in the textbox");
|
||||
|
||||
is(sp.selectedText, "/*\n3\n*/", "selectedText is correct");
|
||||
is(sp.textbox.selectionStart, 24, "selectionStart is correct");
|
||||
is(sp.textbox.selectionEnd, 31, "selectionEnd is correct");
|
||||
let selection = sp.getSelectionRange();
|
||||
is(selection.start, 24, "selection.start is correct");
|
||||
is(selection.end, 31, "selection.end is correct");
|
||||
|
||||
// Test selection execute() and print().
|
||||
// Test selection run() and display().
|
||||
|
||||
sp.textbox.value = "window.foobarBug636725 = 'a';\n" +
|
||||
"window.foobarBug636725 = 'b';";
|
||||
sp.setText("window.foobarBug636725 = 'a';\n" +
|
||||
"window.foobarBug636725 = 'b';");
|
||||
|
||||
sp.selectRange(1, 2);
|
||||
|
||||
is(sp.textbox.selectionStart, 1, "selectionStart is 1");
|
||||
is(sp.textbox.selectionEnd, 2, "selectionEnd is 2");
|
||||
selection = sp.getSelectionRange();
|
||||
|
||||
is(selection.start, 1, "selection.start is 1");
|
||||
is(selection.end, 2, "selection.end is 2");
|
||||
|
||||
sp.selectRange(0, 29);
|
||||
|
||||
is(sp.textbox.selectionStart, 0, "selectionStart is 0");
|
||||
is(sp.textbox.selectionEnd, 29, "selectionEnd is 29");
|
||||
selection = sp.getSelectionRange();
|
||||
|
||||
is(selection.start, 0, "selection.start is 0");
|
||||
is(selection.end, 29, "selection.end is 29");
|
||||
|
||||
exec = sp.run();
|
||||
|
||||
is(exec[0], "window.foobarBug636725 = 'a';",
|
||||
"execute()[0] is correct");
|
||||
"run()[0] is correct");
|
||||
is(exec[1], "a",
|
||||
"execute()[1] is correct");
|
||||
"run()[1] is correct");
|
||||
|
||||
is(sp.textbox.value, "window.foobarBug636725 = 'a';\n" +
|
||||
"window.foobarBug636725 = 'b';",
|
||||
"execute() does not change the textbox value");
|
||||
is(sp.getText(), "window.foobarBug636725 = 'a';\n" +
|
||||
"window.foobarBug636725 = 'b';",
|
||||
"run() does not change the textbox value");
|
||||
|
||||
is(content.wrappedJSObject.foobarBug636725, "a",
|
||||
"execute() worked for the selected range");
|
||||
"run() worked for the selected range");
|
||||
|
||||
sp.textbox.value = "window.foobarBug636725 = 'c';\n" +
|
||||
"window.foobarBug636725 = 'b';";
|
||||
sp.setText("window.foobarBug636725 = 'c';\n" +
|
||||
"window.foobarBug636725 = 'b';");
|
||||
|
||||
sp.selectRange(0, 22);
|
||||
|
||||
sp.display();
|
||||
|
||||
is(content.wrappedJSObject.foobarBug636725, "a",
|
||||
"print() worked for the selected range");
|
||||
"display() worked for the selected range");
|
||||
|
||||
is(sp.textbox.value, "window.foobarBug636725" +
|
||||
"/*\na\n*/" +
|
||||
" = 'c';\n" +
|
||||
"window.foobarBug636725 = 'b';",
|
||||
"print() shows evaluation result in the textbox");
|
||||
is(sp.getText(), "window.foobarBug636725" +
|
||||
"/*\na\n*/" +
|
||||
" = 'c';\n" +
|
||||
"window.foobarBug636725 = 'b';",
|
||||
"display() shows evaluation result in the textbox");
|
||||
|
||||
is(sp.selectedText, "/*\na\n*/", "selectedText is correct");
|
||||
is(sp.textbox.selectionStart, 22, "selectionStart is correct");
|
||||
is(sp.textbox.selectionEnd, 29, "selectionEnd is correct");
|
||||
|
||||
selection = sp.getSelectionRange();
|
||||
is(selection.start, 22, "selection.start is correct");
|
||||
is(selection.end, 29, "selection.end is correct");
|
||||
|
||||
sp.deselect();
|
||||
|
||||
ok(!sp.selectedText, "selectedText is empty");
|
||||
is(sp.textbox.selectionStart, sp.textbox.selectionEnd, "deselect() works");
|
||||
|
||||
selection = sp.getSelectionRange();
|
||||
is(selection.start, selection.end, "deselect() works");
|
||||
|
||||
// Test undo/redo.
|
||||
|
||||
sp.setText("foo1");
|
||||
sp.setText("foo2");
|
||||
is(sp.getText(), "foo2", "editor content updated");
|
||||
sp.undo();
|
||||
is(sp.getText(), "foo1", "undo() works");
|
||||
sp.redo();
|
||||
is(sp.getText(), "foo2", "redo() works");
|
||||
|
||||
gScratchpadWindow.close();
|
||||
gScratchpadWindow = null;
|
||||
|
@ -74,12 +74,12 @@ function fileImported(aStatus, aFileContent)
|
||||
is(aFileContent, gFileContent,
|
||||
"received data is correct");
|
||||
|
||||
is(gScratchpad.textbox.value, gFileContent,
|
||||
"the textbox.value is correct");
|
||||
is(gScratchpad.getText(), gFileContent,
|
||||
"the editor content is correct");
|
||||
|
||||
// Save the file after changes.
|
||||
gFileContent += "// omg, saved!";
|
||||
gScratchpad.textbox.value = gFileContent;
|
||||
gScratchpad.setText(gFileContent);
|
||||
|
||||
gScratchpad.exportToFile(gFile.QueryInterface(Ci.nsILocalFile), true, true,
|
||||
fileExported);
|
||||
@ -94,7 +94,7 @@ function fileExported(aStatus)
|
||||
|
||||
// Attempt another file save, with confirmation which returns false.
|
||||
gFileContent += "// omg, saved twice!";
|
||||
gScratchpad.textbox.value = gFileContent;
|
||||
gScratchpad.setText(gFileContent);
|
||||
|
||||
let oldConfirm = gScratchpadWindow.confirm;
|
||||
let askedConfirmation = false;
|
||||
|
@ -27,8 +27,7 @@ function runTests()
|
||||
|
||||
let sp = gScratchpadWindow.Scratchpad;
|
||||
|
||||
ok(sp.textbox, "textbox exists");
|
||||
sp.textbox.value = "document";
|
||||
sp.setText("document");
|
||||
|
||||
sp.inspect();
|
||||
|
||||
|
@ -58,8 +58,7 @@ function runTests()
|
||||
is(statusbar.getAttribute("label"), contentMenu.getAttribute("label"),
|
||||
"statusbar label is correct");
|
||||
|
||||
ok(sp.textbox, "textbox exists");
|
||||
sp.textbox.value = "window.foosbug653108 = 'aloha';";
|
||||
sp.setText("window.foosbug653108 = 'aloha';");
|
||||
|
||||
ok(!content.wrappedJSObject.foosbug653108,
|
||||
"no content.foosbug653108");
|
||||
@ -78,12 +77,12 @@ function runTests2() {
|
||||
|
||||
ok(!window.foosbug653108, "no window.foosbug653108");
|
||||
|
||||
sp.textbox.value = "window.foosbug653108";
|
||||
sp.setText("window.foosbug653108");
|
||||
let result = sp.run();
|
||||
|
||||
isnot(result, "aloha", "window.foosbug653108 is not aloha");
|
||||
|
||||
sp.textbox.value = "window.foosbug653108 = 'ahoyhoy';";
|
||||
sp.setText("window.foosbug653108 = 'ahoyhoy';");
|
||||
sp.run();
|
||||
|
||||
is(content.wrappedJSObject.foosbug653108, "ahoyhoy",
|
||||
@ -97,7 +96,7 @@ function runTests3() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", runTests3, true);
|
||||
// Check that the sandbox is not cached.
|
||||
|
||||
sp.textbox.value = "typeof foosbug653108;";
|
||||
sp.setText("typeof foosbug653108;");
|
||||
is(sp.run()[1], "undefined", "global variable does not exist");
|
||||
|
||||
gScratchpadWindow.close();
|
||||
|
@ -41,6 +41,8 @@ function runTests()
|
||||
"sp-menu-resetContext": "resetContext",
|
||||
"sp-menu-errorConsole": "openErrorConsole",
|
||||
"sp-menu-webConsole": "openWebConsole",
|
||||
"sp-menu-undo": "undo",
|
||||
"sp-menu-redo": "redo",
|
||||
};
|
||||
|
||||
let lastMethodCalled = null;
|
||||
|
57
browser/devtools/sourceeditor/Makefile.in
Normal file
57
browser/devtools/sourceeditor/Makefile.in
Normal file
@ -0,0 +1,57 @@
|
||||
#
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is Source Editor.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
#
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Rob Campbell <rcampbell@mozilla.com>
|
||||
# Mihai Sucan <mihai.sucan@gmail.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
DEPTH = ../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
ifdef ENABLE_TESTS
|
||||
DIRS += test
|
||||
endif
|
||||
|
||||
EXTRA_JS_MODULES = \
|
||||
source-editor.jsm \
|
||||
source-editor-orion.jsm \
|
||||
source-editor-textarea.jsm \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
29
browser/devtools/sourceeditor/orion/LICENSE
Normal file
29
browser/devtools/sourceeditor/orion/LICENSE
Normal file
@ -0,0 +1,29 @@
|
||||
Eclipse Distribution License - v 1.0
|
||||
|
||||
Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
* Neither the name of the Eclipse Foundation, Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
81
browser/devtools/sourceeditor/orion/Makefile.dryice.js
Executable file
81
browser/devtools/sourceeditor/orion/Makefile.dryice.js
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env node
|
||||
/* vim:set ts=2 sw=2 sts=2 et tw=80:
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is the Source Editor component.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* The Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Mihai Sucan <mihai.sucan@gmail.com> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK *****/
|
||||
|
||||
var copy = require('dryice').copy;
|
||||
|
||||
const ORION_EDITOR = "org.eclipse.orion.client.editor/web";
|
||||
|
||||
var js_src = copy.createDataObject();
|
||||
|
||||
copy({
|
||||
source: [
|
||||
ORION_EDITOR + "/orion/textview/keyBinding.js",
|
||||
ORION_EDITOR + "/orion/textview/rulers.js",
|
||||
ORION_EDITOR + "/orion/textview/undoStack.js",
|
||||
ORION_EDITOR + "/orion/textview/textModel.js",
|
||||
ORION_EDITOR + "/orion/textview/textView.js",
|
||||
ORION_EDITOR + "/orion/editor/htmlGrammar.js",
|
||||
ORION_EDITOR + "/orion/editor/textMateStyler.js",
|
||||
ORION_EDITOR + "/examples/textview/textStyler.js",
|
||||
],
|
||||
dest: js_src,
|
||||
});
|
||||
|
||||
copy({
|
||||
source: js_src,
|
||||
dest: "orion.js",
|
||||
});
|
||||
|
||||
var css_src = copy.createDataObject();
|
||||
|
||||
copy({
|
||||
source: [
|
||||
ORION_EDITOR + "/orion/textview/textview.css",
|
||||
ORION_EDITOR + "/orion/textview/rulers.css",
|
||||
ORION_EDITOR + "/examples/textview/textstyler.css",
|
||||
ORION_EDITOR + "/examples/editor/htmlStyles.css",
|
||||
],
|
||||
dest: css_src,
|
||||
});
|
||||
|
||||
copy({
|
||||
source: css_src,
|
||||
dest: "orion.css",
|
||||
});
|
||||
|
20
browser/devtools/sourceeditor/orion/README
Normal file
20
browser/devtools/sourceeditor/orion/README
Normal file
@ -0,0 +1,20 @@
|
||||
# Introduction
|
||||
|
||||
This is the Orion editor packaged for Mozilla.
|
||||
|
||||
The Orion editor web site: http://www.eclipse.org/orion
|
||||
|
||||
# Upgrade
|
||||
|
||||
To upgrade Orion to a newer version see the UPGRADE file.
|
||||
|
||||
Orion version: git clone from 2011-07-06 (after the 0.2 release)
|
||||
commit hash b19bc0b0f4e2843823bb1b8c8b4a64395c59e617
|
||||
|
||||
# License
|
||||
|
||||
The following files are licensed according to the contents in the LICENSE
|
||||
file:
|
||||
orion.js
|
||||
orion.css
|
||||
|
20
browser/devtools/sourceeditor/orion/UPGRADE
Normal file
20
browser/devtools/sourceeditor/orion/UPGRADE
Normal file
@ -0,0 +1,20 @@
|
||||
Upgrade notes:
|
||||
|
||||
1. Get the Orion client source code from:
|
||||
http://www.eclipse.org/orion
|
||||
|
||||
2. Install Dryice from:
|
||||
https://github.com/mozilla/dryice
|
||||
|
||||
You also need nodejs for Dryice to run:
|
||||
http://nodejs.org
|
||||
|
||||
3. Copy Makefile.dryice.js to:
|
||||
org.eclipse.orion.client/bundles/
|
||||
|
||||
4. Execute Makefile.dryice.js. You should get orion.js and orion.css.
|
||||
|
||||
5. Copy the two files back here.
|
||||
|
||||
6. Make a new build of Firefox.
|
||||
|
115
browser/devtools/sourceeditor/orion/orion.css
Normal file
115
browser/devtools/sourceeditor/orion/orion.css
Normal file
@ -0,0 +1,115 @@
|
||||
.view {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.viewContainer {
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.viewContent {
|
||||
}.ruler_annotation {
|
||||
background-color: #e1ebfb;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.ruler_annotation_todo {
|
||||
}
|
||||
|
||||
.ruler_annotation_todo_overview {
|
||||
background-color: lightgreen;
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.ruler_annotation_breakpoint {
|
||||
}
|
||||
|
||||
.ruler_annotation_breakpoint_overview {
|
||||
background-color: lightblue;
|
||||
border: 1px solid blue;
|
||||
}
|
||||
|
||||
.ruler_lines {
|
||||
background-color: #e1ebfb;
|
||||
border-right: 1px solid #b1badf;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ruler_overview {
|
||||
background-color: #e1ebfb;
|
||||
}
|
||||
|
||||
.ruler_lines_even {
|
||||
background-color: #e1ebfb;
|
||||
}
|
||||
|
||||
.ruler_lines_odd {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.token_comment {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.token_javadoc {
|
||||
color: #00008F;
|
||||
}
|
||||
|
||||
.token_string {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.token_keyword {
|
||||
color: darkred;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token_bracket_outline {
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
.token_bracket {
|
||||
color: white;
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.token_space {
|
||||
background-image: url('/examples/textview/images/white_space.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.token_tab {
|
||||
background-image: url('/examples/textview/images/white_tab.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: left center;
|
||||
}
|
||||
|
||||
.line_caret {
|
||||
background-color: #EAF2FE;
|
||||
}/* Styling for html syntax highlighting */
|
||||
.entity-name-tag {
|
||||
color: #3f7f7f;
|
||||
}
|
||||
|
||||
.entity-other-attribute-name {
|
||||
color: #7f007f;
|
||||
}
|
||||
|
||||
.punctuation-definition-comment {
|
||||
color: #3f5fbf;
|
||||
}
|
||||
|
||||
.comment {
|
||||
color: #3f5fbf
|
||||
}
|
||||
|
||||
.string-quoted {
|
||||
color: #2a00ff;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
8390
browser/devtools/sourceeditor/orion/orion.js
Normal file
8390
browser/devtools/sourceeditor/orion/orion.js
Normal file
File diff suppressed because it is too large
Load Diff
762
browser/devtools/sourceeditor/source-editor-orion.jsm
Normal file
762
browser/devtools/sourceeditor/source-editor-orion.jsm
Normal file
@ -0,0 +1,762 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et tw=80:
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is the Source Editor component (Orion editor).
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* The Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Mihai Sucan <mihai.sucan@gmail.com> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK *****/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const ORION_SCRIPT = "chrome://browser/content/orion.js";
|
||||
const ORION_IFRAME = "data:text/html;charset=utf8,<!DOCTYPE html>" +
|
||||
"<html style='height:100%' dir='ltr'>" +
|
||||
"<body style='height:100%;margin:0;overflow:hidden'>" +
|
||||
"<div id='editor' style='height:100%'></div>" +
|
||||
"</body></html>";
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
/**
|
||||
* Predefined themes for syntax highlighting. This objects maps
|
||||
* SourceEditor.THEMES to Orion CSS files.
|
||||
*/
|
||||
const ORION_THEMES = {
|
||||
textmate: "chrome://browser/content/orion.css",
|
||||
};
|
||||
|
||||
/**
|
||||
* Known editor events you can listen for. This object maps SourceEditor.EVENTS
|
||||
* to Orion events.
|
||||
*/
|
||||
const ORION_EVENTS = {
|
||||
ContextMenu: "ContextMenu",
|
||||
TextChanged: "ModelChanged",
|
||||
Selection: "Selection",
|
||||
};
|
||||
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SourceEditor"];
|
||||
|
||||
/**
|
||||
* The SourceEditor object constructor. The SourceEditor component allows you to
|
||||
* provide users with an editor tailored to the specific needs of editing source
|
||||
* code, aimed primarily at web developers.
|
||||
*
|
||||
* The editor used here is Eclipse Orion (see http://www.eclipse.org/orion).
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SourceEditor() {
|
||||
// Update the SourceEditor defaults from user preferences.
|
||||
|
||||
SourceEditor.DEFAULTS.TAB_SIZE =
|
||||
Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE);
|
||||
SourceEditor.DEFAULTS.EXPAND_TAB =
|
||||
Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB);
|
||||
}
|
||||
|
||||
SourceEditor.prototype = {
|
||||
_view: null,
|
||||
_iframe: null,
|
||||
_undoStack: null,
|
||||
_lines_ruler: null,
|
||||
_styler: null,
|
||||
_mode: null,
|
||||
_expandTab: null,
|
||||
_tabSize: null,
|
||||
|
||||
/**
|
||||
* The editor container element.
|
||||
* @type nsIDOMElement
|
||||
*/
|
||||
parentElement: null,
|
||||
|
||||
/**
|
||||
* Initialize the editor.
|
||||
*
|
||||
* @param nsIDOMElement aElement
|
||||
* The DOM element where you want the editor to show.
|
||||
* @param object aConfig
|
||||
* Editor configuration object. Properties:
|
||||
* - placeholderText - the text you want to be shown by default.
|
||||
* - theme - the syntax highlighting theme you want. You can use one
|
||||
* of the predefined themes, or you can point to your CSS file.
|
||||
* - mode - the editor mode, based on the file type you want to edit.
|
||||
* You can use one of the predefined modes.
|
||||
* - tabSize - define how many spaces to use for a tab character.
|
||||
* - expandTab - tells if you want tab characters to be expanded to
|
||||
* spaces.
|
||||
* - readOnly - make the editor read only.
|
||||
* - showLineNumbers - display the line numbers gutter.
|
||||
* - undoLimit - how many steps should the undo stack hold.
|
||||
* - keys - is an array of objects that allows you to define custom
|
||||
* editor keyboard bindings. Each object can have:
|
||||
* - action - name of the editor action to invoke.
|
||||
* - code - keyCode for the shortcut.
|
||||
* - accel - boolean for the Accel key (cmd/ctrl).
|
||||
* - shift - boolean for the Shift key.
|
||||
* - alt - boolean for the Alt key.
|
||||
* - callback - optional function to invoke, if the action is not
|
||||
* predefined in the editor.
|
||||
* @param function [aCallback]
|
||||
* Function you want to execute once the editor is loaded and
|
||||
* initialized.
|
||||
*/
|
||||
init: function SE_init(aElement, aConfig, aCallback)
|
||||
{
|
||||
if (this._iframe) {
|
||||
throw new Error("SourceEditor is already initialized!");
|
||||
}
|
||||
|
||||
let doc = aElement.ownerDocument;
|
||||
|
||||
this._iframe = doc.createElementNS(XUL_NS, "iframe");
|
||||
this._iframe.flex = 1;
|
||||
|
||||
let onIframeLoad = (function() {
|
||||
this._iframe.removeEventListener("load", onIframeLoad, true);
|
||||
|
||||
Services.scriptloader.loadSubScript(ORION_SCRIPT,
|
||||
this._iframe.contentWindow.wrappedJSObject, "utf8");
|
||||
|
||||
this._onLoad(aCallback);
|
||||
}).bind(this);
|
||||
|
||||
this._iframe.addEventListener("load", onIframeLoad, true);
|
||||
|
||||
this._iframe.setAttribute("src", ORION_IFRAME);
|
||||
|
||||
aElement.appendChild(this._iframe);
|
||||
this.parentElement = aElement;
|
||||
this._config = aConfig;
|
||||
},
|
||||
|
||||
/**
|
||||
* The editor iframe load event handler.
|
||||
*
|
||||
* @private
|
||||
* @param function [aCallback]
|
||||
* Optional function invoked when the editor completes loading.
|
||||
*/
|
||||
_onLoad: function SE__onLoad(aCallback)
|
||||
{
|
||||
let config = this._config;
|
||||
let window = this._iframe.contentWindow.wrappedJSObject;
|
||||
let textview = window.orion.textview;
|
||||
|
||||
this._expandTab = typeof config.expandTab != "undefined" ?
|
||||
config.expandTab : SourceEditor.DEFAULTS.EXPAND_TAB;
|
||||
this._tabSize = config.tabSize || SourceEditor.DEFAULTS.TAB_SIZE;
|
||||
|
||||
let theme = config.theme || SourceEditor.DEFAULTS.THEME;
|
||||
let stylesheet = theme in ORION_THEMES ? ORION_THEMES[theme] : theme;
|
||||
|
||||
this._view = new textview.TextView({
|
||||
model: new textview.TextModel(config.placeholderText),
|
||||
parent: "editor",
|
||||
stylesheet: stylesheet,
|
||||
tabSize: this._tabSize,
|
||||
readonly: config.readOnly,
|
||||
});
|
||||
|
||||
if (config.showLineNumbers) {
|
||||
this._lines_ruler = new textview.LineNumberRuler("left",
|
||||
{styleClass: "ruler_lines", style: {minWidth: "1.4em"}},
|
||||
{styleClass: "ruler_lines_even"}, {styleClass: "ruler_lines_even"});
|
||||
|
||||
this._view.addRuler(this._lines_ruler);
|
||||
}
|
||||
|
||||
this.setMode(config.mode || SourceEditor.DEFAULTS.MODE);
|
||||
|
||||
this._undoStack = new textview.UndoStack(this._view,
|
||||
config.undoLimit || SourceEditor.DEFAULTS.UNDO_LIMIT);
|
||||
|
||||
this._initEditorFeatures();
|
||||
|
||||
(config.keys || []).forEach(function(aKey) {
|
||||
let binding = new textview.KeyBinding(aKey.code, aKey.accel, aKey.shift,
|
||||
aKey.alt);
|
||||
this._view.setKeyBinding(binding, aKey.action);
|
||||
|
||||
if (aKey.callback) {
|
||||
this._view.setAction(aKey.action, aKey.callback);
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (aCallback) {
|
||||
aCallback(this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the custom Orion editor features.
|
||||
* @private
|
||||
*/
|
||||
_initEditorFeatures: function SE__initEditorFeatures()
|
||||
{
|
||||
let window = this._iframe.contentWindow.wrappedJSObject;
|
||||
let textview = window.orion.textview;
|
||||
|
||||
this._view.setAction("tab", this._doTab.bind(this));
|
||||
|
||||
let shiftTabKey = new textview.KeyBinding(Ci.nsIDOMKeyEvent.DOM_VK_TAB,
|
||||
false, true);
|
||||
this._view.setAction("Unindent Lines", this._doUnindentLines.bind(this));
|
||||
this._view.setKeyBinding(shiftTabKey, "Unindent Lines");
|
||||
this._view.setAction("enter", this._doEnter.bind(this));
|
||||
|
||||
if (this._expandTab) {
|
||||
this._view.setAction("deletePrevious", this._doDeletePrevious.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "tab" editor action implementation. This adds support for expanded tabs
|
||||
* to spaces, and support for the indentation of multiple lines at once.
|
||||
* @private
|
||||
*/
|
||||
_doTab: function SE__doTab()
|
||||
{
|
||||
let indent = "\t";
|
||||
let selection = this.getSelection();
|
||||
let model = this._model;
|
||||
let firstLine = model.getLineAtOffset(selection.start);
|
||||
let firstLineStart = model.getLineStart(firstLine);
|
||||
let lastLineOffset = selection.end > selection.start ?
|
||||
selection.end - 1 : selection.end;
|
||||
let lastLine = model.getLineAtOffset(lastLineOffset);
|
||||
|
||||
if (this._expandTab) {
|
||||
let offsetFromLineStart = firstLine == lastLine ?
|
||||
selection.start - firstLineStart : 0;
|
||||
let spaces = this._tabSize - (offsetFromLineStart % this._tabSize);
|
||||
indent = (new Array(spaces + 1)).join(" ");
|
||||
}
|
||||
|
||||
// Do selection indentation.
|
||||
if (firstLine != lastLine) {
|
||||
let lines = [""];
|
||||
let lastLineEnd = model.getLineEnd(lastLine, true);
|
||||
let selectedLines = lastLine - firstLine + 1;
|
||||
|
||||
for (let i = firstLine; i <= lastLine; i++) {
|
||||
lines.push(model.getLine(i, true));
|
||||
}
|
||||
|
||||
this.startCompoundChange();
|
||||
|
||||
this.setText(lines.join(indent), firstLineStart, lastLineEnd);
|
||||
|
||||
let newSelectionStart = firstLineStart == selection.start ?
|
||||
selection.start : selection.start + indent.length;
|
||||
let newSelectionEnd = selection.end + (selectedLines * indent.length);
|
||||
|
||||
this._view.setSelection(newSelectionStart, newSelectionEnd);
|
||||
|
||||
this.endCompoundChange();
|
||||
} else {
|
||||
this.setText(indent, selection.start, selection.end);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* The "deletePrevious" editor action implementation. This adds unindentation
|
||||
* support to the Backspace key implementation.
|
||||
* @private
|
||||
*/
|
||||
_doDeletePrevious: function SE__doDeletePrevious()
|
||||
{
|
||||
let selection = this.getSelection();
|
||||
if (selection.start == selection.end && this._expandTab) {
|
||||
let model = this._model;
|
||||
let lineIndex = model.getLineAtOffset(selection.start);
|
||||
let lineStart = model.getLineStart(lineIndex);
|
||||
let offset = selection.start - lineStart;
|
||||
if (offset >= this._tabSize && (offset % this._tabSize) == 0) {
|
||||
let text = this.getText(lineStart, selection.start);
|
||||
if (!/[^ ]/.test(text)) {
|
||||
this.setText("", selection.start - this._tabSize, selection.end);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* The "Unindent lines" editor action implementation. This method is invoked
|
||||
* when the user presses Shift-Tab.
|
||||
* @private
|
||||
*/
|
||||
_doUnindentLines: function SE__doUnindentLines()
|
||||
{
|
||||
let indent = "\t";
|
||||
|
||||
let selection = this.getSelection();
|
||||
let model = this._model;
|
||||
let firstLine = model.getLineAtOffset(selection.start);
|
||||
let lastLineOffset = selection.end > selection.start ?
|
||||
selection.end - 1 : selection.end;
|
||||
let lastLine = model.getLineAtOffset(lastLineOffset);
|
||||
|
||||
if (this._expandTab) {
|
||||
indent = (new Array(this._tabSize + 1)).join(" ");
|
||||
}
|
||||
|
||||
let lines = [];
|
||||
for (let line, i = firstLine; i <= lastLine; i++) {
|
||||
line = model.getLine(i, true);
|
||||
if (line.indexOf(indent) != 0) {
|
||||
return false;
|
||||
}
|
||||
lines.push(line.substring(indent.length));
|
||||
}
|
||||
|
||||
let firstLineStart = model.getLineStart(firstLine);
|
||||
let lastLineStart = model.getLineStart(lastLine);
|
||||
let lastLineEnd = model.getLineEnd(lastLine, true);
|
||||
|
||||
this.startCompoundChange();
|
||||
|
||||
this.setText(lines.join(""), firstLineStart, lastLineEnd);
|
||||
|
||||
let selectedLines = lastLine - firstLine + 1;
|
||||
let newSelectionStart = firstLineStart == selection.start ?
|
||||
selection.start :
|
||||
Math.max(firstLineStart,
|
||||
selection.start - indent.length);
|
||||
let newSelectionEnd = selection.end - (selectedLines * indent.length) +
|
||||
(selection.end == lastLineStart + 1 ? 1 : 0);
|
||||
if (firstLine == lastLine) {
|
||||
newSelectionEnd = Math.max(lastLineStart, newSelectionEnd);
|
||||
}
|
||||
this._view.setSelection(newSelectionStart, newSelectionEnd);
|
||||
|
||||
this.endCompoundChange();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* The editor Enter action implementation, which adds simple automatic
|
||||
* indentation based on the previous line when the user presses the Enter key.
|
||||
* @private
|
||||
*/
|
||||
_doEnter: function SE__doEnter()
|
||||
{
|
||||
let selection = this.getSelection();
|
||||
if (selection.start != selection.end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let model = this._model;
|
||||
let lineIndex = model.getLineAtOffset(selection.start);
|
||||
let lineText = model.getLine(lineIndex);
|
||||
let lineStart = model.getLineStart(lineIndex);
|
||||
let index = 0;
|
||||
let lineOffset = selection.start - lineStart;
|
||||
while (index < lineOffset && /[ \t]/.test(lineText.charAt(index))) {
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let prefix = lineText.substring(0, index);
|
||||
index = lineOffset;
|
||||
while (index < lineText.length &&
|
||||
/[ \t]/.test(lineText.charAt(index++))) {
|
||||
selection.end++;
|
||||
}
|
||||
|
||||
this.setText(this.getLineDelimiter() + prefix, selection.start,
|
||||
selection.end);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the Orion Model, the TextModel object instance we use.
|
||||
* @private
|
||||
* @type object
|
||||
*/
|
||||
get _model() {
|
||||
return this._view.getModel();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the editor element.
|
||||
*
|
||||
* @return nsIDOMElement
|
||||
* In this implementation a xul:iframe holds the editor.
|
||||
*/
|
||||
get editorElement() {
|
||||
return this._iframe;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an event listener to the editor. You can use one of the known events.
|
||||
*
|
||||
* @see SourceEditor.EVENTS
|
||||
*
|
||||
* @param string aEventType
|
||||
* The event type you want to listen for.
|
||||
* @param function aCallback
|
||||
* The function you want executed when the event is triggered.
|
||||
* @param mixed [aData]
|
||||
* Optional data to pass to the callback when the event is triggered.
|
||||
*/
|
||||
addEventListener:
|
||||
function SE_addEventListener(aEventType, aCallback, aData)
|
||||
{
|
||||
if (aEventType in ORION_EVENTS) {
|
||||
this._view.addEventListener(ORION_EVENTS[aEventType], true,
|
||||
aCallback, aData);
|
||||
} else {
|
||||
throw new Error("SourceEditor.addEventListener() unknown event " +
|
||||
"type " + aEventType);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener from the editor. You can use one of the known
|
||||
* events.
|
||||
*
|
||||
* @see SourceEditor.EVENTS
|
||||
*
|
||||
* @param string aEventType
|
||||
* The event type you have a listener for.
|
||||
* @param function aCallback
|
||||
* The function you have as the event handler.
|
||||
* @param mixed [aData]
|
||||
* The optional data passed to the callback.
|
||||
*/
|
||||
removeEventListener:
|
||||
function SE_removeEventListener(aEventType, aCallback, aData)
|
||||
{
|
||||
if (aEventType in ORION_EVENTS) {
|
||||
this._view.removeEventListener(ORION_EVENTS[aEventType], true,
|
||||
aCallback, aData);
|
||||
} else {
|
||||
throw new Error("SourceEditor.removeEventListener() unknown event " +
|
||||
"type " + aEventType);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Undo a change in the editor.
|
||||
*/
|
||||
undo: function SE_undo()
|
||||
{
|
||||
this._undoStack.undo();
|
||||
},
|
||||
|
||||
/**
|
||||
* Redo a change in the editor.
|
||||
*/
|
||||
redo: function SE_redo()
|
||||
{
|
||||
this._undoStack.redo();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if there are changes that can be undone.
|
||||
*
|
||||
* @return boolean
|
||||
* True if there are changes that can be undone, false otherwise.
|
||||
*/
|
||||
canUndo: function SE_canUndo()
|
||||
{
|
||||
return this._undoStack.canUndo();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if there are changes that can be repeated.
|
||||
*
|
||||
* @return boolean
|
||||
* True if there are changes that can be repeated, false otherwise.
|
||||
*/
|
||||
canRedo: function SE_canRedo()
|
||||
{
|
||||
return this._undoStack.canRedo();
|
||||
},
|
||||
|
||||
/**
|
||||
* Start a compound change in the editor. Compound changes are grouped into
|
||||
* only one change that you can undo later, after you invoke
|
||||
* endCompoundChange().
|
||||
*/
|
||||
startCompoundChange: function SE_startCompoundChange()
|
||||
{
|
||||
this._undoStack.startCompoundChange();
|
||||
},
|
||||
|
||||
/**
|
||||
* End a compound change in the editor.
|
||||
*/
|
||||
endCompoundChange: function SE_endCompoundChange()
|
||||
{
|
||||
this._undoStack.endCompoundChange();
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus the editor.
|
||||
*/
|
||||
focus: function SE_focus()
|
||||
{
|
||||
this._view.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the editor has focus.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the editor is focused, false otherwise.
|
||||
*/
|
||||
hasFocus: function SE_hasFocus()
|
||||
{
|
||||
return this._iframe.ownerDocument.activeElement === this._iframe;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the editor content, in the given range. If no range is given you get
|
||||
* the entire editor content.
|
||||
*
|
||||
* @param number [aStart=0]
|
||||
* Optional, start from the given offset.
|
||||
* @param number [aEnd=content char count]
|
||||
* Optional, end offset for the text you want. If this parameter is not
|
||||
* given, then the text returned goes until the end of the editor
|
||||
* content.
|
||||
* @return string
|
||||
* The text in the given range.
|
||||
*/
|
||||
getText: function SE_getText(aStart, aEnd)
|
||||
{
|
||||
return this._view.getText(aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of characters in the editor content.
|
||||
*
|
||||
* @return number
|
||||
* The number of editor content characters.
|
||||
*/
|
||||
getCharCount: function SE_getCharCount()
|
||||
{
|
||||
return this._model.getCharCount();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the selected text.
|
||||
*
|
||||
* @return string
|
||||
* The currently selected text.
|
||||
*/
|
||||
getSelectedText: function SE_getSelectedText()
|
||||
{
|
||||
let selection = this.getSelection();
|
||||
return this.getText(selection.start, selection.end);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace text in the source editor with the given text, in the given range.
|
||||
*
|
||||
* @param string aText
|
||||
* The text you want to put into the editor.
|
||||
* @param number [aStart=0]
|
||||
* Optional, the start offset, zero based, from where you want to start
|
||||
* replacing text in the editor.
|
||||
* @param number [aEnd=char count]
|
||||
* Optional, the end offset, zero based, where you want to stop
|
||||
* replacing text in the editor.
|
||||
*/
|
||||
setText: function SE_setText(aText, aStart, aEnd)
|
||||
{
|
||||
this._view.setText(aText, aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Drop the current selection / deselect.
|
||||
*/
|
||||
dropSelection: function SE_dropSelection()
|
||||
{
|
||||
this.setCaretOffset(this.getCaretOffset());
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a specific range in the editor.
|
||||
*
|
||||
* @param number aStart
|
||||
* Selection range start.
|
||||
* @param number aEnd
|
||||
* Selection range end.
|
||||
*/
|
||||
setSelection: function SE_setSelection(aStart, aEnd)
|
||||
{
|
||||
this._view.setSelection(aStart, aEnd, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current selection range.
|
||||
*
|
||||
* @return object
|
||||
* An object with two properties, start and end, that give the
|
||||
* selection range (zero based offsets).
|
||||
*/
|
||||
getSelection: function SE_getSelection()
|
||||
{
|
||||
return this._view.getSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current caret offset.
|
||||
*
|
||||
* @return number
|
||||
* The current caret offset.
|
||||
*/
|
||||
getCaretOffset: function SE_getCaretOffset()
|
||||
{
|
||||
return this._view.getCaretOffset();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the caret offset.
|
||||
*
|
||||
* @param number aOffset
|
||||
* The new caret offset you want to set.
|
||||
*/
|
||||
setCaretOffset: function SE_setCaretOffset(aOffset)
|
||||
{
|
||||
this._view.setCaretOffset(aOffset, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the line delimiter used in the document being edited.
|
||||
*
|
||||
* @return string
|
||||
* The line delimiter.
|
||||
*/
|
||||
getLineDelimiter: function SE_getLineDelimiter()
|
||||
{
|
||||
return this._model.getLineDelimiter();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the source editor mode to the file type you are editing.
|
||||
*
|
||||
* @param string aMode
|
||||
* One of the predefined SourceEditor.MODES.
|
||||
*/
|
||||
setMode: function SE_setMode(aMode)
|
||||
{
|
||||
if (this._styler) {
|
||||
this._styler.destroy();
|
||||
this._styler = null;
|
||||
}
|
||||
|
||||
let window = this._iframe.contentWindow.wrappedJSObject;
|
||||
let TextStyler = window.examples.textview.TextStyler;
|
||||
let TextMateStyler = window.orion.editor.TextMateStyler;
|
||||
let HtmlGrammar = window.orion.editor.HtmlGrammar;
|
||||
|
||||
switch (aMode) {
|
||||
case SourceEditor.MODES.JAVASCRIPT:
|
||||
case SourceEditor.MODES.CSS:
|
||||
this._styler = new TextStyler(this._view, aMode);
|
||||
break;
|
||||
|
||||
case SourceEditor.MODES.HTML:
|
||||
case SourceEditor.MODES.XML:
|
||||
this._styler = new TextMateStyler(this._view, HtmlGrammar.grammar);
|
||||
break;
|
||||
}
|
||||
|
||||
this._mode = aMode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current source editor mode.
|
||||
*
|
||||
* @return string
|
||||
* Returns one of the predefined SourceEditor.MODES.
|
||||
*/
|
||||
getMode: function SE_getMode()
|
||||
{
|
||||
return this._mode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Setter for the read-only state of the editor.
|
||||
* @param boolean aValue
|
||||
* Tells if you want the editor to read-only or not.
|
||||
*/
|
||||
set readOnly(aValue)
|
||||
{
|
||||
this._view.readonly = aValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the read-only state of the editor.
|
||||
* @type boolean
|
||||
*/
|
||||
get readOnly()
|
||||
{
|
||||
return this._view.readonly;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy/uninitialize the editor.
|
||||
*/
|
||||
destroy: function SE_destroy()
|
||||
{
|
||||
this._view.destroy();
|
||||
this.parentElement.removeChild(this._iframe);
|
||||
this.parentElement = null;
|
||||
this._iframe = null;
|
||||
this._undoStack = null;
|
||||
this._styler = null;
|
||||
this._lines_ruler = null;
|
||||
this._view = null;
|
||||
this._config = null;
|
||||
},
|
||||
};
|
782
browser/devtools/sourceeditor/source-editor-textarea.jsm
Normal file
782
browser/devtools/sourceeditor/source-editor-textarea.jsm
Normal file
@ -0,0 +1,782 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et tw=80:
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is the Source Editor component (textarea fallback).
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* The Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Mihai Sucan <mihai.sucan@gmail.com> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK *****/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SourceEditor"];
|
||||
|
||||
/**
|
||||
* The SourceEditor object constructor. The SourceEditor component allows you to
|
||||
* provide users with an editor tailored to the specific needs of editing source
|
||||
* code, aimed primarily at web developers.
|
||||
*
|
||||
* The editor used here is a simple textarea. This is used as a fallback
|
||||
* mechanism for when the user disables the code editor feature.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SourceEditor() {
|
||||
// Update the SourceEditor defaults from user preferences.
|
||||
|
||||
SourceEditor.DEFAULTS.TAB_SIZE =
|
||||
Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE);
|
||||
SourceEditor.DEFAULTS.EXPAND_TAB =
|
||||
Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB);
|
||||
|
||||
this._listeners = {};
|
||||
this._lastSelection = {};
|
||||
}
|
||||
|
||||
SourceEditor.prototype = {
|
||||
_textbox: null,
|
||||
_editor: null,
|
||||
_listeners: null,
|
||||
_lineDelimiter: null,
|
||||
_editActionListener: null,
|
||||
_expandTab: null,
|
||||
_tabSize: null,
|
||||
|
||||
/**
|
||||
* The editor container element.
|
||||
* @type nsIDOMElement
|
||||
*/
|
||||
parentElement: null,
|
||||
|
||||
/**
|
||||
* Initialize the editor.
|
||||
*
|
||||
* @param nsIDOMElement aElement
|
||||
* The DOM element where you want the editor to show.
|
||||
* @param object aConfig
|
||||
* Editor configuration object. Properties:
|
||||
* - placeholderText - the text you want to be shown by default.
|
||||
* - mode - the editor mode, based on the file type you want to edit.
|
||||
* You can use one of the predefined modes.
|
||||
* - tabSize - define how many spaces to use for a tab character.
|
||||
* - expandTab - tells if you want tab characters to be expanded to
|
||||
* spaces.
|
||||
* - readOnly - make the editor read only.
|
||||
* - undoLimit - how many steps should the undo stack hold.
|
||||
* @param function [aCallback]
|
||||
* Function you want to execute once the editor is loaded and
|
||||
* initialized.
|
||||
*/
|
||||
init: function SE_init(aElement, aConfig, aCallback)
|
||||
{
|
||||
if (this._textbox) {
|
||||
throw new Error("SourceEditor is already initialized!");
|
||||
}
|
||||
|
||||
let doc = aElement.ownerDocument;
|
||||
let win = doc.defaultView;
|
||||
|
||||
this._textbox = doc.createElementNS(XUL_NS, "textbox");
|
||||
this._textbox.flex = 1;
|
||||
this._textbox.setAttribute("multiline", true);
|
||||
this._textbox.setAttribute("dir", "ltr");
|
||||
|
||||
aElement.appendChild(this._textbox);
|
||||
|
||||
this.parentElement = aElement;
|
||||
this._editor = this._textbox.editor;
|
||||
|
||||
this._expandTab = aConfig.expandTab !== undefined ?
|
||||
aConfig.expandTab : SourceEditor.DEFAULTS.EXPAND_TAB;
|
||||
this._tabSize = aConfig.tabSize || SourceEditor.DEFAULTS.TAB_SIZE;
|
||||
|
||||
this._textbox.style.MozTabSize = this._tabSize;
|
||||
|
||||
this._textbox.setAttribute("value", aConfig.placeholderText);
|
||||
this._textbox.setAttribute("class", "monospace");
|
||||
this._textbox.style.direction = "ltr";
|
||||
this._textbox.readOnly = aConfig.readOnly;
|
||||
|
||||
// Make sure that the SourceEditor Selection events are fired properly.
|
||||
// Also make sure that the Tab key inserts spaces when expandTab is true.
|
||||
this._textbox.addEventListener("select", this._onSelect.bind(this), false);
|
||||
this._textbox.addEventListener("keypress", this._onKeyPress.bind(this), false);
|
||||
this._textbox.addEventListener("keyup", this._onSelect.bind(this), false);
|
||||
this._textbox.addEventListener("click", this._onSelect.bind(this), false);
|
||||
|
||||
// Mimic the mode change.
|
||||
this.setMode(aConfig.mode || SourceEditor.DEFAULTS.MODE);
|
||||
|
||||
this._editor.transactionManager.maxTransactionCount =
|
||||
aConfig.undoLimit || SourceEditor.DEFAULTS.UNDO_LIMIT;
|
||||
|
||||
// Make sure that the transactions stack is clean.
|
||||
this._editor.transactionManager.clear();
|
||||
this._editor.resetModificationCount();
|
||||
|
||||
// Add the edit action listener so we can fire the SourceEditor TextChanged
|
||||
// events.
|
||||
this._editActionListener = new EditActionListener(this);
|
||||
this._editor.addEditActionListener(this._editActionListener);
|
||||
|
||||
this._lineDelimiter = win.navigator.platform.indexOf("Win") > -1 ?
|
||||
"\r\n" : "\n";
|
||||
|
||||
this._config = aConfig;
|
||||
|
||||
if (aCallback) {
|
||||
aCallback(this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The textbox keypress event handler allows users to indent code using the
|
||||
* Tab key.
|
||||
*
|
||||
* @private
|
||||
* @param nsIDOMEvent aEvent
|
||||
* The DOM object for the event.
|
||||
*/
|
||||
_onKeyPress: function SE__onKeyPress(aEvent)
|
||||
{
|
||||
if (aEvent.keyCode != aEvent.DOM_VK_TAB || aEvent.shiftKey ||
|
||||
aEvent.metaKey || aEvent.ctrlKey || aEvent.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
aEvent.preventDefault();
|
||||
|
||||
let caret = this.getCaretOffset();
|
||||
let indent = "\t";
|
||||
|
||||
if (this._expandTab) {
|
||||
let text = this._textbox.value;
|
||||
let lineStart = caret;
|
||||
while (lineStart > 0) {
|
||||
let c = text.charAt(lineStart - 1);
|
||||
if (c == "\r" || c == "\n") {
|
||||
break;
|
||||
}
|
||||
lineStart--;
|
||||
}
|
||||
let offset = caret - lineStart;
|
||||
let spaces = this._tabSize - (offset % this._tabSize);
|
||||
indent = (new Array(spaces + 1)).join(" ");
|
||||
}
|
||||
|
||||
this.setText(indent, caret, caret);
|
||||
this.setCaretOffset(caret + indent.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* The textbox keyup, click and select event handler tracks selection
|
||||
* changes. This method invokes the SourceEditor Selection event handlers.
|
||||
*
|
||||
* @see SourceEditor.EVENTS.SELECTION
|
||||
* @private
|
||||
*/
|
||||
_onSelect: function SE__onSelect()
|
||||
{
|
||||
let selection = this.getSelection();
|
||||
selection.collapsed = selection.start == selection.end;
|
||||
if (selection.collapsed && this._lastSelection.collapsed) {
|
||||
this._lastSelection = selection;
|
||||
return; // just a cursor move.
|
||||
}
|
||||
|
||||
if (this._lastSelection.start != selection.start ||
|
||||
this._lastSelection.end != selection.end) {
|
||||
let sendEvent = {
|
||||
oldValue: {start: this._lastSelection.start,
|
||||
end: this._lastSelection.end},
|
||||
newValue: {start: selection.start, end: selection.end},
|
||||
};
|
||||
|
||||
let listeners = this._listeners[SourceEditor.EVENTS.SELECTION] || [];
|
||||
listeners.forEach(function(aListener) {
|
||||
aListener.callback.call(null, sendEvent, aListener.data);
|
||||
}, this);
|
||||
|
||||
this._lastSelection = selection;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The TextChanged event dispatcher. This method is called when a change in
|
||||
* the text occurs. All of the SourceEditor TextChanged event handlers are
|
||||
* notified about the lower level change.
|
||||
*
|
||||
* @see SourceEditor.EVENTS.TEXT_CHANGED
|
||||
* @see EditActionListener
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param object aEvent
|
||||
* The TextChanged event object that is going to be sent to the
|
||||
* SourceEditor event handlers.
|
||||
*/
|
||||
_onTextChanged: function SE__onTextChanged(aEvent)
|
||||
{
|
||||
let listeners = this._listeners[SourceEditor.EVENTS.TEXT_CHANGED] || [];
|
||||
listeners.forEach(function(aListener) {
|
||||
aListener.callback.call(null, aEvent, aListener.data);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the editor element.
|
||||
*
|
||||
* @return nsIDOMElement
|
||||
* In this implementation a xul:textbox is returned.
|
||||
*/
|
||||
get editorElement() {
|
||||
return this._textbox;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an event listener to the editor. You can use one of the known events.
|
||||
*
|
||||
* @see SourceEditor.EVENTS
|
||||
*
|
||||
* @param string aEventType
|
||||
* The event type you want to listen for.
|
||||
* @param function aCallback
|
||||
* The function you want executed when the event is triggered.
|
||||
* @param mixed [aData]
|
||||
* Optional data to pass to the callback when the event is triggered.
|
||||
*/
|
||||
addEventListener:
|
||||
function SE_addEventListener(aEventType, aCallback, aData)
|
||||
{
|
||||
const EVENTS = SourceEditor.EVENTS;
|
||||
let listener = {
|
||||
type: aEventType,
|
||||
data: aData,
|
||||
callback: aCallback,
|
||||
};
|
||||
|
||||
if (aEventType == EVENTS.CONTEXT_MENU) {
|
||||
listener.domType = "contextmenu";
|
||||
listener.target = this._textbox;
|
||||
listener.handler = this._onContextMenu.bind(this, listener);
|
||||
listener.target.addEventListener(listener.domType, listener.handler, false);
|
||||
}
|
||||
|
||||
if (!(aEventType in this._listeners)) {
|
||||
this._listeners[aEventType] = [];
|
||||
}
|
||||
|
||||
this._listeners[aEventType].push(listener);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener from the editor. You can use one of the known
|
||||
* events.
|
||||
*
|
||||
* @see SourceEditor.EVENTS
|
||||
*
|
||||
* @param string aEventType
|
||||
* The event type you have a listener for.
|
||||
* @param function aCallback
|
||||
* The function you have as the event handler.
|
||||
* @param mixed [aData]
|
||||
* The optional data passed to the callback.
|
||||
*/
|
||||
removeEventListener:
|
||||
function SE_removeEventListener(aEventType, aCallback, aData)
|
||||
{
|
||||
let listeners = this._listeners[aEventType];
|
||||
if (!listeners) {
|
||||
throw new Error("SourceEditor.removeEventListener() called for an " +
|
||||
"unknown event.");
|
||||
}
|
||||
|
||||
const EVENTS = SourceEditor.EVENTS;
|
||||
|
||||
this._listeners[aEventType] = listeners.filter(function(aListener) {
|
||||
let isSameListener = aListener.type == aEventType &&
|
||||
aListener.callback === aCallback &&
|
||||
aListener.data === aData;
|
||||
if (isSameListener && aListener.domType) {
|
||||
aListener.target.removeEventListener(aListener.domType,
|
||||
aListener.handler, false);
|
||||
}
|
||||
return !isSameListener;
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* The xul:textbox contextmenu event handler. This is used a wrapper for each
|
||||
* contextmenu event listener added by the SourceEditor client.
|
||||
*
|
||||
* @param object aListener
|
||||
* The object that holds listener information, see this._listener and
|
||||
* this.addEventListener().
|
||||
* @param nsIDOMEvent aDOMEvent
|
||||
* The nsIDOMEvent object that triggered the context menu.
|
||||
*/
|
||||
_onContextMenu: function SE__onContextMenu(aListener, aDOMEvent)
|
||||
{
|
||||
let input = this._textbox.inputField;
|
||||
let rect = this._textbox.getBoundingClientRect();
|
||||
|
||||
// Prepare the event object we send to the event handler.
|
||||
let sendEvent = {
|
||||
x: aDOMEvent.clientX - rect.left + input.scrollLeft,
|
||||
y: aDOMEvent.clientY - rect.top + input.scrollTop,
|
||||
screenX: aDOMEvent.screenX,
|
||||
screenY: aDOMEvent.screenY,
|
||||
};
|
||||
|
||||
aDOMEvent.preventDefault();
|
||||
aListener.callback.call(null, sendEvent, aListener.data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Undo a change in the editor.
|
||||
*/
|
||||
undo: function SE_undo()
|
||||
{
|
||||
this._editor.undo(1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Redo a change in the editor.
|
||||
*/
|
||||
redo: function SE_redo()
|
||||
{
|
||||
this._editor.redo(1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if there are changes that can be undone.
|
||||
*
|
||||
* @return boolean
|
||||
* True if there are changes that can be undone, false otherwise.
|
||||
*/
|
||||
canUndo: function SE_canUndo()
|
||||
{
|
||||
let isEnabled = {};
|
||||
let canUndo = {};
|
||||
this._editor.canUndo(isEnabled, canUndo);
|
||||
return canUndo.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if there are changes that can be repeated.
|
||||
*
|
||||
* @return boolean
|
||||
* True if there are changes that can be repeated, false otherwise.
|
||||
*/
|
||||
canRedo: function SE_canRedo()
|
||||
{
|
||||
let isEnabled = {};
|
||||
let canRedo = {};
|
||||
this._editor.canRedo(isEnabled, canRedo);
|
||||
return canRedo.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Start a compound change in the editor. Compound changes are grouped into
|
||||
* only one change that you can undo later, after you invoke
|
||||
* endCompoundChange().
|
||||
*/
|
||||
startCompoundChange: function SE_startCompoundChange()
|
||||
{
|
||||
this._editor.beginTransaction();
|
||||
},
|
||||
|
||||
/**
|
||||
* End a compound change in the editor.
|
||||
*/
|
||||
endCompoundChange: function SE_endCompoundChange()
|
||||
{
|
||||
this._editor.endTransaction();
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus the editor.
|
||||
*/
|
||||
focus: function SE_focus()
|
||||
{
|
||||
this._textbox.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the editor has focus.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the editor is focused, false otherwise.
|
||||
*/
|
||||
hasFocus: function SE_hasFocus()
|
||||
{
|
||||
return this._textbox.ownerDocument.activeElement ===
|
||||
this._textbox.inputField;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the editor content, in the given range. If no range is given you get
|
||||
* the entire editor content.
|
||||
*
|
||||
* @param number [aStart=0]
|
||||
* Optional, start from the given offset.
|
||||
* @param number [aEnd=content char count]
|
||||
* Optional, end offset for the text you want. If this parameter is not
|
||||
* given, then the text returned goes until the end of the editor
|
||||
* content.
|
||||
* @return string
|
||||
* The text in the given range.
|
||||
*/
|
||||
getText: function SE_getText(aStart, aEnd)
|
||||
{
|
||||
let value = this._textbox.value || "";
|
||||
if (aStart === undefined || aStart === null) {
|
||||
aStart = 0;
|
||||
}
|
||||
if (aEnd === undefined || aEnd === null) {
|
||||
aEnd = value.length;
|
||||
}
|
||||
return value.substring(aStart, aEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of characters in the editor content.
|
||||
*
|
||||
* @return number
|
||||
* The number of editor content characters.
|
||||
*/
|
||||
getCharCount: function SE_getCharCount()
|
||||
{
|
||||
return this._textbox.textLength;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the selected text.
|
||||
*
|
||||
* @return string
|
||||
* The currently selected text.
|
||||
*/
|
||||
getSelectedText: function SE_getSelectedText()
|
||||
{
|
||||
let selection = this.getSelection();
|
||||
return selection.start != selection.end ?
|
||||
this.getText(selection.start, selection.end) : "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace text in the source editor with the given text, in the given range.
|
||||
*
|
||||
* @param string aText
|
||||
* The text you want to put into the editor.
|
||||
* @param number [aStart=0]
|
||||
* Optional, the start offset, zero based, from where you want to start
|
||||
* replacing text in the editor.
|
||||
* @param number [aEnd=char count]
|
||||
* Optional, the end offset, zero based, where you want to stop
|
||||
* replacing text in the editor.
|
||||
*/
|
||||
setText: function SE_setText(aText, aStart, aEnd)
|
||||
{
|
||||
if (aStart === undefined) {
|
||||
this._textbox.value = aText;
|
||||
} else {
|
||||
if (aEnd === undefined) {
|
||||
aEnd = this._textbox.textLength;
|
||||
}
|
||||
|
||||
let value = this._textbox.value || "";
|
||||
let removedText = value.substring(aStart, aEnd);
|
||||
let prefix = value.substr(0, aStart);
|
||||
let suffix = value.substr(aEnd);
|
||||
|
||||
if (suffix) {
|
||||
this._editActionListener._setTextRangeEvent = {
|
||||
start: aStart,
|
||||
removedCharCount: removedText.length,
|
||||
addedCharCount: aText.length,
|
||||
};
|
||||
}
|
||||
|
||||
this._textbox.value = prefix + aText + suffix;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Drop the current selection / deselect.
|
||||
*/
|
||||
dropSelection: function SE_dropSelection()
|
||||
{
|
||||
let selection = this._editor.selection;
|
||||
selection.collapse(selection.focusNode, selection.focusOffset);
|
||||
this._onSelect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a specific range in the editor.
|
||||
*
|
||||
* @param number aStart
|
||||
* Selection range start.
|
||||
* @param number aEnd
|
||||
* Selection range end.
|
||||
*/
|
||||
setSelection: function SE_setSelection(aStart, aEnd)
|
||||
{
|
||||
this._textbox.setSelectionRange(aStart, aEnd);
|
||||
this._onSelect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current selection range.
|
||||
*
|
||||
* @return object
|
||||
* An object with two properties, start and end, that give the
|
||||
* selection range (zero based offsets).
|
||||
*/
|
||||
getSelection: function SE_getSelection()
|
||||
{
|
||||
return {
|
||||
start: this._textbox.selectionStart,
|
||||
end: this._textbox.selectionEnd
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current caret offset.
|
||||
*
|
||||
* @return number
|
||||
* The current caret offset.
|
||||
*/
|
||||
getCaretOffset: function SE_getCaretOffset()
|
||||
{
|
||||
let selection = this.getSelection();
|
||||
return selection.start < selection.end ?
|
||||
selection.end : selection.start;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the caret offset.
|
||||
*
|
||||
* @param number aOffset
|
||||
* The new caret offset you want to set.
|
||||
*/
|
||||
setCaretOffset: function SE_setCaretOffset(aOffset)
|
||||
{
|
||||
this.setSelection(aOffset, aOffset);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the line delimiter used in the document being edited.
|
||||
*
|
||||
* @return string
|
||||
* The line delimiter.
|
||||
*/
|
||||
getLineDelimiter: function SE_getLineDelimiter()
|
||||
{
|
||||
return this._lineDelimiter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the source editor mode to the file type you are editing.
|
||||
*
|
||||
* Note: this implementation makes no difference between any of the available
|
||||
* modes.
|
||||
*
|
||||
* @param string aMode
|
||||
* One of the predefined SourceEditor.MODES.
|
||||
*/
|
||||
setMode: function SE_setMode(aMode)
|
||||
{
|
||||
// nothing to do here
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current source editor mode.
|
||||
*
|
||||
* @return string
|
||||
* Returns one of the predefined SourceEditor.MODES. In this
|
||||
* implementation SourceEditor.MODES.TEXT is always returned.
|
||||
*/
|
||||
getMode: function SE_getMode()
|
||||
{
|
||||
return SourceEditor.MODES.TEXT;
|
||||
},
|
||||
|
||||
/**
|
||||
* Setter for the read-only state of the editor.
|
||||
* @param boolean aValue
|
||||
* Tells if you want the editor to read-only or not.
|
||||
*/
|
||||
set readOnly(aValue)
|
||||
{
|
||||
this._textbox.readOnly = aValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the read-only state of the editor.
|
||||
* @type boolean
|
||||
*/
|
||||
get readOnly()
|
||||
{
|
||||
return this._textbox.readOnly;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy/uninitialize the editor.
|
||||
*/
|
||||
destroy: function SE_destroy()
|
||||
{
|
||||
for (let eventType in this._listeners) {
|
||||
this._listeners[eventType].forEach(function(aListener) {
|
||||
if (aListener.domType) {
|
||||
aListener.target.removeEventListener(aListener.domType,
|
||||
aListener.handler, false);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
this._editor.removeEditActionListener(this._editActionListener);
|
||||
this.parentElement.removeChild(this._textbox);
|
||||
this.parentElement = null;
|
||||
this._editor = null;
|
||||
this._textbox = null;
|
||||
this._config = null;
|
||||
this._listeners = null;
|
||||
this._lastSelection = null;
|
||||
this._editActionListener = null;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The nsIEditActionListener for the nsIEditor of the xul:textbox used by the
|
||||
* SourceEditor. This listener traces text changes such that SourceEditor
|
||||
* TextChanged event handlers get their events.
|
||||
*
|
||||
* @see
|
||||
* http://mxr.mozilla.org/mozilla-central/source/editor/idl/nsIEditActionListener.idl
|
||||
*
|
||||
* @constructor
|
||||
* @param object aSourceEditor
|
||||
* An instance of the SourceEditor to notify when text changes happen.
|
||||
*/
|
||||
function EditActionListener(aSourceEditor) {
|
||||
this._sourceEditor = aSourceEditor;
|
||||
}
|
||||
|
||||
EditActionListener.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIEditActionListener]),
|
||||
|
||||
WillCreateNode: function() { },
|
||||
DidCreateNode: function() { },
|
||||
WillInsertNode: function() { },
|
||||
|
||||
DidInsertNode: function EAL_DidInsertNode(aNode)
|
||||
{
|
||||
if (aNode.nodeType != aNode.TEXT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
let event;
|
||||
|
||||
if (this._setTextRangeEvent) {
|
||||
event = this._setTextRangeEvent;
|
||||
delete this._setTextRangeEvent;
|
||||
} else {
|
||||
event = {
|
||||
start: 0,
|
||||
removedCharCount: 0,
|
||||
addedCharCount: aNode.textContent.length,
|
||||
};
|
||||
}
|
||||
|
||||
this._sourceEditor._onTextChanged(event);
|
||||
},
|
||||
|
||||
WillDeleteNode: function() { },
|
||||
DidDeleteNode: function() { },
|
||||
WillSplitNode: function() { },
|
||||
DidSplitNode: function() { },
|
||||
WillJoinNodes: function() { },
|
||||
DidJoinNodes: function() { },
|
||||
WillInsertText: function() { },
|
||||
|
||||
DidInsertText: function EAL_DidInsertText(aTextNode, aOffset, aString)
|
||||
{
|
||||
let event = {
|
||||
start: aOffset,
|
||||
removedCharCount: 0,
|
||||
addedCharCount: aString.length,
|
||||
};
|
||||
|
||||
this._sourceEditor._onTextChanged(event);
|
||||
},
|
||||
|
||||
WillDeleteText: function() { },
|
||||
|
||||
DidDeleteText: function EAL_DidDeleteText(aTextNode, aOffset, aLength)
|
||||
{
|
||||
let event = {
|
||||
start: aOffset,
|
||||
removedCharCount: aLength,
|
||||
addedCharCount: 0,
|
||||
};
|
||||
|
||||
this._sourceEditor._onTextChanged(event);
|
||||
},
|
||||
|
||||
WillDeleteSelection: function EAL_WillDeleteSelection()
|
||||
{
|
||||
if (this._setTextRangeEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selection = this._sourceEditor.getSelection();
|
||||
let str = this._sourceEditor.getSelectedText();
|
||||
|
||||
let event = {
|
||||
start: selection.start,
|
||||
removedCharCount: str.length,
|
||||
addedCharCount: 0,
|
||||
};
|
||||
|
||||
this._sourceEditor._onTextChanged(event);
|
||||
},
|
||||
|
||||
DidDeleteSelection: function() { },
|
||||
};
|
144
browser/devtools/sourceeditor/source-editor.jsm
Normal file
144
browser/devtools/sourceeditor/source-editor.jsm
Normal file
@ -0,0 +1,144 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et tw=80:
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is the Source Editor component.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* The Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Mihai Sucan <mihai.sucan@gmail.com> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK *****/
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const PREF_EDITOR_COMPONENT = "devtools.editor.component";
|
||||
|
||||
var component = Services.prefs.getCharPref(PREF_EDITOR_COMPONENT);
|
||||
var obj = {};
|
||||
try {
|
||||
Cu.import("resource:///modules/source-editor-" + component + ".jsm", obj);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
Cu.reportError("SourceEditor component failed to load: " + component);
|
||||
|
||||
// If the component does not exist, clear the user pref back to the default.
|
||||
Services.prefs.clearUserPref(PREF_EDITOR_COMPONENT);
|
||||
|
||||
// Load the default editor component.
|
||||
component = Services.prefs.getCharPref(PREF_EDITOR_COMPONENT);
|
||||
Cu.import("resource:///modules/source-editor-" + component + ".jsm", obj);
|
||||
}
|
||||
|
||||
// Export the SourceEditor.
|
||||
var SourceEditor = obj.SourceEditor;
|
||||
var EXPORTED_SYMBOLS = ["SourceEditor"];
|
||||
|
||||
// Add the constants used by all SourceEditors.
|
||||
|
||||
/**
|
||||
* Known SourceEditor preferences.
|
||||
*/
|
||||
SourceEditor.PREFS = {
|
||||
TAB_SIZE: "devtools.editor.tabsize",
|
||||
EXPAND_TAB: "devtools.editor.expandtab",
|
||||
COMPONENT: PREF_EDITOR_COMPONENT,
|
||||
};
|
||||
|
||||
/**
|
||||
* Predefined source editor modes for JavaScript, CSS and other languages.
|
||||
*/
|
||||
SourceEditor.MODES = {
|
||||
JAVASCRIPT: "js",
|
||||
CSS: "css",
|
||||
TEXT: "text",
|
||||
HTML: "html",
|
||||
XML: "xml",
|
||||
};
|
||||
|
||||
/**
|
||||
* Predefined themes for syntax highlighting.
|
||||
*/
|
||||
SourceEditor.THEMES = {
|
||||
TEXTMATE: "textmate",
|
||||
};
|
||||
|
||||
/**
|
||||
* Source editor configuration defaults.
|
||||
*/
|
||||
SourceEditor.DEFAULTS = {
|
||||
MODE: SourceEditor.MODES.TEXT,
|
||||
THEME: SourceEditor.THEMES.TEXTMATE,
|
||||
UNDO_LIMIT: 200,
|
||||
TAB_SIZE: 4, // overriden by pref
|
||||
EXPAND_TAB: true, // overriden by pref
|
||||
};
|
||||
|
||||
/**
|
||||
* Known editor events you can listen for.
|
||||
*/
|
||||
SourceEditor.EVENTS = {
|
||||
/**
|
||||
* The contextmenu event is fired when the editor context menu is invoked. The
|
||||
* event object properties:
|
||||
* - x - the pointer location on the x axis, relative to the document the
|
||||
* user is editing.
|
||||
* - y - the pointer location on the y axis, relative to the document the
|
||||
* user is editing.
|
||||
* - screenX - the pointer location on the x axis, relative to the screen.
|
||||
* This value comes from the DOM contextmenu event.screenX property.
|
||||
* - screenY - the pointer location on the y axis, relative to the screen.
|
||||
* This value comes from the DOM contextmenu event.screenY property.
|
||||
*/
|
||||
CONTEXT_MENU: "ContextMenu",
|
||||
|
||||
/**
|
||||
* The TextChanged event is fired when the editor content changes. The event
|
||||
* object properties:
|
||||
* - start - the character offset in the document where the change has
|
||||
* occured.
|
||||
* - removedCharCount - the number of characters removed from the document.
|
||||
* - addedCharCount - the number of characters added to the document.
|
||||
*/
|
||||
TEXT_CHANGED: "TextChanged",
|
||||
|
||||
/**
|
||||
* The Selection event is fired when the editor selection changes. The event
|
||||
* object properties:
|
||||
* - oldValue - the old selection range.
|
||||
* - newValue - the new selection range.
|
||||
* Both ranges are objects which hold two properties: start and end.
|
||||
*/
|
||||
SELECTION: "Selection",
|
||||
};
|
||||
|
51
browser/devtools/sourceeditor/test/Makefile.in
Normal file
51
browser/devtools/sourceeditor/test/Makefile.in
Normal file
@ -0,0 +1,51 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is Source Editor test code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2011
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Rob Campbell <rcampbell@mozilla.com> (Original Author)
|
||||
# Mihai Sucan <mihai.sucan@gmail.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
DEPTH = ../../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = browser/devtools/sourceeditor/test
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_BROWSER_TEST_FILES = \
|
||||
browser_sourceeditor_initialization.js \
|
||||
|
||||
libs:: $(_BROWSER_TEST_FILES)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
@ -0,0 +1,361 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource:///modules/source-editor.jsm");
|
||||
|
||||
let testWin;
|
||||
let testDoc;
|
||||
let editor;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
const windowUrl = "data:text/xml,<?xml version='1.0'?>" +
|
||||
"<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
|
||||
" title='test for bug 660784' width='600' height='500'><hbox flex='1'/></window>";
|
||||
const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
|
||||
|
||||
testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
|
||||
|
||||
testWin.addEventListener("load", initEditor, false);
|
||||
}
|
||||
|
||||
function initEditor()
|
||||
{
|
||||
testWin.removeEventListener("load", initEditor, false);
|
||||
testDoc = testWin.document;
|
||||
|
||||
let hbox = testDoc.querySelector("hbox");
|
||||
|
||||
editor = new SourceEditor();
|
||||
let config = {
|
||||
showLineNumbers: true,
|
||||
placeholderText: "foobarbaz",
|
||||
tabSize: 7,
|
||||
expandTab: true,
|
||||
};
|
||||
|
||||
editor.init(hbox, config, editorLoaded);
|
||||
}
|
||||
|
||||
function editorLoaded()
|
||||
{
|
||||
ok(editor.editorElement, "editor loaded");
|
||||
|
||||
is(editor.parentElement, testDoc.querySelector("hbox"),
|
||||
"parentElement is correct");
|
||||
|
||||
editor.focus();
|
||||
|
||||
is(editor.getMode(), SourceEditor.DEFAULTS.MODE, "default editor mode");
|
||||
|
||||
// Test general editing methods.
|
||||
|
||||
ok(!editor.canUndo(), "canUndo() works (nothing to undo), just loaded");
|
||||
|
||||
ok(!editor.readOnly, "editor is not read-only");
|
||||
|
||||
is(editor.getText(), "foobarbaz", "placeholderText works");
|
||||
|
||||
is(editor.getText().length, editor.getCharCount(),
|
||||
"getCharCount() is correct");
|
||||
|
||||
is(editor.getText(3, 5), "ba", "getText() range works");
|
||||
|
||||
editor.setText("source-editor");
|
||||
|
||||
is(editor.getText(), "source-editor", "setText() works");
|
||||
|
||||
editor.setText("code", 0, 6);
|
||||
|
||||
is(editor.getText(), "code-editor", "setText() range works");
|
||||
|
||||
ok(editor.canUndo(), "canUndo() works (things to undo)");
|
||||
ok(!editor.canRedo(), "canRedo() works (nothing to redo yet)");
|
||||
|
||||
editor.undo();
|
||||
|
||||
is(editor.getText(), "source-editor", "undo() works");
|
||||
|
||||
ok(editor.canRedo(), "canRedo() works (things to redo)");
|
||||
|
||||
editor.redo();
|
||||
|
||||
is(editor.getText(), "code-editor", "redo() works");
|
||||
|
||||
// Test selection methods.
|
||||
|
||||
editor.setSelection(0, 4);
|
||||
|
||||
is(editor.getSelectedText(), "code", "getSelectedText() works");
|
||||
|
||||
let selection = editor.getSelection();
|
||||
ok(selection.start == 0 && selection.end == 4, "getSelection() works");
|
||||
|
||||
editor.dropSelection();
|
||||
|
||||
selection = editor.getSelection();
|
||||
ok(selection.start == 4 && selection.end == 4, "dropSelection() works");
|
||||
|
||||
editor.setCaretOffset(7);
|
||||
is(editor.getCaretOffset(), 7, "setCaretOffset() works");
|
||||
|
||||
// Test grouped changes.
|
||||
|
||||
editor.setText("foobar");
|
||||
|
||||
editor.startCompoundChange();
|
||||
|
||||
editor.setText("foo1");
|
||||
editor.setText("foo2");
|
||||
editor.setText("foo3");
|
||||
|
||||
editor.endCompoundChange();
|
||||
|
||||
is(editor.getText(), "foo3", "editor content is correct");
|
||||
|
||||
editor.undo();
|
||||
is(editor.getText(), "foobar", "compound change undo() works");
|
||||
|
||||
editor.redo();
|
||||
is(editor.getText(), "foo3", "compound change redo() works");
|
||||
|
||||
// Minimal keyboard usage tests.
|
||||
|
||||
ok(editor.hasFocus(), "editor has focus");
|
||||
|
||||
editor.setText("code-editor");
|
||||
editor.setCaretOffset(7);
|
||||
EventUtils.synthesizeKey(".", {}, testWin);
|
||||
|
||||
is(editor.getText(), "code-ed.itor", "focus() and typing works");
|
||||
|
||||
EventUtils.synthesizeKey("a", {}, testWin);
|
||||
|
||||
is(editor.getText(), "code-ed.aitor", "typing works");
|
||||
|
||||
is(editor.getCaretOffset(), 9, "caret moved");
|
||||
|
||||
EventUtils.synthesizeKey("VK_LEFT", {}, testWin);
|
||||
|
||||
is(editor.getCaretOffset(), 8, "caret moved to the left");
|
||||
|
||||
EventUtils.synthesizeKey("a", {accelKey: true}, testWin);
|
||||
|
||||
is(editor.getSelectedText(), "code-ed.aitor",
|
||||
"select all worked");
|
||||
|
||||
EventUtils.synthesizeKey("x", {accelKey: true}, testWin);
|
||||
|
||||
ok(!editor.getText(), "cut works");
|
||||
|
||||
EventUtils.synthesizeKey("v", {accelKey: true}, testWin);
|
||||
EventUtils.synthesizeKey("v", {accelKey: true}, testWin);
|
||||
|
||||
is(editor.getText(), "code-ed.aitorcode-ed.aitor", "paste works");
|
||||
|
||||
editor.setText("foo");
|
||||
|
||||
EventUtils.synthesizeKey("a", {accelKey: true}, testWin);
|
||||
EventUtils.synthesizeKey("c", {accelKey: true}, testWin);
|
||||
EventUtils.synthesizeKey("v", {accelKey: true}, testWin);
|
||||
EventUtils.synthesizeKey("v", {accelKey: true}, testWin);
|
||||
|
||||
is(editor.getText(), "foofoo", "ctrl-a, c, v, v works");
|
||||
|
||||
is(editor.getCaretOffset(), 6, "caret location is correct");
|
||||
|
||||
EventUtils.synthesizeKey(".", {}, testWin);
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, testWin);
|
||||
|
||||
is(editor.getText(), "foofoo. ", "Tab works");
|
||||
|
||||
is(editor.getCaretOffset(), 14, "caret location is correct");
|
||||
|
||||
// Test the Tab key.
|
||||
|
||||
editor.setText("a\n b\n c");
|
||||
editor.setCaretOffset(0);
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, testWin);
|
||||
is(editor.getText(), " a\n b\n c", "Tab works");
|
||||
|
||||
// Code editor specific tests. These are not applicable when the textarea
|
||||
// fallback is used.
|
||||
let component = Services.prefs.getCharPref(SourceEditor.PREFS.COMPONENT);
|
||||
if (component != "textarea") {
|
||||
editor.setMode(SourceEditor.MODES.JAVASCRIPT);
|
||||
is(editor.getMode(), SourceEditor.MODES.JAVASCRIPT, "setMode() works");
|
||||
|
||||
editor.setSelection(0, editor.getCharCount() - 1);
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, testWin);
|
||||
is(editor.getText(), " a\n b\n c", "lines indented");
|
||||
|
||||
EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, testWin);
|
||||
is(editor.getText(), " a\n b\n c", "lines outdented (shift-tab)");
|
||||
|
||||
testBackspaceKey();
|
||||
testReturnKey();
|
||||
}
|
||||
|
||||
// Test the read-only mode.
|
||||
|
||||
editor.setText("foofoo");
|
||||
|
||||
editor.readOnly = true;
|
||||
EventUtils.synthesizeKey("b", {}, testWin);
|
||||
is(editor.getText(), "foofoo", "editor is now read-only (keyboard)");
|
||||
|
||||
editor.setText("foobar");
|
||||
is(editor.getText(), "foobar", "editor allows programmatic changes (setText)");
|
||||
|
||||
editor.readOnly = false;
|
||||
|
||||
editor.setCaretOffset(editor.getCharCount());
|
||||
EventUtils.synthesizeKey("-", {}, testWin);
|
||||
is(editor.getText(), "foobar-", "editor is now editable again");
|
||||
|
||||
// Test the Selection event.
|
||||
|
||||
editor.setText("foobarbaz");
|
||||
|
||||
editor.setSelection(1, 4);
|
||||
|
||||
let event = null;
|
||||
|
||||
let eventHandler = function(aEvent) {
|
||||
event = aEvent;
|
||||
};
|
||||
editor.addEventListener(SourceEditor.EVENTS.SELECTION, eventHandler);
|
||||
|
||||
editor.setSelection(0, 3);
|
||||
|
||||
ok(event, "selection event fired");
|
||||
ok(event.oldValue.start == 1 && event.oldValue.end == 4,
|
||||
"event.oldValue is correct");
|
||||
ok(event.newValue.start == 0 && event.newValue.end == 3,
|
||||
"event.newValue is correct");
|
||||
|
||||
event = null;
|
||||
editor.dropSelection();
|
||||
|
||||
ok(event, "selection dropped");
|
||||
ok(event.oldValue.start == 0 && event.oldValue.end == 3,
|
||||
"event.oldValue is correct");
|
||||
ok(event.newValue.start == 3 && event.newValue.end == 3,
|
||||
"event.newValue is correct");
|
||||
|
||||
event = null;
|
||||
EventUtils.synthesizeKey("a", {accelKey: true}, testWin);
|
||||
|
||||
ok(event, "select all worked");
|
||||
ok(event.oldValue.start == 3 && event.oldValue.end == 3,
|
||||
"event.oldValue is correct");
|
||||
ok(event.newValue.start == 0 && event.newValue.end == 9,
|
||||
"event.newValue is correct");
|
||||
|
||||
event = null;
|
||||
editor.removeEventListener(SourceEditor.EVENTS.SELECTION, eventHandler);
|
||||
editor.dropSelection();
|
||||
|
||||
ok(!event, "selection event listener removed");
|
||||
|
||||
// Test the TextChanged event.
|
||||
|
||||
editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, eventHandler);
|
||||
|
||||
EventUtils.synthesizeKey(".", {}, testWin);
|
||||
|
||||
ok(event, "the TextChanged event fired after keypress");
|
||||
is(event.start, 9, "event.start is correct");
|
||||
is(event.removedCharCount, 0, "event.removedCharCount is correct");
|
||||
is(event.addedCharCount, 1, "event.addedCharCount is correct");
|
||||
|
||||
let chars = editor.getText().length;
|
||||
event = null;
|
||||
|
||||
EventUtils.synthesizeKey("a", {accelKey: true}, testWin);
|
||||
EventUtils.synthesizeKey("c", {accelKey: true}, testWin);
|
||||
|
||||
editor.setCaretOffset(chars);
|
||||
|
||||
EventUtils.synthesizeKey("v", {accelKey: true}, testWin);
|
||||
|
||||
ok(event, "the TextChanged event fired after paste");
|
||||
is(event.start, chars, "event.start is correct");
|
||||
is(event.removedCharCount, 0, "event.removedCharCount is correct");
|
||||
is(event.addedCharCount, chars, "event.addedCharCount is correct");
|
||||
|
||||
editor.setText("line1\nline2\nline3");
|
||||
chars = editor.getText().length;
|
||||
|
||||
event = null;
|
||||
|
||||
editor.setText("a\nline4\nline5", chars);
|
||||
|
||||
ok(event, "the TextChanged event fired after setText()");
|
||||
is(event.start, chars, "event.start is correct");
|
||||
is(event.removedCharCount, 0, "event.removedCharCount is correct");
|
||||
is(event.addedCharCount, 13, "event.addedCharCount is correct");
|
||||
|
||||
editor.setText("line3b\nline4b\nfoo", 12, 24);
|
||||
|
||||
ok(event, "the TextChanged event fired after setText() again");
|
||||
is(event.start, 12, "event.start is correct");
|
||||
is(event.removedCharCount, 12, "event.removedCharCount is correct");
|
||||
is(event.addedCharCount, 17, "event.addedCharCount is correct");
|
||||
|
||||
editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, eventHandler);
|
||||
|
||||
// Done.
|
||||
|
||||
editor.destroy();
|
||||
ok(!editor.parentElement && !editor.editorElement, "destroy() works");
|
||||
|
||||
testWin.close();
|
||||
|
||||
testWin = testDoc = editor = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function testBackspaceKey()
|
||||
{
|
||||
editor.setText(" a\n b\n c");
|
||||
editor.setCaretOffset(7);
|
||||
EventUtils.synthesizeKey("VK_BACK_SPACE", {}, testWin);
|
||||
is(editor.getText(), "a\n b\n c", "line outdented (Backspace)");
|
||||
|
||||
editor.undo();
|
||||
|
||||
editor.setCaretOffset(6);
|
||||
EventUtils.synthesizeKey("VK_BACK_SPACE", {}, testWin);
|
||||
is(editor.getText(), " a\n b\n c", "backspace one char works");
|
||||
}
|
||||
|
||||
function testReturnKey()
|
||||
{
|
||||
editor.setText(" a\n b\n c");
|
||||
|
||||
editor.setCaretOffset(8);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, testWin);
|
||||
EventUtils.synthesizeKey("x", {}, testWin);
|
||||
|
||||
let lineDelimiter = editor.getLineDelimiter();
|
||||
ok(lineDelimiter, "we have the line delimiter");
|
||||
|
||||
is(editor.getText(), " a" + lineDelimiter + " x\n b\n c",
|
||||
"return maintains indentation");
|
||||
|
||||
editor.setCaretOffset(12 + lineDelimiter.length);
|
||||
EventUtils.synthesizeKey("z", {}, testWin);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, testWin);
|
||||
EventUtils.synthesizeKey("y", {}, testWin);
|
||||
is(editor.getText(), " a" + lineDelimiter +
|
||||
" z" + lineDelimiter + " yx\n b\n c",
|
||||
"return maintains indentation (again)");
|
||||
}
|
||||
|
@ -114,10 +114,3 @@
|
||||
<!ENTITY webConsoleCmd.label "Web Console">
|
||||
<!ENTITY webConsoleCmd.accesskey "W">
|
||||
<!ENTITY webConsoleCmd.commandkey "k">
|
||||
|
||||
<!-- LOCALIZATION NOTE (textbox.placeholder1): This is some placeholder text
|
||||
- that appears when the Scratchpad's text area is empty and unfocused.
|
||||
- It should be a one-line JavaScript comment, i.e., preceded by '//'
|
||||
-->
|
||||
<!ENTITY textbox.placeholder1 "// Enter some JavaScript, select it, right click and select Run, Inspect or Display.">
|
||||
|
||||
|
@ -118,6 +118,7 @@
|
||||
<li><a href="about:license#chromium">Chromium License</a></li>
|
||||
<li><a href="about:license#hunspell-nl">Dutch Spellchecking Dictionary License</a></li>
|
||||
<li><a href="about:license#expat">Expat License</a></li>
|
||||
<li><a href="about:license#edl">Eclipse Distribution License</a></li>
|
||||
<li><a href="about:license#firebug">Firebug License</a></li>
|
||||
<li><a href="about:license#gfx-font-list">gfxFontList License</a></li>
|
||||
<li><a href="about:license#gears">Google Gears License</a></li>
|
||||
@ -2334,6 +2335,45 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
<hr>
|
||||
|
||||
<h1><a name="edl"></a>Eclipse Distribution License</h1>
|
||||
|
||||
<p>This license applies to certain files in the directory
|
||||
<span class="path">browser/devtools/sourceeditor/orion/</span>.</p>
|
||||
|
||||
<pre>
|
||||
Eclipse Distribution License - v 1.0
|
||||
|
||||
Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
* Neither the name of the Eclipse Foundation, Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</pre>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<h1><a name="firebug"></a>Firebug License</h1>
|
||||
|
||||
<p>This license applies to the code
|
||||
|
Loading…
Reference in New Issue
Block a user