Bug 922812 - Add an 'eval current top level function' command to scratchpad; r=past

This commit is contained in:
Nick Fitzgerald 2013-11-19 15:17:40 -08:00
Родитель 27e2b44c9e
Коммит 8db5f53579
8 изменённых файлов: 286 добавлений и 0 удалений

Просмотреть файл

@ -24,6 +24,7 @@ const BUTTON_POSITION_SAVE = 0;
const BUTTON_POSITION_CANCEL = 1;
const BUTTON_POSITION_DONT_SAVE = 2;
const BUTTON_POSITION_REVERT = 0;
const EVAL_FUNCTION_TIMEOUT = 1000; // milliseconds
const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
@ -75,6 +76,9 @@ XPCOMUtils.defineLazyGetter(this, "REMOTE_TIMEOUT", () =>
XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
"resource://gre/modules/ShortcutUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Reflect",
"resource://gre/modules/reflect.jsm");
// Because we have no constructor / destructor where we can log metrics we need
// to do so here.
let telemetry = new Telemetry();
@ -567,6 +571,168 @@ var Scratchpad = {
return deferred.promise;
},
/**
* Parse the text and return an AST. If we can't parse it, write an error
* comment and return false.
*/
_parseText: function SP__parseText(aText) {
try {
return Reflect.parse(aText);
} catch (e) {
this.writeAsErrorComment(DevToolsUtils.safeErrorString(e));
return false;
}
},
/**
* Determine if the given AST node location contains the given cursor
* position.
*
* @returns Boolean
*/
_containsCursor: function (aLoc, aCursorPos) {
// Our line numbers are 1-based, while CodeMirror's are 0-based.
const lineNumber = aCursorPos.line + 1;
const columnNumber = aCursorPos.ch;
if (aLoc.start.line <= lineNumber && aLoc.end.line >= lineNumber) {
if (aLoc.start.line === aLoc.end.line) {
return aLoc.start.column <= columnNumber
&& aLoc.end.column >= columnNumber;
}
if (aLoc.start.line == lineNumber) {
return columnNumber >= aLoc.start.column;
}
if (aLoc.end.line == lineNumber) {
return columnNumber <= aLoc.end.column;
}
return true;
}
return false;
},
/**
* Find the top level function AST node that the cursor is within.
*
* @returns Object|null
*/
_findTopLevelFunction: function SP__findTopLevelFunction(aAst, aCursorPos) {
for (let statement of aAst.body) {
switch (statement.type) {
case "FunctionDeclaration":
if (this._containsCursor(statement.loc, aCursorPos)) {
return statement;
}
break;
case "VariableDeclaration":
for (let decl of statement.declarations) {
if (!decl.init) {
continue;
}
if ((decl.init.type == "FunctionExpression"
|| decl.init.type == "ArrowExpression")
&& this._containsCursor(decl.loc, aCursorPos)) {
return decl;
}
}
break;
}
}
return null;
},
/**
* Get the source text associated with the given function statement.
*
* @param Object aFunction
* @param String aFullText
* @returns String
*/
_getFunctionText: function SP__getFunctionText(aFunction, aFullText) {
let functionText = "";
// Initially set to 0, but incremented first thing in the loop below because
// line numbers are 1 based, not 0 based.
let lineNumber = 0;
const { start, end } = aFunction.loc;
const singleLine = start.line === end.line;
for (let line of aFullText.split(/\n/g)) {
lineNumber++;
if (singleLine && start.line === lineNumber) {
functionText = line.slice(start.column, end.column);
break;
}
if (start.line === lineNumber) {
functionText += line.slice(start.column) + "\n";
continue;
}
if (end.line === lineNumber) {
functionText += line.slice(0, end.column);
break;
}
if (start.line < lineNumber && end.line > lineNumber) {
functionText += line + "\n";
}
}
return functionText;
},
/**
* Evaluate the top level function that the cursor is resting in.
*
* @returns Promise [text, error, result]
*/
evalTopLevelFunction: function SP_evalTopLevelFunction() {
const text = this.getText();
const ast = this._parseText(text);
if (!ast) {
return promise.resolve([text, undefined, undefined]);
}
const cursorPos = this.editor.getCursor();
const funcStatement = this._findTopLevelFunction(ast, cursorPos);
if (!funcStatement) {
return promise.resolve([text, undefined, undefined]);
}
let functionText = this._getFunctionText(funcStatement, text);
// TODO: This is a work around for bug 940086. It should be removed when
// that is fixed.
if (funcStatement.type == "FunctionDeclaration"
&& !functionText.startsWith("function ")) {
functionText = "function " + functionText;
funcStatement.loc.start.column -= 9;
}
// The decrement by one is because our line numbers are 1-based, while
// CodeMirror's are 0-based.
const from = {
line: funcStatement.loc.start.line - 1,
ch: funcStatement.loc.start.column
};
const to = {
line: funcStatement.loc.end.line - 1,
ch: funcStatement.loc.end.column
};
const marker = this.editor.markText(from, to, { className: "eval-text" });
setTimeout(() => marker.clear(), EVAL_FUNCTION_TIMEOUT);
return this.evaluate(functionText);
},
/**
* Writes out a primitive value as a comment. This handles values which are
* to be printed directly (number, string) as well as grips to values

Просмотреть файл

@ -63,6 +63,7 @@
<command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
<command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
<command id="sp-cmd-reloadAndRun" oncommand="Scratchpad.reloadAndRun();"/>
<command id="sp-cmd-evalFunction" oncommand="Scratchpad.evalTopLevelFunction();"/>
<command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
<command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
<command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
@ -108,6 +109,10 @@
key="&reloadAndRun.key;"
command="sp-cmd-reloadAndRun"
modifiers="accel,shift"/>
<key id="sp-key-evalFunction"
key="&evalFunction.key;"
command="sp-cmd-evalFunction"
modifiers="accel"/>
<key id="sp-key-errorConsole"
key="&errorConsoleCmd.commandkey;"
command="sp-cmd-errorConsole"
@ -213,6 +218,11 @@
key="sp-key-reloadAndRun"
accesskey="&reloadAndRun.accesskey;"
command="sp-cmd-reloadAndRun"/>
<menuitem id="sp-text-evalFunction"
label="&evalFunction.label;"
key="sp-key-evalFunction"
accesskey="&evalFunction.accesskey;"
command="sp-cmd-evalFunction"/>
</menupopup>
</menu>
@ -314,6 +324,11 @@
accesskey="&display.accesskey;"
key="sp-key-display"
command="sp-cmd-display"/>
<menuitem id="sp-text-evalFunction"
label="&evalFunction.label;"
key="sp-key-evalFunction"
accesskey="&evalFunction.accesskey;"
command="sp-cmd-evalFunction"/>
</menupopup>
</popupset>

Просмотреть файл

@ -4,6 +4,7 @@ support-files = head.js
[browser_scratchpad_browser_last_window_closing.js]
[browser_scratchpad_reset_undo.js]
[browser_scratchpad_display_outputs_errors.js]
[browser_scratchpad_eval_func.js]
[browser_scratchpad_goto_line_ui.js]
[browser_scratchpad_reload_and_run.js]
[browser_scratchpad_display_non_error_exceptions.js]

Просмотреть файл

@ -0,0 +1,86 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
openScratchpad(runTests);
}, true);
content.location = "data:text/html;charset=utf8,test Scratchpad eval function.";
}
function reportErrorAndQuit(error) {
DevToolsUtils.reportException("browser_scratchpad_eval_func.js", error);
ok(false);
finish();
}
function runTests(sw)
{
const sp = sw.Scratchpad;
let foo = "" + function main() { console.log(1); };
let bar = "var bar = " + (() => { console.log(2); });
const fullText =
foo + "\n" +
"\n" +
bar + "\n"
sp.setText(fullText);
// On the function declaration.
sp.editor.setCursor({ line: 0, ch: 18 });
sp.evalTopLevelFunction()
.then(([text, error, result]) => {
is(text, foo, "Should re-eval foo.");
ok(!error, "Should not have got an error.");
ok(result, "Should have got a result.");
})
// On the arrow function.
.then(() => {
sp.editor.setCursor({ line: 2, ch: 18 });
return sp.evalTopLevelFunction();
})
.then(([text, error, result]) => {
is(text, bar.replace("var ", ""), "Should re-eval bar.");
ok(!error, "Should not have got an error.");
ok(result, "Should have got a result.");
})
// On the empty line.
.then(() => {
sp.editor.setCursor({ line: 1, ch: 0 });
return sp.evalTopLevelFunction();
})
.then(([text, error, result]) => {
is(text, fullText,
"Should get full text back since we didn't find a specific function.");
ok(!error, "Should not have got an error.");
ok(!result, "Should not have got a result.");
})
// Syntax error.
.then(() => {
sp.setText("function {}");
sp.editor.setCursor({ line: 0, ch: 9 });
return sp.evalTopLevelFunction();
})
.then(([text, error, result]) => {
is(text, "function {}",
"Should get the full text back since there was a parse error.");
ok(!error, "Should not have got an error");
ok(!result, "Should not have got a result");
ok(sp.getText().contains("SyntaxError"),
"We should have written the syntax error to the scratchpad.");
})
.then(finish, reportErrorAndQuit);
}

Просмотреть файл

@ -78,6 +78,7 @@ const CM_MAPPING = [
"clearHistory",
"openDialog",
"cursorCoords",
"markText",
"refresh"
];

Просмотреть файл

@ -120,3 +120,10 @@
- the same name in browser.dtd.
-->
<!ENTITY errorConsoleCmd.commandkey "j">
<!-- LOCALIZATION NOTE (evalFunction.label): This command allows the developer
- to evaluate the top-level function that the cursor is currently at.
-->
<!ENTITY evalFunction.label "Evaluate Current Function">
<!ENTITY evalFunction.accesskey "v">
<!ENTITY evalFunction.key "e">

Просмотреть файл

@ -189,6 +189,11 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */
color: white;
}
/* Highlight for evaluating current statement. */
div.CodeMirror span.eval-text {
background-color: #556;
}
.cm-s-mozilla .CodeMirror-linenumber { /* line number text */
color: #5f7387;
}

Просмотреть файл

@ -188,6 +188,11 @@ div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */
color: black;
}
/* Highlight for evaluating current statement. */
div.CodeMirror span.eval-text {
background-color: #ccd;
}
.cm-s-mozilla .CodeMirror-linenumber { /* line number text */
color: #667380;
}