From 544a38dd45f6a58d34296c6c768afd086eb2ac70 Mon Sep 17 00:00:00 2001 From: Christopher Lenz Date: Fri, 28 Mar 2008 23:32:19 +0000 Subject: Imported trunk. git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@642432 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/shell.js | 700 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 700 insertions(+) create mode 100644 share/www/script/shell.js (limited to 'share/www/script/shell.js') diff --git a/share/www/script/shell.js b/share/www/script/shell.js new file mode 100644 index 00000000..f4c6aff5 --- /dev/null +++ b/share/www/script/shell.js @@ -0,0 +1,700 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +var +histList = [""], +histPos = 0, +_scope = {}, +_win, // a top-level context +question, +_in, +_out, +tooManyMatches = null, +lastError = null; + +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 = e.srcElement ? e.srcElement : e.target; // IE vs. standard + + 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(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(Shell.refocus, 0); void 0"; +} -- cgit v1.2.3