]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/evergreen/util/shell.html
88c11852b92e41db34eaeecffcad0ec9dc6c62ab
[Evergreen.git] / Open-ILS / xul / staff_client / chrome / content / evergreen / util / shell.html
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2
3 <html onclick="keepFocusInTextbox(event)">
4 <head>
5 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
6 <title>JavaScript Shell 1.3.1</title>
7
8 <script type="text/javascript">
9 // Modified by Jason for Evergreen
10 var 
11 histList = [""], 
12 histPos = 0, 
13 _scope = {}, 
14 _win, // a top-level context
15 question,
16 _in,
17 _out,
18 tooManyMatches = null,
19 lastError = null;
20
21 function refocus()
22 {
23   _in.blur(); // Needed for Mozilla to scroll correctly.
24   _in.focus();
25 }
26
27 function init()
28 {
29   _in = document.getElementById("input");
30   _out = document.getElementById("output");
31
32   _win = window;
33
34   if (opener && !opener.closed)
35   {
36     println("Using bookmarklet version of shell: commands will run in opener's context.", "message");
37     _win = opener;
38   }
39
40   initTarget();
41
42   recalculateInputHeight();
43   refocus();
44 }
45
46 function initTarget()
47 {
48   _win.Shell = window;
49   _win.print = shellCommands.print;
50 }
51
52
53 // Unless the user is selected something, refocus the textbox.
54 // (requested by caillon, brendan, asa)
55 function keepFocusInTextbox(e) 
56 {
57   var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard
58   
59   while (!g.tagName)
60     g = g.parentNode;
61   var t = g.tagName.toUpperCase();
62   if (t=="A" || t=="INPUT")
63     return;
64     
65   if (window.getSelection) {
66     // Mozilla
67     if (String(window.getSelection()))
68       return;
69   }
70   else if (document.getSelection) {
71     // Opera? Netscape 4?
72     if (document.getSelection())
73       return;
74   }
75   else {
76     // IE
77     if ( document.selection.createRange().text )
78       return;
79   }
80   
81   refocus();
82 }
83
84 function inputKeydown(e) {
85   // Use onkeydown because IE doesn't support onkeypress for arrow keys
86
87   //alert(e.keyCode + " ^ " + e.keycode);
88
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)
98       hist(true);
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)
102       hist(false);
103   } else if (e.keyCode == 9) { // tab
104     tabcomplete();
105     setTimeout(function() { refocus(); }, 0); // refocus because tab was hit
106   } else { }
107
108   setTimeout(recalculateInputHeight, 0);
109   
110   //return true;
111 };
112
113 function recalculateInputHeight()
114 {
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
118   
119   if (_in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0.
120     _in.rows = rows;
121 }
122
123 function println(s, type)
124 {
125   if((s=String(s)))
126   {
127     var newdiv = document.createElement("div");
128     newdiv.appendChild(document.createTextNode(s));
129     newdiv.className = type;
130     _out.appendChild(newdiv);
131     return newdiv;
132   }
133 }
134
135 function printWithRunin(h, s, type)
136 {
137   var div = println(s, type);
138   var head = document.createElement("strong");
139   head.appendChild(document.createTextNode(h + ": "));
140   div.insertBefore(head, div.firstChild);
141 }
142
143
144 var shellCommands = 
145 {
146 load : function load(url)
147 {
148   var s = _win.document.createElement("script");
149   s.type = "text/javascript";
150   s.src = url;
151   _win.document.getElementsByTagName("head")[0].appendChild(s);
152   println("Loading " + url + "...", "message");
153 },
154
155 print : function print(s) { println(s, "print"); },
156
157 // the normal function, "print", shouldn't return a value
158 // (suggested by brendan; later noticed it was a problem when showing others)
159 pr : function pr(s) 
160
161   shellCommands.print(s); // need to specify shellCommands so it doesn't try window.print()!
162   return s;
163 },
164
165 props : function props(e)
166 {
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
170
171   var protoLevels = 0;
172
173   for (p = e; p; p = p.__proto__)
174   {
175     for (i=0; i<ns.length; ++i)
176       as[i][protoLevels] = [];
177     ++protoLevels;
178   }
179
180   for(var a in e)
181   {
182     // Shortcoming: doesn't check that VALUES are the same in object and prototype.
183
184     var protoLevel = -1;
185     try
186     {
187       for (p = e; p && (a in p); p = p.__proto__)
188         ++protoLevel;
189     }
190     catch(er) { protoLevel = 0; } // "in" operator throws when param to props() is a string
191
192     var type = 1;
193     try
194     {
195       if ((typeof e[a]) == "function")
196         type = 0;
197     }
198     catch (er) { type = 2; }
199
200     as[type][protoLevel].push(a);
201   }
202
203   function times(s, n) { return n ? s + times(s, n-1) : ""; }
204
205   for (j=0; j<protoLevels; ++j)
206     for (i=0;i<ns.length;++i)
207       if (as[i][j].length) 
208         printWithRunin(ns[i] + times(" of prototype", j), as[i][j].join(", "), "propList");
209 },
210
211 blink : function blink(node)
212 {
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");
217
218   function setOutline(o) { 
219     return function() {
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;
223       }
224       else if (node.style.MozOutline != node.style.bogusProperty) {
225         // browser supports MozOutline (Firefox 1.0.x and older)
226         node.style.MozOutline = o;
227       }
228       else {
229         // browser only supports border (IE). border is a fallback because it moves things around.
230         node.style.border = o;
231       }
232     }
233   } 
234   
235   function focusIt(a) {
236     return function() {
237       a.focus(); 
238     }
239   }
240
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);
245   }
246
247   for(var i=1;i<7;++i)
248     setTimeout(setOutline((i%2)?'3px solid red':'none'), i*100);
249
250   setTimeout(focusIt(window), 800);
251   setTimeout(focusIt(_in), 810);
252 },
253
254 scope : function scope(sc)
255 {
256   if (!sc) sc = {};
257   _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");
259 },
260
261 mathHelp : function mathHelp()
262 {
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");
265 },
266
267 ans : undefined,
268
269 cls : function cls()
270 {
271         while (_out.lastChild) { _out.removeChild( _out.lastChild ); }
272 }
273 };
274
275
276 function hist(up)
277 {
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 
281   // down again.
282
283   var L = histList.length;
284
285   if (L == 1)
286     return;
287
288   if (up)
289   {
290     if (histPos == L-1)
291     {
292       // Save this entry in case the user hits the down key.
293       histList[histPos] = _in.value;
294     }
295
296     if (histPos > 0)
297     {
298       histPos--;
299       // Use a timeout to prevent up from moving cursor within new text
300       // Set to nothing first for the same reason
301       setTimeout(
302         function() {
303           _in.value = ''; 
304           _in.value = histList[histPos]; 
305           if (_in.setSelectionRange) 
306             _in.setSelectionRange(0, 0);
307         },
308         0
309       );
310     }
311   } 
312   else // down
313   {
314     if (histPos < L-1)
315     {
316       histPos++;
317       _in.value = histList[histPos];
318     }
319     else if (histPos == L-1)
320     {
321       // Already on the current entry: clear but save
322       if (_in.value)
323       {
324         histList[histPos] = _in.value;
325         ++histPos;
326         _in.value = "";
327       }
328     }
329   }
330 }
331
332 function tabcomplete()
333 {
334   /*
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 '.'
340    */
341   function findbeginning(s, from, stopAtDot)
342   {
343     /*
344      *  Complicated function.
345      *
346      *  Return true if s[i] == q BUT ONLY IF
347      *  s[i-1] is not a backslash.
348      */
349     function equalButNotEscaped(s,i,q)
350     {
351       if(s.charAt(i) != q) // not equal go no further
352         return false;
353
354       if(i==0) // beginning of string
355         return true;
356
357       if(s.charAt(i-1) == '\\') // escaped?
358         return false;
359
360       return true;
361     }
362
363     var nparens = 0;
364     var i;
365     for(i=from; i>=0; i--)
366     {
367       if(s.charAt(i) == ' ')
368         break;
369
370       if(stopAtDot && s.charAt(i) == '.')
371         break;
372         
373       if(s.charAt(i) == ')')
374         nparens++;
375       else if(s.charAt(i) == '(')
376         nparens--;
377
378       if(nparens < 0)
379         break;
380
381       // skip quoted strings
382       if(s.charAt(i) == '\'' || s.charAt(i) == '\"')
383       {
384         //dump("skipping quoted chars: ");
385         var quot = s.charAt(i);
386         i--;
387         while(i >= 0 && !equalButNotEscaped(s,i,quot)) {
388           //dump(s.charAt(i));
389           i--;
390         }
391         //dump("\n");
392       }
393     }
394     return i;
395   }
396
397   function getcaretpos(inp)
398   {
399     if(inp.selectionEnd)
400       return inp.selectionEnd;
401
402     if(inp.createTextRange)
403     {
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;
409     }
410
411     return inp.value.length; // sucks, punt
412   }
413
414   function setselectionto(inp,pos)
415   {
416     if(inp.selectionStart) {
417       inp.selectionStart = inp.selectionEnd = pos;
418     }
419     else if(inp.createTextRange) {
420       var docrange = _win.Shell.document.selection.createRange();
421       var inprange = inp.createTextRange();
422       inprange.move('character',pos);
423       inprange.select();
424     }
425     else { // err...
426     /*
427       inp.select();
428       if(_win.Shell.document.getSelection())
429         _win.Shell.document.getSelection() = "";
430         */
431     }
432   }
433     // get position of cursor within the input box
434     var caret = getcaretpos(_in);
435
436     if(caret) {
437       //dump("----\n");
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) != '.') {
444         dotpos = caret;
445 //dump("changed dot pos: " + dotpos + "\n");
446       }
447
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)
453       {
454         // try completing function args
455         if(_in.value.charAt(dotpos) == '(' ||
456  (_in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos))
457         {
458           var fn,fname;
459   var from = (_in.value.charAt(dotpos) == '(') ? dotpos : spacepos;
460           spacepos = findbeginning(_in.value, from-1, false);
461
462           fname = _in.value.substr(spacepos+1,from-(spacepos+1));
463   //dump("fname: " + fname + "\n");
464           try {
465             with(_win.Shell._scope)
466               with(_win)
467                 with(Shell.shellCommands)
468                   fn = eval(fname);
469           }
470           catch(er) {
471             //dump('fn is not a valid object\n');
472             return;
473           }
474           if(fn == undefined) {
475              //dump('fn is undefined');
476              return;
477           }
478           if(fn instanceof Function)
479           {
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");
483           }
484
485           return;
486         }
487         else
488           obj = _win;
489       }
490       else
491       {
492         var objname = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
493         //dump("objname: |" + objname + "|\n");
494         try {
495           with(_win.Shell._scope)
496             with(_win)
497                 obj = eval(objname);
498         }
499         catch(er) {
500           printError(er); 
501           return;
502         }
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");
507           return;
508         }
509       }
510       //dump("obj: " + obj + "\n");
511       // get the thing we're trying to complete
512       if(dotpos == caret)
513       {
514         if(spacepos+1 == dotpos || spacepos == dotpos)
515         {
516           // nothing to complete
517           //dump("nothing to complete\n");
518           return;
519         }
520
521         complete = _in.value.substr(spacepos+1,dotpos-(spacepos+1));
522       }
523       else {
524         complete = _in.value.substr(dotpos+1,caret-(dotpos+1));
525       }
526       //dump("complete: " + complete + "\n");
527       // ok, now look at all the props/methods of this obj
528       // and find ones starting with 'complete'
529       var matches = [];
530       var bestmatch = null;
531       for(var a in obj)
532       {
533         //a = a.toString();
534         //XXX: making it lowercase could help some cases,
535         // but screws up my general logic.
536         if(a.substr(0,complete.length) == complete) {
537           matches.push(a);
538           ////dump("match: " + a + "\n");
539           // if no best match, this is the best match
540           if(bestmatch == null)
541           {
542             bestmatch = a;
543           }
544           else {
545             // the best match is the longest common string
546             function min(a,b){ return ((a<b)?a:b); }
547             var i;
548             for(i=0; i< min(bestmatch.length, a.length); i++)
549             {
550               if(bestmatch.charAt(i) != a.charAt(i))
551                 break;
552             }
553             bestmatch = bestmatch.substr(0,i);
554             ////dump("bestmatch len: " + i + "\n");
555           }
556           ////dump("bestmatch: " + bestmatch + "\n");
557         }
558       }
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)) {
564
565         printWithRunin("Matches: ", matches.join(', '), "tabcomplete");
566         tooManyMatches = null;
567       }
568       else if(matches.length > 10)
569       {
570         println(matches.length + " matches.  Press tab again to see them all", "tabcomplete");
571         tooManyMatches = objAndComplete;
572       }
573       else {
574         tooManyMatches = null;
575       }
576       if(bestmatch != "")
577       {
578         var sstart;
579         if(dotpos == caret) {
580           sstart = spacepos+1;
581         }
582         else {
583           sstart = dotpos+1;
584         }
585         _in.value = _in.value.substr(0, sstart)
586                   + bestmatch
587                   + _in.value.substr(caret);
588         setselectionto(_in,caret + (bestmatch.length - complete.length));
589       }
590     }
591 }
592
593 function printQuestion(q)
594 {
595   println(q, "input");
596 }
597
598 function printAnswer(a)
599 {
600   if (a !== undefined) {
601     println(a, "normalOutput");
602     shellCommands.ans = a;
603   }
604 }
605
606 function printError(er)
607
608   var lineNumberString;
609
610   lastError = er; // for debugging the shell
611   if (er.name)
612   {
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.
616   }
617   else
618     println(er, "error"); // Because security errors in Moz /only/ have toString.
619 }
620
621 function go(s)
622 {
623   _in.value = question = s ? s : _in.value;
624
625   if (question == "")
626     return;
627
628   histList[histList.length-1] = question;
629   histList[histList.length] = "";
630   histPos = histList.length - 1;
631   
632   // Unfortunately, this has to happen *before* the JavaScript is run, so that 
633   // print() output will go in the right place.
634   _in.value='';
635   recalculateInputHeight();
636   printQuestion(question);
637
638   if (_win.closed) {
639     printError("Target window has been closed.");
640     return;
641   }
642   
643   try { ("Shell" in _win) }
644   catch(er) {
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.");
646     return;
647   }
648
649   if (!("Shell" in _win))
650     initTarget(); // silent
651
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";
654 }
655
656 </script>
657
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>
661
662 <style type="text/css">
663 body { background: white; color: black; }
664
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; }
668
669 form { margin: 0; padding: 0; }
670 #input { width: 100%; border: none; padding: 0; }
671
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; }
679 </style>
680 </head>
681
682 <body onload="try { init(); } catch(E) { dump(E+'\n'); alert(E); }"> 
683
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>
685
686 <div><textarea id="input" class="input" wrap="off" onkeydown="inputKeydown(event)" rows="1"></textarea></div>
687
688 </body>
689
690 </html>