text: /* ------------------------------------------------------------------------*
   * Copyright 2013-2014 Arne F. Claassen
   *
   * 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 Josh = Josh || {};
  Josh.Version = "0.2.10";
  (function(root) {
    Josh.Keys = {
      Special: {
        Backspace: 8,
        Tab: 9,	//9
        Enter: 13,//13
        Pause: 19,
        CapsLock: 20,
        Escape: 27,
        Space: 32,
        PageUp: 33,
        PageDown: 34,
        End: 35,
        Home: 36,
        Left: 37,
        Up: 38,
        Right: 39,
        Down: 40,
        Insert: 45,
        Delete: 46
      }
    };
  
    Josh.ReadLine = function(config) {
      config = config || {};
  
      // instance fields
      var _console = config.console || (Josh.Debug && root.console ? root.console : {
        log: function() {
        }
      });
      var _history = config.history || new Josh.History();
      var _killring = config.killring || new Josh.KillRing();
      var _boundToElement = config.element ? true : false;
      var _element = config.element || root;
      var _active = false;
      var _onActivate;
      var _onDeactivate;
      var _onCompletion;
      var _onEnter;
      var _onChange;
      var _onCancel;
      var _onEOT;
      var _onClear;
      var _onSearchStart;
      var _onSearchEnd;
      var _onSearchChange;
      var _inSearch = false;
      var _searchMatch;
      var _lastSearchText = '';
      var _text = '';
      var _cursor = 0;
      var _lastCmd;
      var _completionActive;
      var _cmdQueue = [];
      var _suspended = false;
      var _cmdMap = {
        complete: cmdComplete,
        done: cmdDone,
        noop: cmdNoOp,
        history_top: cmdHistoryTop,
        history_end: cmdHistoryEnd,
        history_next: cmdHistoryNext,
        history_previous: cmdHistoryPrev,
        end: cmdEnd,
        home: cmdHome,
        left: cmdLeft,
        right: cmdRight,
        cancel: cmdCancel,
        'delete': cmdDeleteChar,
        backspace: cmdBackspace,
        kill_eof: cmdKillToEOF,
        kill_wordback: cmdKillWordBackward,
        kill_wordforward: cmdKillWordForward,
        yank: cmdYank,
        clear: cmdClear,
        search: cmdReverseSearch,
        wordback: cmdBackwardWord,
        wordforward: cmdForwardWord,
        yank_rotate: cmdRotate
    };
      var _keyMap = {
        'default': {
          8: cmdBackspace,    // Backspace
          9: cmdComplete,     // Tab
          13: cmdDone,        // Enter
          27: cmdEsc,         // Esc
          33: cmdHistoryTop,  // Page Up
          34: cmdHistoryEnd,  // Page Down
          35: cmdEnd,         // End
          36: cmdHome,        // Home
          37: cmdLeft,        // Left
          38: cmdHistoryPrev, // Up
          39: cmdRight,       // Right
          40: cmdHistoryNext, // Down
          46: cmdDeleteChar,  // Delete
          10: cmdNoOp,        // Pause
          19: cmdNoOp,        // Caps Lock
          45: cmdNoOp         // Insert
        },
        control: {
          65: cmdHome,          // A
          66: cmdLeft,          // B
          67: cmdCancel,        // C
          68: cmdDeleteChar,    // D
          69: cmdEnd,           // E
          70: cmdRight,         // F
          80: cmdHistoryPrev,   // P
          78: cmdHistoryNext,   // N
          75: cmdKillToEOF,     // K
          89: cmdYank,          // Y
          76: cmdClear,         // L
          82: cmdReverseSearch  // R
        },
        meta: {
          8: cmdKillWordBackward, // Backspace
          66: cmdBackwardWord,    // B
          68: cmdKillWordForward, // D
          70: cmdForwardWord,     // F
          89: cmdRotate           // Y
        }
      };
  
      // public methods
      var self = {
        isActive: function() {
          return _active;
        },
        activate: function() {
          _active = true;
          if(_onActivate) {
            _onActivate();
          }
        },
        deactivate: function() {
          _active = false;
          if(_onDeactivate) {
            _onDeactivate();
          }
        },
        bind: function(key, action) {
          var k = getKey(key);
          var cmd = _cmdMap[action];
          if(!cmd) {
            return;
          }
          _keyMap[k.modifier][k.code];
        },
        unbind: function(key) {
          var k = getKey(key);
          delete _keyMap[k.modifier][k.code];
        },
        attach: function(el) {
          if(_element) {
            self.detach();
          }
          _console.log("attaching");
          _console.log(el);
          _element = el;
          _boundToElement = true;
          addEvent(_element, "focus", self.activate);
          addEvent(_element, "blur", self.deactivate);
          subscribeToKeys();
        },
        detach: function() {
          removeEvent(_element, "focus", self.activate);
          removeEvent(_element, "blur", self.deactivate);
          _element = null;
          _boundToElement = false;
        },
        onActivate: function(completionHandler) {
          _onActivate = completionHandler;
        },
        onDeactivate: function(completionHandler) {
          _onDeactivate = completionHandler;
        },
        onChange: function(changeHandler) {
          _onChange = changeHandler;
        },
        onClear: function(completionHandler) {
          _onClear = completionHandler;
        },
        onEnter: function(enterHandler) {
          _onEnter = enterHandler;
        },
        onCompletion: function(completionHandler) {
          _onCompletion = completionHandler;
        },
        onCancel: function(completionHandler) {
          _onCancel = completionHandler;
        },
        onEOT: function(completionHandler) {
          _onEOT = completionHandler;
        },
        onSearchStart: function(completionHandler) {
          _onSearchStart = completionHandler;
        },
        onSearchEnd: function(completionHandler) {
          _onSearchEnd = completionHandler;
        },
        onSearchChange: function(completionHandler) {
          _onSearchChange = completionHandler;
        },
        getLine: function() {
          return {
            text: _text,
            cursor: _cursor
          };
        },
        setLine: function(line) {
          _text = line.text;
          _cursor = line.cursor;
          refresh();
        }
      };
  
      // private methods
      function addEvent(element, name, callback) {
        if(element.addEventListener) {
          element.addEventListener(name, callback, false);
        } else if(element.attachEvent) {
          element.attachEvent('on' + name, callback);
        }
      }
  
      function removeEvent(element, name, callback) {
        if(element.removeEventListener) {
          element.removeEventListener(name, callback, false);
        } else if(element.detachEvent) {
          element.detachEvent('on' + name, callback);
        }
      }
  
      function getKeyInfo(e) {
        var code = e.keyCode || e.charCode;
        var c = String.fromCharCode(code);
        return {
          code: code,
          character: c,
          shift: e.shiftKey,
          control: e.controlKey,
          alt: e.altKey,
          isChar: true
        };
      }
  
      function getKey(key) {
        var k = {
          modifier: 'default',
          code: key.keyCode
        };
        if(key.metaKey || key.altKey) {
          k.modifier = 'meta';
        } else if(key.ctrlKey) {
          k.modifier = 'control';
        }
        if(key['char']) {
          k.code = key['char'].charCodeAt(0);
        }
        return k;
      }
  
      function queue(cmd) {
        if(_suspended) {
          _cmdQueue.push(cmd);
          return;
        }
        call(cmd);
      }
  
      function call(cmd) {
        _console.log('calling: ' + cmd.name + ', previous: ' + _lastCmd);
        if(_inSearch && cmd.name != "cmdKeyPress" && cmd.name != "cmdReverseSearch") {
          _inSearch = false;
          if(cmd.name == 'cmdEsc') {
            _searchMatch = null;
          }
          if(_searchMatch) {
            if(_searchMatch.text) {
              _cursor = _searchMatch.cursoridx;
              _text = _searchMatch.text;
              _history.applySearch();
            }
            _searchMatch = null;
          }
          if(_onSearchEnd) {
            _onSearchEnd();
          }
        }
        if(!_inSearch && _killring.isinkill() && cmd.name.substr(0, 7) != 'cmdKill') {
          _killring.commit();
        }
        _lastCmd = cmd.name;
        cmd();
      }
  
      function suspend(asyncCall) {
        _suspended = true;
        asyncCall(resume);
      }
  
      function resume() {
        var cmd = _cmdQueue.shift();
        if(!cmd) {
          _suspended = false;
          return;
        }
        call(cmd);
        resume();
      }
  
      function cmdNoOp() {
        // no-op, used for keys we capture and ignore
      }
  
      function cmdEsc() {
        // no-op, only has an effect on reverse search and that action was taken in call()
      }
  
      function cmdBackspace() {
        if(_cursor == 0) {
          return;
        }
        --_cursor;
        _text = remove(_text, _cursor, _cursor + 1);
        refresh();
      }
  
      function cmdComplete() {
        if(!_onCompletion) {
          return;
        }
        suspend(function(resumeCallback) {
          _onCompletion(self.getLine(), function(completion) {
            if(completion) {
              _text = insert(_text, _cursor, completion);
              updateCursor(_cursor + completion.length);
            }
            _completionActive = true;
            resumeCallback();
          });
        });
      }
  
      function cmdDone() {
        if(!_text) {
          return;
        }
        var text = _text;
        //_history.accept(text);
        _text = '';
        _cursor = 0;
        if(!_onEnter) {
          return;
        }
        suspend(function(resumeCallback) {
          _onEnter(text, function(text) {
            if(text) {
              _text = text;
              _cursor = _text.length;
            }
            if(_onChange) {
              _onChange(self.getLine());
            }
            resumeCallback();
          });
        });
  
      }
  
      function cmdEnd() {
        updateCursor(_text.length);
      }
  
      function cmdHome() {
        updateCursor(0);
      }
  
      function cmdLeft() {
        if(_cursor == 0) {
          return;
        }
        updateCursor(_cursor - 1);
      }
  
      function cmdRight() {
        if(_cursor == _text.length) {
          return;
        }
        updateCursor(_cursor + 1);
      }
  
      function cmdBackwardWord() {
        if(_cursor == 0) {
          return;
        }
        updateCursor(findBeginningOfPreviousWord());
      }
  
      function cmdForwardWord() {
        if(_cursor == _text.length) {
          return;
        }
        updateCursor(findEndOfCurrentWord());
      }
  
      function cmdHistoryPrev() {
        if(!_history.hasPrev()) {
          return;
        }
        getHistory(_history.prev);
      }
  
      function cmdHistoryNext() {
        if(!_history.hasNext()) {
          return;
        }
        getHistory(_history.next);
      }
  
      function cmdHistoryTop() {
        getHistory(_history.top);
      }
  
      function cmdHistoryEnd() {
        getHistory(_history.end);
      }
  
      function cmdDeleteChar() {
        if(_text.length == 0) {
          if(_onEOT) {
            _onEOT();
            return;
          }
        }
        if(_cursor == _text.length) {
          return;
        }
        _text = remove(_text, _cursor, _cursor + 1);
        refresh();
      }
  
      function cmdCancel() {
        if(_onCancel) {
          _onCancel();
        }
      }
  
      function cmdKillToEOF() {
        _killring.append(_text.substr(_cursor));
        _text = _text.substr(0, _cursor);
        refresh();
      }
  
      function cmdKillWordForward() {
        if(_text.length == 0) {
          return;
        }
        if(_cursor == _text.length) {
          return;
        }
        var end = findEndOfCurrentWord();
        if(end == _text.length - 1) {
          return cmdKillToEOF();
        }
        _killring.append(_text.substring(_cursor, end))
        _text = remove(_text, _cursor, end);
        refresh();
      }
  
      function cmdKillWordBackward() {
        if(_cursor == 0) {
          return;
        }
        var oldCursor = _cursor;
        _cursor = findBeginningOfPreviousWord();
        _killring.prepend(_text.substring(_cursor, oldCursor));
        _text = remove(_text, _cursor, oldCursor);
        refresh();
      }
  
      function cmdYank() {
        var yank = _killring.yank();
        if(!yank) {
          return;
        }
        _text = insert(_text, _cursor, yank);
        updateCursor(_cursor + yank.length);
      }
  
      function cmdRotate() {
        var lastyanklength = _killring.lastyanklength();
        if(!lastyanklength) {
          return;
        }
        var yank = _killring.rotate();
        if(!yank) {
          return;
        }
        var oldCursor = _cursor;
        _cursor = _cursor - lastyanklength;
        _text = remove(_text, _cursor, oldCursor);
        _text = insert(_text, _cursor, yank);
        updateCursor(_cursor + yank.length);
      }
  
      function cmdClear() {
        if(_onClear) {
          _onClear();
        } else {
          refresh();
        }
      }
  
      function cmdReverseSearch() {
        if(!_inSearch) {
          _inSearch = true;
          if(_onSearchStart) {
            _onSearchStart();
          }
          if(_onSearchChange) {
            _onSearchChange({});
          }
        } else {
          if(!_searchMatch) {
            _searchMatch = {term: ''};
          }
          search();
        }
      }
  
      function updateCursor(position) {
        _cursor = position;
        refresh();
      }
  
      function addText(c) {
        _text = insert(_text, _cursor, c);
        ++_cursor;
        refresh();
      }
  
      function addSearchText(c) {
        if(!_searchMatch) {
          _searchMatch = {term: ''};
        }
        _searchMatch.term += c;
        search();
      }
  
      function search() {
        _console.log("searchtext: " + _searchMatch.term);
        var match = _history.search(_searchMatch.term);
        if(match != null) {
          _searchMatch = match;
          _console.log("match: " + match);
          if(_onSearchChange) {
            _onSearchChange(match);
          }
        }
      }
  
      function refresh() {
        if(_onChange) {
          _onChange(self.getLine());
        }
      }
  
      function getHistory(historyCall) {
        _history.update(_text);
        _text = historyCall();
        updateCursor(_text.length);
      }
  
      function findBeginningOfPreviousWord() {
        var position = _cursor - 1;
        if(position < 0) {
          return 0;
        }
        var word = false;
        for(var i = position; i > 0; i--) {
          var word2 = isWordChar(_text[i]);
          if(word && !word2) {
            return i + 1;
          }
          word = word2;
        }
        return 0;
      }
  
      function findEndOfCurrentWord() {
        if(_text.length == 0) {
          return 0;
        }
        var position = _cursor + 1;
        if(position >= _text.length) {
          return _text.length - 1;
        }
        var word = false;
        for(var i = position; i < _text.length; i++) {
          var word2 = isWordChar(_text[i]);
          if(word && !word2) {
            return i;
          }
          word = word2;
        }
        return _text.length - 1;
      }
  
      function isWordChar(c) {
        if(c == undefined) {
          return false;
        }
        var code = c.charCodeAt(0);
        return (code >= 48 && code <= 57)
          || (code >= 65 && code <= 90)
          || (code >= 97 && code <= 122);
      }
  
      function remove(text, from, to) {
        if(text.length <= 1 || text.length <= to - from) {
          return '';
        }
        if(from == 0) {
  
          // delete leading characters
          return text.substr(to);
        }
        var left = text.substr(0, from);
        var right = text.substr(to);
        return left + right;
      }
  
      function insert(text, idx, ins) {
        if(idx == 0) {
          return ins + text;
        }
        if(idx >= text.length) {
          return text + ins;
        }
        var left = text.substr(0, idx);
        var right = text.substr(idx);
        return left + ins + right;
      }
  
      function subscribeToKeys() {
  
        // set up key capture
        _element.onkeydown = function(e) {
          e = e || window.event;
  
          // return as unhandled if we're not active or the key is just a modifier key
          if(!_active || e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 || e.keyCode == 91) {
            return true;
          }
  
          // check for some special first keys, regardless of modifiers
          _console.log("key: " + e.keyCode);
          var cmd = _keyMap['default'][e.keyCode];
          // intercept ctrl- and meta- sequences (may override the non-modifier cmd captured above
          var mod;
          if(e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
            mod = _keyMap.control[e.keyCode];
            if(mod) {
              cmd = mod;
            }
          } else if((e.altKey || e.metaKey) && !e.ctrlKey && !e.shiftKey) {
            mod = _keyMap.meta[e.keyCode];
            if(mod) {
              cmd = mod;
            }
          }
          if(!cmd) {
            return true;
          }
          queue(cmd);
          e.preventDefault();
          e.stopPropagation();
          e.cancelBubble = true;
          return false;
        };
  
        _element.onkeypress = function(e) {
          if(!_active) {
            return true;
          }
          var key = getKeyInfo(e);
          if(key.code == 0 || e.defaultPrevented || e.metaKey || e.altKey || e.ctrlKey) {
            return false;
          }
          queue(function cmdKeyPress() {
            if(_inSearch) {
              addSearchText(key.character);
            } else {
              addText(key.character);
            }
          });
          e.preventDefault();
          e.stopPropagation();
          e.cancelBubble = true;
          return false;
        };
      }
      if(_boundToElement) {
        self.attach(_element);
      } else {
        subscribeToKeys();
      }
      return self;
    };
  })(this);
  