Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
742 lines (634 sloc) 23 KB
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>JavaScript Shell Extended</title><script type="text/javascript" id="inputEval">function line1EvalShellInput() { _win.eval("try{ Shell.printAnswer(eval('with(Shell._scope) with(Shell.shellCommands) { eval(Shell.question) }')); } catch(er) { Shell.printError(er); }; setTimeout(Shell.refocus, 0);") }</script>
<script type="text/javascript">
//<![CDATA[
// JavaScript Shell Extended
if ( typeof JSON == "undefined" ) { // load JSON support (via json2.js) if it doesn't already exist
document.getElementsByTagName("head")[0].appendChild(document.createElement("script")).src = "json2.js";
}
var
histList = [""],
histPos = 0,
_scope = {},
_win, // a top-level context
question,
_in,
_out,
tooManyMatches = null,
lastError = null;
function _JS_version_check(start, end) { // get latest JS version supported & redefine input eval function in it
var scripts = [];
if ( typeof start == "undefined" ) var start = _JS_version_check.start;
if ( typeof end == "undefined" ) var end = _JS_version_check.end;
for (var i = start; i <= end; i++) {
var s = document.createElement("script");
s.type = "application/javascript;version=" + parseFloat((i/10).toFixed(1)).toString();
s.src = "data:application/javascript," + encodeURIComponent(_JS_version_check.code + ";\nvar JavaScript_version = " + parseFloat((i/10).toFixed(1)).toString());
document.getElementsByTagName("head")[0].appendChild(s);
scripts.push(s);
}
for (var i=0; i<scripts.length; i++) { // clean up <script>s
document.getElementsByTagName("head")[0].removeChild(scripts[i]);
}
}
_JS_version_check.start = 10; // start:Float*10
_JS_version_check.end = 20; // highest version to test for (end:Float*10)
_JS_version_check.code = document.getElementById("inputEval").innerHTML;
_JS_version_check();
function refocus() {
_in.blur(); // Needed for Mozilla to scroll correctly.
_in.focus();
}
function init() {
_in = document.getElementById("input");
_out = document.getElementById("output");
_win = window;
if (opener && !opener.closed) {
println("Using bookmarklet version of shell: commands will run in opener's context.", "message");
_win = opener;
}
initTarget();
recalculateInputHeight();
refocus();
}
function initTarget() {
_win.Shell = window;
_win.print = shellCommands.print;
}
// Unless the user is selected something, refocus the textbox.
// (requested by caillon, brendan, asa)
function keepFocusInTextbox(e) {
var g = false;
if (typeof e.target != "undefined") {
g = e.target;
}
else if (typeof e.srcElement != "undefined") {
g = e.srcElement;
}
if (g) {
while (!g.tagName)
g = g.parentNode;
var t = g.tagName.toUpperCase();
if (t=="A" || t=="INPUT")
return;
if (window.getSelection) {
// Mozilla
if (String(window.getSelection()))
return;
}
else if (document.getSelection) {
// Opera? Netscape 4?
if (document.getSelection())
return;
}
else {
// IE
if ( document.selection.createRange().text )
return;
}
}
refocus();
}
function inputKeydown(e) {
// Use onkeydown because IE doesn't support onkeypress for arrow keys
//alert(e.keyCode + " ^ " + e.keycode);
if (e.shiftKey && e.keyCode == 13) { // shift-enter
// don't do anything; allow the shift-enter to insert a line break as normal
} else if (e.keyCode == 13) { // enter
// execute the input on enter
try { go(); } catch(er) { alert(er); };
setTimeout(function() { _in.value = ""; }, 0); // can't preventDefault on input, so clear it later
} else if (e.keyCode == 38) { // up
// go up in history if at top or ctrl-up
if (e.ctrlKey || caretInFirstLine(_in))
hist(true);
} else if (e.keyCode == 40) { // down
// go down in history if at end or ctrl-down
if (e.ctrlKey || caretInLastLine(_in))
hist(false);
} else if (e.keyCode == 9) { // tab
tabcomplete();
setTimeout(function() { refocus(); }, 0); // refocus because tab was hit
} else { }
setTimeout(recalculateInputHeight, 0);
//return true;
};
function caretInFirstLine(textbox) {
// IE doesn't support selectionStart/selectionEnd
if (textbox.selectionStart == undefined)
return true;
var firstLineBreak = textbox.value.indexOf("\n");
return ((firstLineBreak == -1) || (textbox.selectionStart <= firstLineBreak));
}
function caretInLastLine(textbox) {
// IE doesn't support selectionStart/selectionEnd
if (textbox.selectionEnd == undefined)
return true;
var lastLineBreak = textbox.value.lastIndexOf("\n");
return (textbox.selectionEnd > lastLineBreak);
}
function recalculateInputHeight() {
var rows = _in.value.split(/\n/).length
+ 1 // prevent scrollbar flickering in Mozilla
+ (window.opera ? 1 : 0); // leave room for scrollbar in Opera
if (_in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0.
_in.rows = rows;
}
function println(s, type) {
if((s=String(s))) {
var newdiv = document.createElement("div");
newdiv.appendChild(document.createTextNode(s));
newdiv.className = type;
_out.appendChild(newdiv);
return newdiv;
}
}
function printWithRunin(h, s, type) {
var div = println(s, type);
var head = document.createElement("strong");
head.appendChild(document.createTextNode(h + ": "));
div.insertBefore(head, div.firstChild);
}
var shellCommands = {
load : function load(uri, printLoadMsg, forcedVersion) {
if ( typeof printLoadMsg == "undefined" ) printLoadMsg = true;
if ( Object.prototype.toString.call(uri) == "[object Array]" ) {
for (var i = 0, len = uri.length; i < len; i++) {
load(uri[i], false, forcedVersion);
}
if (printLoadMsg) println("Loading " + uri.join(", ") + "...", "message");
return;
}
var s = _win.document.createElement("script");
s.type = "text/javascript";
if ( typeof forcedVersion == "number" ) s.type += ";version=" + forcedVersion.toString();
else if ( typeof JavaScript_version != "undefined" && typeof forcedVersion == "undefined" ) s.type += ";version=" + JavaScript_version.toString();
s.src = uri;
_win.document.getElementsByTagName("head")[0].appendChild(s);
if (printLoadMsg) println("Loading " + uri + "...", "message");
},
clear : function clear() {
while (_out.childNodes[0])
_out.removeChild(_out.childNodes[0]);
},
print : function print() {
println(Array.prototype.slice.call(arguments).join(" "), "print");
},
// the normal function, "print", shouldn't return a value
// (suggested by brendan; later noticed it was a problem when showing others)
pr : function pr(s) {
shellCommands.print(s); // need to specify shellCommands so it doesn't try window.print()!
return s;
},
props : function props(e, onePerLine) {
if (e === null) {
println("props called with null argument", "error");
return;
}
if (e === undefined) {
println("props called with undefined argument", "error");
return;
}
var ns = ["Methods", "Fields", "Unreachables"];
var as = [[], [], []]; // array of (empty) arrays of arrays!
var p, j, i; // loop variables, several used multiple times
var protoLevels = 0;
for (p = e; p; p = p.__proto__) {
for (i=0; i<ns.length; ++i)
as[i][protoLevels] = [];
++protoLevels;
}
for(var a in e) {
// Shortcoming: doesn't check that VALUES are the same in object and prototype.
var protoLevel = -1;
try
{
for (p = e; p && (a in p); p = p.__proto__)
++protoLevel;
}
catch(er) { protoLevel = 0; } // "in" operator throws when param to props() is a string
var type = 1;
try {
if ((typeof e[a]) == "function")
type = 0;
}
catch (er) { type = 2; }
as[type][protoLevel].push(a);
}
function times(s, n) { return n ? s + times(s, n-1) : ""; }
for (j=0; j<protoLevels; ++j)
for (i=0;i<ns.length;++i)
if (as[i][j].length)
printWithRunin(
ns[i] + times(" of prototype", j),
(onePerLine ? "\n\n" : "") + as[i][j].sort().join(onePerLine ? "\n" : ", ") + (onePerLine ? "\n\n" : ""),
"propList"
);
},
blink : function blink(node) {
if (!node) throw("blink: argument is null or undefined.");
if (node.nodeType == null) throw("blink: argument must be a node.");
if (node.nodeType == 3) throw("blink: argument must not be a text node");
if (node.documentElement) throw("blink: argument must not be the document object");
function setOutline(o) {
return function() {
if (node.style.outline != node.style.bogusProperty) {
// browser supports outline (Firefox 1.1 and newer, CSS3, Opera 8).
node.style.outline = o;
}
else if (node.style.MozOutline != node.style.bogusProperty) {
// browser supports MozOutline (Firefox 1.0.x and older)
node.style.MozOutline = o;
}
else {
// browser only supports border (IE). border is a fallback because it moves things around.
node.style.border = o;
}
}
}
function focusIt(a) {
return function() {
a.focus();
}
}
if (node.ownerDocument) {
var windowToFocusNow = (node.ownerDocument.defaultView || node.ownerDocument.parentWindow); // Moz vs. IE
if (windowToFocusNow)
setTimeout(focusIt(windowToFocusNow.top), 0);
}
for(var i=1;i<7;++i)
setTimeout(setOutline((i%2)?'3px solid red':'none'), i*100);
setTimeout(focusIt(window), 800);
setTimeout(focusIt(_in), 810);
},
scope : function scope(sc) {
if (!sc) sc = {};
_scope = sc;
println("Scope is now " + sc + ". If a variable is not found in this scope, window will also be searched. New variables will still go on window.", "message");
},
mathHelp : function mathHelp() {
printWithRunin("Math constants", "E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2", "propList");
printWithRunin("Math methods", "abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan", "propList");
},
ans : undefined
};
function hist(up) {
// histList[0] = first command entered, [1] = second, etc.
// type something, press up --> thing typed is now in "limbo"
// (last item in histList) and should be reachable by pressing
// down again.
var L = histList.length;
if (L == 1)
return;
if (up) {
if (histPos == L-1) {
// Save this entry in case the user hits the down key.
histList[histPos] = _in.value;
}
if (histPos > 0) {
histPos--;
// Use a timeout to prevent up from moving cursor within new text
// Set to nothing first for the same reason
setTimeout(
function() {
_in.value = '';
_in.value = histList[histPos];
var caretPos = _in.value.length;
if (_in.setSelectionRange)
_in.setSelectionRange(caretPos, caretPos);
},
0
);
}
}
else { // down
if (histPos < L-1) {
histPos++;
_in.value = histList[histPos];
}
else if (histPos == L-1) {
// Already on the current entry: clear but save
if (_in.value) {
histList[histPos] = _in.value;
++histPos;
_in.value = "";
}
}
}
}
function tabcomplete() {
/*
* Working backwards from s[from], find the spot
* where this expression starts. It will scan
* until it hits a mismatched ( or a space,
* but it skips over quoted strings.
* If stopAtDot is true, stop at a '.'
*/
function findbeginning(s, from, stopAtDot) {
/*
* Complicated function.
*
* Return true if s[i] == q BUT ONLY IF
* s[i-1] is not a backslash.
*/
function equalButNotEscaped(s,i,q) {
if(s.charAt(i) != q) // not equal go no further
return false;
if(i==0) // beginning of string
return true;
if(s.charAt(i-1) == '\\') // escaped?
return false;
return true;
}
var nparens = 0;
var i;
for(i=from; i>=0; i--) {
if(s.charAt(i) == ' ')
break;
if(stopAtDot && s.charAt(i) == '.')
break;
if(s.charAt(i) == ')')
nparens++;
else if(s.charAt(i) == '(')
nparens--;
if(nparens < 0)
break;
// skip quoted strings
if(s.charAt(i) == '\'' || s.charAt(i) == '\"') {
//dump("skipping quoted chars: ");
var quot = s.charAt(i);
i--;
while(i >= 0 && !equalButNotEscaped(s,i,quot)) {
//dump(s.charAt(i));
i--;
}
//dump("\n");
}
}
return i;
}
// XXX should be used more consistently (instead of using selectionStart/selectionEnd throughout code)
// XXX doesn't work in IE, even though it contains IE-specific code
function getcaretpos(inp) {
if(inp.selectionEnd != null)
return inp.selectionEnd;
if(inp.createTextRange) {
var docrange = _win.Shell.document.selection.createRange();
var inprange = inp.createTextRange();
if (inprange.setEndPoint)
{
inprange.setEndPoint('EndToStart', docrange);
return inprange.text.length;
}
}
return inp.value.length; // sucks, punt
}
function setselectionto(inp,pos) {
if(inp.selectionStart) {
inp.selectionStart = inp.selectionEnd = pos;
}
else if(inp.createTextRange) {
var docrange = _win.Shell.document.selection.createRange();
var inprange = inp.createTextRange();
inprange.move('character',pos);
inprange.select();
}
else { // err...
/*
inp.select();
if(_win.Shell.document.getSelection())
_win.Shell.document.getSelection() = "";
*/
}
}
// get position of cursor within the input box
var caret = getcaretpos(_in);
if(caret) {
//dump("----\n");
var dotpos, spacepos, complete, obj;
//dump("caret pos: " + caret + "\n");
// see if there's a dot before here
dotpos = findbeginning(_in.value, caret-1, true);
//dump("dot pos: " + dotpos + "\n");
if(dotpos == -1 || _in.value.charAt(dotpos) != '.') {
dotpos = caret;
//dump("changed dot pos: " + dotpos + "\n");
}
// look backwards for a non-variable-name character
spacepos = findbeginning(_in.value, dotpos-1, false);
//dump("space pos: " + spacepos + "\n");
// get the object we're trying to complete on
if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret) {
// try completing function args
if(_in.value.charAt(dotpos) == '(' ||
(_in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos))
{
var fn,fname;
var from = (_in.value.charAt(dotpos) == '(') ? dotpos : spacepos;
spacepos = findbeginning(_in.value, from-1, false);
fname = _in.value.substr(spacepos+1,from-(spacepos+1));
//dump("fname: " + fname + "\n");
try {
with(_win.Shell._scope)
with(_win)
with(Shell.shellCommands)
fn = eval(fname);
}
catch(er) {
//dump('fn is not a valid object\n');
return;
}
if(fn == undefined) {
//dump('fn is undefined');
return;
}
if(fn instanceof Function) {
// Print function definition, including argument names, but not function body
if(!fn.toString().match(/function .+?\(\) +\{\n +\[native code\]\n\}/))
println(fn.toString().match(/function .+?\(.*?\)/), "tabcomplete");
}
return;
}
else
obj = _win;
}
else {
var objname = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
//dump("objname: |" + objname + "|\n");
try {
with(_win.Shell._scope)
with(_win)
obj = eval(objname);
}
catch(er) {
printError(er);
return;
}
if(obj == undefined) {
// sometimes this is tabcomplete's fault, so don't print it :(
// e.g. completing from "print(document.getElements"
// println("Can't complete from null or undefined expression " + objname, "error");
return;
}
}
//dump("obj: " + obj + "\n");
// get the thing we're trying to complete
if(dotpos == caret) {
if(spacepos+1 == dotpos || spacepos == dotpos) {
// nothing to complete
//dump("nothing to complete\n");
return;
}
complete = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
}
else {
complete = _in.value.substr(dotpos+1,caret-(dotpos+1));
}
//dump("complete: " + complete + "\n");
// ok, now look at all the props/methods of this obj
// and find ones starting with 'complete'
var matches = [];
var bestmatch = null;
for(var a in obj) {
//a = a.toString();
//XXX: making it lowercase could help some cases,
// but screws up my general logic.
if(a.substr(0,complete.length) == complete) {
matches.push(a);
////dump("match: " + a + "\n");
// if no best match, this is the best match
if(bestmatch == null) {
bestmatch = a;
}
else {
// the best match is the longest common string
function min(a,b){ return ((a<b)?a:b); }
var i;
for(i=0; i< min(bestmatch.length, a.length); i++) {
if(bestmatch.charAt(i) != a.charAt(i))
break;
}
bestmatch = bestmatch.substr(0,i);
////dump("bestmatch len: " + i + "\n");
}
////dump("bestmatch: " + bestmatch + "\n");
}
}
bestmatch = (bestmatch || "");
////dump("matches: " + matches + "\n");
var objAndComplete = (objname || obj) + "." + bestmatch;
//dump("matches.length: " + matches.length + ", tooManyMatches: " + tooManyMatches + ", objAndComplete: " + objAndComplete + "\n");
if(matches.length > 1 && (tooManyMatches == objAndComplete || matches.length <= 10)) {
printWithRunin("Matches: ", matches.join(', '), "tabcomplete");
tooManyMatches = null;
}
else if(matches.length > 10) {
println(matches.length + " matches. Press tab again to see them all", "tabcomplete");
tooManyMatches = objAndComplete;
}
else {
tooManyMatches = null;
}
if(bestmatch != "") {
var sstart;
if(dotpos == caret) {
sstart = spacepos+1;
}
else {
sstart = dotpos+1;
}
_in.value = _in.value.substr(0, sstart)
+ bestmatch
+ _in.value.substr(caret);
setselectionto(_in,caret + (bestmatch.length - complete.length));
}
}
}
function printQuestion(q) {
println(q, "input");
}
function printAnswer(a) {
if (a !== undefined) {
println(a, "normalOutput");
shellCommands.ans = a;
}
}
function printError(er) {
var lineNumberString;
lastError = er; // for debugging the shell
if (er.name)
{
// lineNumberString should not be "", to avoid a very wacky bug in IE 6.
lineNumberString = (er.lineNumber != undefined) ? (" on line " + er.lineNumber + ": ") : ": ";
println(er.name + lineNumberString + er.message, "error"); // Because IE doesn't have error.toString.
}
else
println(er, "error"); // Because security errors in Moz /only/ have toString.
}
function go(s) {
_in.value = question = s ? s : _in.value;
if (question == "")
return;
histList[histList.length-1] = question;
histList[histList.length] = "";
histPos = histList.length - 1;
// Unfortunately, this has to happen *before* the JavaScript is run, so that
// print() output will go in the right place.
_in.value='';
recalculateInputHeight();
printQuestion(question);
if (_win.closed) {
printError("Target window has been closed.");
return;
}
try { ("Shell" in _win) }
catch(er) {
printError("The JavaScript Shell cannot access variables in the target window. The most likely reason is that the target window now has a different page loaded and that page has a different hostname than the original page.");
return;
}
if (!("Shell" in _win))
initTarget(); // silent
// Evaluate Shell.question using _win's eval (this is why eval isn't in the |with|, IIRC).
line1EvalShellInput();
}
// foward all console.log calls to print
if ( typeof console != "object" ) var console = {};
if ( typeof console.log != "function" && typeof print == "function" ) console.log = function() { Shell.shellCommands.print.apply(this, Array.prototype.slice.call(arguments)) }
if (Object.prototype.__defineGetter__)
// obj.* short for [prop for (prop in obj) if (prop !== "*")]
Object.prototype.__defineGetter__("*", function () {
var props = [];
for (var prop in this)
if (prop !== "*")
props.push(prop);
return props;
});
window.onload = init;
document.onclick = function(e) { keepFocusInTextbox(e) };
//]]>
</script>
<!-- for http://ted.mielczarek.org/code/mozilla/extensiondev/ -->
<script type="text/javascript" src="chrome://extensiondev/content/rdfhistory.js"></script>
<script type="text/javascript" src="chrome://extensiondev/content/chromeShellExtras.js"></script>
<style type="text/css">
body { background: white; color: black; }
#output { white-space: pre; white-space: -moz-pre-wrap; } /* Preserve line breaks, but wrap too if browser supports it */
h3 { margin-top: 0; margin-bottom: 0em; }
h3 + div { margin: 0; }
form { margin: 0; padding: 0; }
#input { width: 100%; border: none; padding: 0; overflow: auto; }
.input { color: blue; background: white; font: inherit; font-weight: bold; margin-top: .5em; /* background: #E6E6FF; */ }
.normalOutput { color: black; background: white; }
.print { color: brown; background: white; }
.error { color: red; background: white; }
.propList { color: green; background: white; }
.message { color: green; background: white; }
.tabcomplete { color: purple; background: white; }
</style>
</head>
<body>
<h3>JavaScript Shell Extended</h3>
<div>Features: autocompletion of property names with Tab, multiline input with Shift+Enter, input history with (Ctrl+) Up/Down, <a accesskey="M" href="#" onclick="go('scope(Math); mathHelp();');return false;" title="Accesskey: M">Math</a>, <a accesskey="H" href="http://code.eligrey.com/shell/help.html" title="Accesskey: H">help</a></div><div>Values and functions: ans, print(string), <a accesskey="P" href="#" onclick="go('props(ans)');return false;" title="Accesskey: P">props(object)</a>, <a accesskey="B" href="#" onclick="go('blink(ans)');return false" title="Accesskey: B">blink(node)</a>, <a accesskey="C" href="#" onclick="go('clear()');return false;" title="Accesskey: C">clear()</a>, load(scriptURL), scope(object), JSON.parse(JSON string), <a accesskey="J" href="#" onclick="go('JSON.stringify(ans)');return false;" title="Accesskey: J">JSON.stringify(object)</a></div>
<div id="output"></div>
<div><textarea id="input" class="input" wrap="off" onkeydown="inputKeydown(event)" rows="1"></textarea></div>
</body>
</html>
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.