зеркало из https://github.com/mozilla/gecko-dev.git
Bug 922812 - Add an 'eval current top level function' command to scratchpad; r=past
This commit is contained in:
Родитель
27e2b44c9e
Коммит
8db5f53579
|
@ -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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче