var histList = [""], histPos = 0, _scope = {}, _in, _out, _win, question, tooManyMatches = null, lastError = null; function getScrollMaxY() { return ( 'scrollMaxY' in window ) ? window.scrollMaxY : (document.documentElement.scrollHeight - document.documentElement.clientHeight); } function refocus() { _in.blur(); // Needed for Mozilla to scroll correctly. _in.focus(); scrollTo(0, getScrollMaxY() ); } function afocus () { tabcomplete(); refocus(); } function init() { _in = document.getElementById("input"); _out = document.getElementById("output"); _in.accessKey = "Q"; _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 = e.srcElement ? e.srcElement : e.target; // IE vs. standard while (!g.tagName) g = g.parentNode; var t = g.tagName.toUpperCase(); if (t=="TEXTAREA" || 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); setTimeout(function() { refocus(); }, 0); } else if (e.keyCode == 40) { // down // go down in history if at end or ctrl-down if (e.ctrlKey || caretInLastLine(_in)) hist(false); setTimeout(function() { refocus(); }, 0); } 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(url) { var s = _win.document.createElement("script"); s.type = "text/javascript"; s.src = url; _win.document.getElementsByTagName("head")[0].appendChild(s); println("Loading " + url + "...", "message"); }, clear : function clear() { var CHILDREN_TO_PRESERVE = 3; while (_out.childNodes[CHILDREN_TO_PRESERVE]) _out.removeChild(_out.childNodes[CHILDREN_TO_PRESERVE]); }, print : function print(s) { println(s, "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 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 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). _win.location.href = "javascript:try{ Shell.printAnswer(eval('with(Shell._scope) with(Shell.shellCommands) {' + Shell.question + String.fromCharCode(10) + '}')); } catch(er) { Shell.printError(er); }; setTimeout('window.scrollTo(0,document.body.scrollHeight)', 0); void 0"; } historial = [] function exe () { historial.unshift(texto.value); _win.location.href = "javascript:try{ Shell.printAnswer(eval('with(Shell._scope) with(Shell.shellCommands) {' + historial[0] + String.fromCharCode(10) + '}')); } catch(er) { Shell.printError(er); }; setTimeout(Shell.refocus, 0); void 0"; } function edit () { if (texto.style.display == "block") texto.style.display = "none"; else texto.style.display = "block"; } function Load () { document.body.style.width="640px"; document.body.style.height="480px"; if (window.sizeToContent) window.sizeToContent(); (function(){document.body.appendChild(document.createElement('script')).src='/dir/Jash.js';})(); } load = shellCommands.load;