diff options
| author | Christopher Lenz <cmlenz@apache.org> | 2008-03-28 23:32:19 +0000 | 
|---|---|---|
| committer | Christopher Lenz <cmlenz@apache.org> | 2008-03-28 23:32:19 +0000 | 
| commit | 544a38dd45f6a58d34296c6c768afd086eb2ac70 (patch) | |
| tree | c84cc02340b06aae189cff0dbfaee698f273f1f5 /share/www/script/shell.js | |
| parent | 804cbbe033b8e7a3e8d7058aaf31bdf69ef18ac5 (diff) | |
Imported trunk.
git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@642432 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'share/www/script/shell.js')
| -rw-r--r-- | share/www/script/shell.js | 700 | 
1 files changed, 700 insertions, 0 deletions
| 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<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). +  _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"; +} | 
