1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
3 <html onclick="keepFocusInTextbox(event)">
5 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
6 <title>JavaScript Shell 1.3.1</title>
8 <script type="text/javascript">
9 // Modified by Jason for Evergreen
14 _win, // a top-level context
18 tooManyMatches = null,
23 _in.blur(); // Needed for Mozilla to scroll correctly.
29 _in = document.getElementById("input");
30 _out = document.getElementById("output");
34 if (opener && !opener.closed)
36 println("Using bookmarklet version of shell: commands will run in opener's context.", "message");
42 recalculateInputHeight();
49 _win.print = shellCommands.print;
53 // Unless the user is selected something, refocus the textbox.
54 // (requested by caillon, brendan, asa)
55 function keepFocusInTextbox(e)
57 var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard
61 var t = g.tagName.toUpperCase();
62 if (t=="A" || t=="INPUT")
65 if (window.getSelection) {
67 if (String(window.getSelection()))
70 else if (document.getSelection) {
72 if (document.getSelection())
77 if ( document.selection.createRange().text )
84 function inputKeydown(e) {
85 // Use onkeydown because IE doesn't support onkeypress for arrow keys
87 //alert(e.keyCode + " ^ " + e.keycode);
89 if (e.shiftKey && e.keyCode == 13) { // shift-enter
90 // don't do anything; allow the shift-enter to insert a line break as normal
91 } else if (e.keyCode == 13) { // enter
92 // execute the input on enter
93 try { go(); } catch(er) { alert(er); };
94 setTimeout(function() { _in.value = ""; }, 0); // can't preventDefault on input, so clear it later
95 } else if (e.keyCode == 38) { // up
96 // go up in history if at top or ctrl-up
97 if (e.ctrlKey || _in.selectionStart == null || _in.selectionStart == 0)
99 } else if (e.keyCode == 40) { // down
100 // go down in history if at end or ctrl-down
101 if (e.ctrlKey || _in.selectionStart == null || _in.selectionEnd == _in.textLength)
103 } else if (e.keyCode == 9) { // tab
105 setTimeout(function() { refocus(); }, 0); // refocus because tab was hit
108 setTimeout(recalculateInputHeight, 0);
113 function recalculateInputHeight()
115 var rows = _in.value.split(/\n/).length
116 + 1 // prevent scrollbar flickering in Mozilla
117 + (window.opera ? 1 : 0); // leave room for scrollbar in Opera
119 if (_in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0.
123 function println(s, type)
127 var newdiv = document.createElement("div");
128 newdiv.appendChild(document.createTextNode(s));
129 newdiv.className = type;
130 _out.appendChild(newdiv);
135 function printWithRunin(h, s, type)
137 var div = println(s, type);
138 var head = document.createElement("strong");
139 head.appendChild(document.createTextNode(h + ": "));
140 div.insertBefore(head, div.firstChild);
146 load : function load(url)
148 var s = _win.document.createElement("script");
149 s.type = "text/javascript";
151 _win.document.getElementsByTagName("head")[0].appendChild(s);
152 println("Loading " + url + "...", "message");
155 print : function print(s) { println(s, "print"); },
157 // the normal function, "print", shouldn't return a value
158 // (suggested by brendan; later noticed it was a problem when showing others)
161 shellCommands.print(s); // need to specify shellCommands so it doesn't try window.print()!
165 props : function props(e)
167 var ns = ["Methods", "Fields", "Unreachables"];
168 var as = [[], [], []]; // array of (empty) arrays of arrays!
169 var p, j, i; // loop variables, several used multiple times
173 for (p = e; p; p = p.__proto__)
175 for (i=0; i<ns.length; ++i)
176 as[i][protoLevels] = [];
182 // Shortcoming: doesn't check that VALUES are the same in object and prototype.
187 for (p = e; p && (a in p); p = p.__proto__)
190 catch(er) { protoLevel = 0; } // "in" operator throws when param to props() is a string
195 if ((typeof e[a]) == "function")
198 catch (er) { type = 2; }
200 as[type][protoLevel].push(a);
203 function times(s, n) { return n ? s + times(s, n-1) : ""; }
205 for (j=0; j<protoLevels; ++j)
206 for (i=0;i<ns.length;++i)
208 printWithRunin(ns[i] + times(" of prototype", j), as[i][j].join(", "), "propList");
211 blink : function blink(node)
213 if (!node) throw("blink: argument is null or undefined.");
214 if (node.nodeType == null) throw("blink: argument must be a node.");
215 if (node.nodeType == 3) throw("blink: argument must not be a text node");
216 if (node.documentElement) throw("blink: argument must not be the document object");
218 function setOutline(o) {
220 if (node.style.outline != node.style.bogusProperty) {
221 // browser supports outline (Firefox 1.1 and newer, CSS3, Opera 8).
222 node.style.outline = o;
224 else if (node.style.MozOutline != node.style.bogusProperty) {
225 // browser supports MozOutline (Firefox 1.0.x and older)
226 node.style.MozOutline = o;
229 // browser only supports border (IE). border is a fallback because it moves things around.
230 node.style.border = o;
235 function focusIt(a) {
241 if (node.ownerDocument) {
242 var windowToFocusNow = (node.ownerDocument.defaultView || node.ownerDocument.parentWindow); // Moz vs. IE
243 if (windowToFocusNow)
244 setTimeout(focusIt(windowToFocusNow.top), 0);
248 setTimeout(setOutline((i%2)?'3px solid red':'none'), i*100);
250 setTimeout(focusIt(window), 800);
251 setTimeout(focusIt(_in), 810);
254 scope : function scope(sc)
258 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");
261 mathHelp : function mathHelp()
263 printWithRunin("Math constants", "E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2", "propList");
264 printWithRunin("Math methods", "abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan", "propList");
271 while (_out.lastChild) { _out.removeChild( _out.lastChild ); }
278 // histList[0] = first command entered, [1] = second, etc.
279 // type something, press up --> thing typed is now in "limbo"
280 // (last item in histList) and should be reachable by pressing
283 var L = histList.length;
292 // Save this entry in case the user hits the down key.
293 histList[histPos] = _in.value;
299 // Use a timeout to prevent up from moving cursor within new text
300 // Set to nothing first for the same reason
304 _in.value = histList[histPos];
305 if (_in.setSelectionRange)
306 _in.setSelectionRange(0, 0);
317 _in.value = histList[histPos];
319 else if (histPos == L-1)
321 // Already on the current entry: clear but save
324 histList[histPos] = _in.value;
332 function tabcomplete()
335 * Working backwards from s[from], find the spot
336 * where this expression starts. It will scan
337 * until it hits a mismatched ( or a space,
338 * but it skips over quoted strings.
339 * If stopAtDot is true, stop at a '.'
341 function findbeginning(s, from, stopAtDot)
344 * Complicated function.
346 * Return true if s[i] == q BUT ONLY IF
347 * s[i-1] is not a backslash.
349 function equalButNotEscaped(s,i,q)
351 if(s.charAt(i) != q) // not equal go no further
354 if(i==0) // beginning of string
357 if(s.charAt(i-1) == '\\') // escaped?
365 for(i=from; i>=0; i--)
367 if(s.charAt(i) == ' ')
370 if(stopAtDot && s.charAt(i) == '.')
373 if(s.charAt(i) == ')')
375 else if(s.charAt(i) == '(')
381 // skip quoted strings
382 if(s.charAt(i) == '\'' || s.charAt(i) == '\"')
384 //dump("skipping quoted chars: ");
385 var quot = s.charAt(i);
387 while(i >= 0 && !equalButNotEscaped(s,i,quot)) {
397 function getcaretpos(inp)
400 return inp.selectionEnd;
402 if(inp.createTextRange)
404 //dump('using createTextRange\n');
405 var docrange = _win.Shell.document.selection.createRange();
406 var inprange = inp.createTextRange();
407 inprange.setEndPoint('EndToStart', docrange);
408 return inprange.text.length;
411 return inp.value.length; // sucks, punt
414 function setselectionto(inp,pos)
416 if(inp.selectionStart) {
417 inp.selectionStart = inp.selectionEnd = pos;
419 else if(inp.createTextRange) {
420 var docrange = _win.Shell.document.selection.createRange();
421 var inprange = inp.createTextRange();
422 inprange.move('character',pos);
428 if(_win.Shell.document.getSelection())
429 _win.Shell.document.getSelection() = "";
433 // get position of cursor within the input box
434 var caret = getcaretpos(_in);
438 var dotpos, spacepos, complete, obj;
439 //dump("caret pos: " + caret + "\n");
440 // see if there's a dot before here
441 dotpos = findbeginning(_in.value, caret-1, true);
442 //dump("dot pos: " + dotpos + "\n");
443 if(dotpos == -1 || _in.value.charAt(dotpos) != '.') {
445 //dump("changed dot pos: " + dotpos + "\n");
448 // look backwards for a non-variable-name character
449 spacepos = findbeginning(_in.value, dotpos-1, false);
450 //dump("space pos: " + spacepos + "\n");
451 // get the object we're trying to complete on
452 if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret)
454 // try completing function args
455 if(_in.value.charAt(dotpos) == '(' ||
456 (_in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos))
459 var from = (_in.value.charAt(dotpos) == '(') ? dotpos : spacepos;
460 spacepos = findbeginning(_in.value, from-1, false);
462 fname = _in.value.substr(spacepos+1,from-(spacepos+1));
463 //dump("fname: " + fname + "\n");
465 with(_win.Shell._scope)
467 with(Shell.shellCommands)
471 //dump('fn is not a valid object\n');
474 if(fn == undefined) {
475 //dump('fn is undefined');
478 if(fn instanceof Function)
480 // Print function definition, including argument names, but not function body
481 if(!fn.toString().match(/function .+?\(\) +\{\n +\[native code\]\n\}/))
482 println(fn.toString().match(/function .+?\(.*?\)/), "tabcomplete");
492 var objname = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
493 //dump("objname: |" + objname + "|\n");
495 with(_win.Shell._scope)
503 if(obj == undefined) {
504 // sometimes this is tabcomplete's fault, so don't print it :(
505 // e.g. completing from "print(document.getElements"
506 // println("Can't complete from null or undefined expression " + objname, "error");
510 //dump("obj: " + obj + "\n");
511 // get the thing we're trying to complete
514 if(spacepos+1 == dotpos || spacepos == dotpos)
516 // nothing to complete
517 //dump("nothing to complete\n");
521 complete = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
524 complete = _in.value.substr(dotpos+1,caret-(dotpos+1));
526 //dump("complete: " + complete + "\n");
527 // ok, now look at all the props/methods of this obj
528 // and find ones starting with 'complete'
530 var bestmatch = null;
534 //XXX: making it lowercase could help some cases,
535 // but screws up my general logic.
536 if(a.substr(0,complete.length) == complete) {
538 ////dump("match: " + a + "\n");
539 // if no best match, this is the best match
540 if(bestmatch == null)
545 // the best match is the longest common string
546 function min(a,b){ return ((a<b)?a:b); }
548 for(i=0; i< min(bestmatch.length, a.length); i++)
550 if(bestmatch.charAt(i) != a.charAt(i))
553 bestmatch = bestmatch.substr(0,i);
554 ////dump("bestmatch len: " + i + "\n");
556 ////dump("bestmatch: " + bestmatch + "\n");
559 bestmatch = (bestmatch || "");
560 ////dump("matches: " + matches + "\n");
561 var objAndComplete = (objname || obj) + "." + bestmatch;
562 //dump("matches.length: " + matches.length + ", tooManyMatches: " + tooManyMatches + ", objAndComplete: " + objAndComplete + "\n");
563 if(matches.length > 1 && (tooManyMatches == objAndComplete || matches.length <= 10)) {
565 printWithRunin("Matches: ", matches.join(', '), "tabcomplete");
566 tooManyMatches = null;
568 else if(matches.length > 10)
570 println(matches.length + " matches. Press tab again to see them all", "tabcomplete");
571 tooManyMatches = objAndComplete;
574 tooManyMatches = null;
579 if(dotpos == caret) {
585 _in.value = _in.value.substr(0, sstart)
587 + _in.value.substr(caret);
588 setselectionto(_in,caret + (bestmatch.length - complete.length));
593 function printQuestion(q)
598 function printAnswer(a)
600 if (a !== undefined) {
601 println(a, "normalOutput");
602 shellCommands.ans = a;
606 function printError(er)
608 var lineNumberString;
610 lastError = er; // for debugging the shell
613 // lineNumberString should not be "", to avoid a very wacky bug in IE 6.
614 lineNumberString = (er.lineNumber != undefined) ? (" on line " + er.lineNumber + ": ") : ": ";
615 println(er.name + lineNumberString + er.message, "error"); // Because IE doesn't have error.toString.
618 println(er, "error"); // Because security errors in Moz /only/ have toString.
623 _in.value = question = s ? s : _in.value;
628 histList[histList.length-1] = question;
629 histList[histList.length] = "";
630 histPos = histList.length - 1;
632 // Unfortunately, this has to happen *before* the JavaScript is run, so that
633 // print() output will go in the right place.
635 recalculateInputHeight();
636 printQuestion(question);
639 printError("Target window has been closed.");
643 try { ("Shell" in _win) }
645 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.");
649 if (!("Shell" in _win))
650 initTarget(); // silent
652 // Evaluate Shell.question using _win's eval (this is why eval isn't in the |with|, IIRC).
653 _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(Shell.refocus, 0); void 0";
658 <!-- for http://ted.mielczarek.org/code/mozilla/extensiondev/ -->
659 <script type="text/javascript" src="chrome://extensiondev/content/rdfhistory.js"></script>
660 <script type="text/javascript" src="chrome://extensiondev/content/chromeShellExtras.js"></script>
662 <style type="text/css">
663 body { background: white; color: black; }
665 #output { white-space: pre; white-space: -moz-pre-wrap; } /* Preserve line breaks, but wrap too if browser supports it */
666 h3 { margin-top: 0; margin-bottom: 0em; }
667 h3 + div { margin: 0; }
669 form { margin: 0; padding: 0; }
670 #input { width: 100%; border: none; padding: 0; }
672 .input { color: blue; background: white; font: inherit; font-weight: bold; margin-top: .5em; /* background: #E6E6FF; */ }
673 .normalOutput { color: black; background: white; }
674 .print { color: brown; background: white; }
675 .error { color: red; background: white; }
676 .propList { color: green; background: white; }
677 .message { color: green; background: white; }
678 .tabcomplete { color: purple; background: white; }
682 <body onload="params.w = window; params.app = 'Shell'; mw.OpenILS_init(params); init();" onunload="mw.OpenILS_exit(params);">
684 <div id="output"><h3>JavaScript Shell 1.3.1</h3><div>Features: autocompletion of property names with Tab, multiline input with Shift+Enter, input history with (Ctrl+) Up/Down, <a accesskey=M href="javascript:go('scope(Math); mathHelp();');">Math</a>, <a accesskey=H href="http://www.squarefree.com/shell/?ignoreReferrerFrom=shell1.3.1">help</a></div><div>Values and functions: ans, print(string), <a accesskey=P href="javascript:go('props(ans)')">props(object)</a>, <a accesskey=B href="javascript:go('blink(ans)')">blink(node)</a>, load(scriptURL), scope(object), cls()</div></div>
686 <div><textarea id="input" class="input" wrap="off" onkeydown="inputKeydown(event)" rows="1"></textarea></div>