text: 
  (function(root, factory) {
    if (typeof define === 'function' && define.amd) {
      define(["prismjs"], factory);
    } else if (typeof exports === 'object') {
      module.exports = factory(require('prismjs'));
    } else {
      root.mdEdit = factory(root.Prism);
    }
  }(this, function(Prism) {
  
    var yaml = {
      'scalar': {
        'pattern': /([\-:]\s*(![^\s]+)?[ \t]*[|>])[ \t]*(?:(\n[ \t]+)[^\r\n]+(?:\3[^\r\n]+)*)/,
        'lookbehind': true,
        'alias': 'string'
      },
      'comment': /#[^\n]+/,
      'key': {
        'pattern': /(\s*[:\-,[{\n?][ \t]*(![^\s]+)?[ \t]*)[^\n{[\]},#]+?(?=\s*:\s)/,
        'lookbehind': true,
        'alias': 'atrule'
      },
      'directive': {
        'pattern': /((^|\n)[ \t]*)%[^\n]+/,
        'lookbehind': true,
        'alias': 'important'
      },
      'datetime': {
        'pattern': /([:\-,[{]\s*(![^\s]+)?[ \t]*)(\d{4}-\d\d?-\d\d?([tT]|[ \t]+)\d\d?:\d{2}:\d{2}(\.\d*)?[ \t]*(Z|[-+]\d\d?(:\d{2})?)?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(:\d{2}(\.\d*)?)?)(?=[ \t]*(\n|$|,|]|}))/,
        'lookbehind': true,
        'alias': 'number'
      },
      'boolean': {
        'pattern': /([:\-,[{]\s*(![^\s]+)?[ \t]*)(true|false)[ \t]*(?=\n|$|,|]|})/i,
        'lookbehind': true,
        'alias': 'important'
      },
      'null': {
        'pattern': /([:\-,[{]\s*(![^\s]+)?[ \t]*)(null|~)[ \t]*(?=\n|$|,|]|})/i,
        'lookbehind': true,
        'alias': 'important'
      },
      'string': {
        'pattern': /([:\-,[{]\s*(![^\s]+)?[ \t]*)("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')(?=[ \t]*(\n|$|,|]|}))/,
        'lookbehind': true
      },
      'number': {
        'pattern': /([:\-,[{]\s*(![^\s]+)?[ \t]*)[+\-]?(0x[\dA-Fa-f]+|0o[0-7]+|(\d+\.?\d*|\.?\d+)(e[\+\-]?\d+)?|\.inf|\.nan)[ \t]*(?=\n|$|,|]|})/i,
        'lookbehind': true
      },
      'tag': /![^\s]+/,
      'important': /[&*][\w]+/,
      'punctuation': /([:[\]{}\-,|>?]|---|\.\.\.)/
    };
  
    var md = (function(){
      var md = {
        'comment': Prism['languages']['markup']['comment']
      };
  
  
      md['front-matter'] = {
        'pattern': /^---\n[\s\S]*?\n---(?=\n|$)/,
        'inside': {
          'marker front-matter-marker start': /^---/,
          'marker front-matter-marker end': /---$/,
          'rest': yaml
        }
      };
  
  
      var inlines = {};
      var blocks = {};
  
      function inline(name, def){
        blocks[name] = inlines[name] = md[name] = def;
      }
      function block(name, def){
        blocks[name] = md[name] = def;
      }
  
  
      var langAliases = {
        'markup': [ 'markup', 'html', 'xml' ],
        'javascript': [ 'javascript', 'js' ]
      };
  
      for(var i in Prism['languages']){
        if(!Prism['languages'].hasOwnProperty(i)) continue;
        var l = Prism['languages'][i];
        if(typeof l === 'function') continue;
  
        var aliases = langAliases[i];
        var matches = aliases ? aliases.join('|') : i;
  
        block('code-block fenced ' + i, {
          'pattern': new RegExp('(^ {0,3}|\\n {0,3})(([`~])\\3\\3) *(' + matches + ')( [^`\n]*)? *\\n(?:[\\s\\S]*?)\\n {0,3}(\\2\\3*(?= *\\n)|$)', 'gi'),
          'lookbehind': true,
          'inside': {
            'code-language': {
              'pattern': /(^([`~])\2+)((?!\2)[^\2\n])+/,
              'lookbehind': true
            },
            'marker code-fence start': /^([`~])\1+/,
            'marker code-fence end': /([`~])\1+$/,
            'code-inner': {
              'pattern': /(^\n)[\s\S]*(?=\n$)/,
              'lookbehind': true,
              'alias': 'language-' + i,
              'inside': l
            }
          }
        });
      }
  
  
      block('code-block fenced untagged', {
        'pattern': /(^ {0,3}|\n {0,3})(([`~])\3\3)[^`\n]*\n(?:[\s\S]*?)\n {0,3}(\2\3*(?= *\n)|$)/g,
        'lookbehind': true,
        'inside': {
          'code-language': {
            'pattern': /(^([`~])\2+)((?!\2)[^\2\n])+/,
            'lookbehind': true
          },
          'marker code-fence start': /^([`~])\1+/,
          'marker code-fence end': /([`~])\1+$/,
          'code-inner': {
            'pattern': /(^\n)[\s\S]*(?=\n$)/,
            'lookbehind': true
          }
        }
      });
  
  
      block('heading setext-heading heading-1', {
        'pattern': /^ {0,3}[^\s].*\n {0,3}=+[ \t]*$/gm,
        'inside': {
          'marker heading-setext-line': {
            'pattern': /^( {0,3}[^\s].*\n) {0,3}=+[ \t]*$/gm,
            'lookbehind': true
          },
          'rest': inlines
        }
      });
  
      block('heading setext-heading heading-2', {
        'pattern': /^ {0,3}[^\s].*\n {0,3}-+[ \t]*$/gm,
        'inside': {
          'marker heading-setext-line': {
            'pattern': /^( {0,3}[^\s].*\n) {0,3}-+[ \t]*$/gm,
            'lookbehind': true
          },
          'rest': inlines
        }
      });
  
      var headingInside = {
        'marker heading-hash start': /^ *#+ */,
        'marker heading-hash end': / +#+ *$/,
        'rest': inlines
      };
      for(var i = 1; i <= 6; i += 1){
        block('heading heading-'+i, {
          'pattern': new RegExp('^ {0,3}#{'+i+'}(?![#\\S]).*$', 'gm'),
          'inside': headingInside
        });
      }
  
  
  
      var linkText = {
        'pattern': /^\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\]/,
        'inside': {
          'marker bracket start': /^\[/,
          'marker bracket end': /\]$/,
          'link-text-inner': {
            'pattern': /[\w\W]+/,
            'inside': inlines
          }
        }
      };
  
      var linkLabel = {
        'pattern': /\[(?:\\.|[^\]])*\]/,
        'inside': {
          'marker bracket start': /^\[/,
          'marker bracket end': /\]$/,
          'link-label-inner': /[\w\W]+/
        }
      };
  
      var imageText = {
        'pattern': /^!\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\]/,
        'inside': {
          'marker image-bang': /^!/,
          'marker bracket start': /^\[/,
          'marker bracket end': /\]$/,
          'image-text-inner': {
            'pattern': /[\w\W]+/,
            'inside': inlines
          }
        }
      };
  
      var linkURL = {
        'pattern': /^(\s*)(?!<)(?:\\.|[^\(\)\s]|\([^\(\)\s]*\))+/,
        'lookbehind': true
      };
  
      var linkBracedURL = {
        'pattern': /^(\s*)<(?:\\.|[^<>\n])*>/,
        'lookbehind': true,
        'inside': {
          'marker brace start': /^</,
          'marker brace end': />$/,
          'braced-href-inner': /[\w\W]+/
        }
      };
  
      var linkTitle = {
        'pattern': /('(?:\\'|[^'])+'|"(?:\\"|[^"])+")\s*$/,
        // 'lookbehind': true,
        'inside': {
          'marker quote start': /^['"]/,
          'marker quote end': /['"]$/,
          'title-inner': /[\w\W]+/
        }
      };
  
      var linkParams = {
        'pattern': /\( *(?:(?!<)(?:\\.|[^\(\)\s]|\([^\(\)\s]*\))*|<(?:[^<>\n]|\\.)*>)( +('(?:[^']|\\')+'|"(?:[^"]|\\")+"))? *\)/,
        'inside': {
          'marker bracket start': /^\(/,
          'marker bracket end': /\)$/,
          'link-params-inner': {
            'pattern': /[\w\W]+/,
            'inside': {
              'link-title': linkTitle,
              'href': linkURL,
              'braced-href': linkBracedURL
            }
          }
        }
      };
  
  
  
  
      block('hr', {
        'pattern': /^[\t ]*([*\-_])([\t ]*\1){2,}([\t ]*$)/gm,
        'inside': {
          'marker hr-marker': /[*\-_]/g
        }
      });
  
      block('urldef', {
        'pattern': /^( {0,3})\[(?:\\.|[^\]])+]: *\n? *(?:(?!<)(?:\\.|[^\(\)\s]|\([^\(\)\s]*\))*|<(?:[^<>\n]|\\.)*>)( *\n? *('(?:\\'|[^'])+'|"(?:\\"|[^"])+"))?$/gm,
        'lookbehind': true,
        'inside': {
          'link-label': linkLabel,
          'marker urldef-colon': /^:/,
          'link-title': linkTitle,
          'href': linkURL,
          'braced-href': linkBracedURL
        }
      });
  
      block('blockquote', {
        'pattern': /^[\t ]*>[\t ]?.+(?:\n(?!\n)|.)*/gm,
        'inside': {
          'marker quote-marker': /^[\t ]*>[\t ]?/gm,
          'blockquote-content': {
            'pattern': /[^>]+/,
            'inside': {
              'rest': blocks
            }
          }
        }
      });
  
      block('list', {
        'pattern': /^[\t ]*([*+\-]|\d+\.)[\t ].+(?:\n(?!\n)|.)*/gm,
        'inside': {
          'li': {
            'pattern': /^[\t ]*([*+\-]|\d+\.)[\t ].+(?:\n|[ \t]+[^*+\- \t].*\n)*/gm,
            'inside': {
              'marker list-item': /^[ \t]*([*+\-]|\d+\.)[ \t]/m,
              'rest': blocks
            }
          }
        }
      });
  
      block('code-block indented', {
        'pattern': /(^|(?:^|(?:^|\n)(?![ \t]*([*+\-]|\d+\.)[ \t]).*\n)\s*?\n)((?: {4}|\t).*(?:\n|$))+/g,
        'lookbehind': true
      });
  
      block('p', {
        'pattern': /[^\n](?:\n(?!\n)|.)*[^\n]/g,
        'inside': inlines
      });
  
      inline('image', {
        'pattern': /(^|[^\\])!\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\]\(\s*(?:(?!<)(?:\\.|[^\(\)\s]|\([^\(\)\s]*\))*|<(?:[^<>\n]|\\.)*>)(\s+('(?:[^']|\\')+'|"(?:[^"]|\\")+"))?\s*\)/,
        'lookbehind': true,
        'inside': {
          'link-text': imageText,
          'link-params': linkParams
        }
      });
  
      inline('link', {
        'pattern': /(^|[^\\])\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\]\(\s*(?:(?!<)(?:\\.|[^\(\)\s]|\([^\(\)\s]*\))*|<(?:[^<>\n]|\\.)*>)(\s+('(?:[^']|\\')+'|"(?:[^"]|\\")+"))?\s*\)/,
        'lookbehind': true,
        'inside': {
          'link-text': linkText,
          'link-params': linkParams
        }
      });
  
      inline('image image-ref', {
        'pattern': /(^|[^\\])!\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\] ?\[(?:\\.|[^\]])*\]/,
        'lookbehind': true,
        'inside': {
          'link-text': imageText,
          'link-label': linkLabel
        }
      });
      inline('link link-ref', {
        'pattern': /(^|[^\\])\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\] ?\[(?:\\.|[^\]])*\]/,
        'lookbehind': true,
        'inside': {
          'link-text': linkText,
          'link-label': linkLabel
        }
      });
  
      inline('image image-ref shortcut-ref', {
        'pattern': /(^|[^\\])!\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\]/,
        'lookbehind': true,
        'inside': {
          'marker image-bang': /^!/,
          'link-text': linkText
        }
      });
      inline('link link-ref shortcut-ref', {
        'pattern': /(^|[^\\])\[(?:\\.|[^\[\]]|\[[^\[\]]*\])*\]/,
        'lookbehind': true,
        'inside': {
          'link-text': linkText
        }
      });
  
  
      inline('code', {
        'pattern': /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/g,
        'lookbehind': true,
        'inside': {
          'marker code-marker start': /^`/,
          'marker code-marker end': /`$/,
          'code-inner': /[\w\W]+/
        }
      });
  
      inline('strong', {
        'pattern': /(^|[^\\*_]|\\[*_])([_\*])\2(?:\n(?!\n)|.)+?\2{2}(?!\2)/g,
        // 'pattern': /(^|[^\\])(\*\*|__)(?:\n(?!\n)|.)+?\2/,
        'lookbehind': true,
        'inside': {
          'marker strong-marker start': /^(\*\*|__)/,
          'marker strong-marker end': /(\*\*|__)$/,
          'strong-inner': {
            'pattern': /[\w\W]+/,
            'inside': inlines
          }
        }
      });
  
      inline('em', {
        // 'pattern': /(^|[^\\])(\*|_)(\S[^\2]*?)??[^\s\\]+?\2/g,
        'pattern': /(^|[^\\*_]|\\[*_])(\*|_)(?:\n(?!\n)|.)+?\2(?!\2)/g,
        'lookbehind': true,
        'inside': {
          'marker em-marker start': /^(\*|_)/,
          'marker em-marker end': /(\*|_)$/,
          'em-inner': {
            'pattern': /[\w\W]+/,
            'inside': inlines
          }
        }
      });
  
      inline('strike', {
        'pattern': /(^|\n|(?!\\)\W)(~~)(?=\S)([^\r]*?\S)\2/gm,
        'lookbehind': true,
        'inside': {
          'marker strike-marker start': /^~~/,
          'marker strike-marker end': /~~$/,
          'strike-inner': {
            'pattern': /[\w\W]+/,
            'inside': inlines
          }
        }
      });
  
      inline('comment', Prism['languages']['markup']['comment']);
  
      var tag = Prism['languages']['markup']['tag'];
      var tagMatch = tag['pattern'];
  
      inline('tag', {
        'pattern': new RegExp("(^|[^\\\\])" + tagMatch.source, 'i'),
        'lookbehind': true,
        'inside': tag['inside']
      });
      inline('entity', Prism['languages']['markup']['entity']);
  
      return md;
    })();
  
    var evt = {
      bind: function(el, evt, fn){
        el.addEventListener(evt, fn, false);
      }
    };
  
  
    function spliceString(str, i, remove, add){
      remove = +remove || 0;
      add = add || '';
  
      return str.slice(0,i) + add + str.slice(i+remove);
    }
  
    function normalizeLineEnd(str){
      return str.replace(/\r(\n)?/g, '\n');
    }
  
    var actions = {
      'newline': function(state, options){
        var s = state.start;
        var lf = state.before.lastIndexOf('\n') + 1;
        var afterLf = state.before.slice(lf);
        var indent = afterLf.match(/^\s*/)[0];
        var add = indent;
        var clearPrevLine = false;
  
        if(/^ {0,3}$/.test(indent)){ // maybe list
          var l = afterLf.slice(indent.length);
          if(/^[*+\-]\s+/.test(l)){
            add += l.match(/^[*+\-]\s+/)[0];
            clearPrevLine = /^[*+\-]\s+$/.test(l);
          }else if(/^\d+\.\s+/.test(l)){
            add += l.match(/^\d+\.\s+/)[0]
              .replace(/^\d+/, function(n){ return +n+1; });
            clearPrevLine = /^\d+\.\s+$/.test(l);
          }else if(/^>/.test(l)){
            add += l.match(/^>\s*/)[0];
            clearPrevLine = /^>\s*$/.test(l);
          }
        }
  
        add = '\n' + add;
  
        var del = state.sel;
        state.sel = '';
  
        if(clearPrevLine){ // if prev line was actually an empty liste item, clear it
          del = afterLf + del;
          state.before = state.before.slice(0, lf);
          state.start -= afterLf.length;
          s -= afterLf.length;
          add = '\n';
        }
  
        state.before += add;
        state.start += add.length;
        state.end = state.start;
  
        return { add: add, del: del, start: s };
      },
  
      'indent': function(state, options){
        var lf = state.before.lastIndexOf('\n') + 1;
  
        // TODO deal with soft tabs
  
        if(options.inverse){
          if(/\s/.test(state.before.charAt(lf))){
            state.before = spliceString(state.before, lf, 1);
            state.start -= 1;
          }
          state.sel = state.sel.replace(/\r?\n(?!\r?\n)\s/, '\n');
        }else if(state.sel || options.ctrl){
          state.before = spliceString(state.before, lf, 0, '\t');
          state.sel = state.sel.replace(/\r?\n/, '\n\t');
          state.start += 1;
        }else{
          state.before += '\t';
          state.start += 1;
          state.end  += 1;
  
          return { add: '\t', del: '', start: state.start - 1 };
        }
  
        state.end = state.start + state.sel.length;
  
        return {
          action: 'indent',
          start: state.start,
          end: state.end,
          inverse: options.inverse
        };
      },
  
      'wrap': function(state, options){
        var match = {
            '(': ')',
            '[': ']',
            '{': '}',
            '<': '>'
          }[options.bracket] || options.bracket;
  
        state.before += options.bracket;
        state.after = match + state.after;
        state.start += 1;
        state.end += 1;
  
        return {
          add: options.bracket + state.sel + match,
          del: state.sel,
          start: state.start - 1,
          end: state.end - 1
        };
      }
    };
  
    function SelectionManager(elt){
      this.elt = elt;
    }
  
    SelectionManager.prototype.getStart = function(){
      var selection = getSelection();
  
      if(!selection.rangeCount) return 0;
  
      var range = selection.getRangeAt(0);
      var el = range.startContainer;
      var container = el;
      var offset = range.startOffset;
  
      if(!(this.elt.compareDocumentPosition(el) & 0x10)){
        // selection is outside this element.
        return 0;
      }
  
      do{
        while((el = el.previousSibling)){
          if(el.textContent){
            offset += el.textContent.length;
          }
        }
  
        el = container = container.parentNode;
      }while(el && el !== this.elt);
  
      return offset;
    };
  
    SelectionManager.prototype.getEnd = function(){
      var selection = getSelection();
  
      if(!selection.rangeCount) return 0;
  
      return this.getStart() + String(selection.getRangeAt(0)).length;
    };
  
    SelectionManager.prototype.setRange = function(start, end, noscroll){
      var range = document.createRange();
      var startOffset = findOffset(this.elt, start);
      var endOffset = startOffset;
      if(end && end !== start){
        endOffset = findOffset(this.elt, end);
      }else{
        if(noscroll !== false) scrollToCaret.call(this, endOffset.element, endOffset.offset);
      }
  
      range.setStart(startOffset.element, startOffset.offset);
      range.setEnd(endOffset.element, endOffset.offset);
  
      var selection = getSelection();
      selection.removeAllRanges();
      selection.addRange(range);
    };
  
  
  
    var caret = document.createElement('span');
    caret.style.position = 'absolute';
    caret.innerHTML = '|';
  
    function scrollToCaret(el, offset){
      var t = el.textContent;
      var p = el.parentNode;
      var before = t.slice(0, offset);
      var after = t.slice(offset);
  
      el.textContent = after;
      var b4 = document.createTextNode(before);
      p.insertBefore(caret, el);
      p.insertBefore(b4, caret);
  
      // caret.scrollIntoViewIfNeeded();
      var tp = caret.offsetTop;
      var h = caret.offsetHeight;
      var ch = this.elt.offsetHeight;
      var st = this.elt.scrollTop;
  
      el.textContent = t;
      p.removeChild(caret);
      p.removeChild(b4);
  
      if(tp - st < 0){
        this.elt.scrollTop = tp;
      }else if(tp - st + h > ch){
        this.elt.scrollTop = tp + h - ch;
      }
    }
  
  
  
  
    function findOffset(root, ss) {
      if(!root) {
        return null;
      }
  
      var offset = 0,
        element = root;
  
      do {
        var container = element;
        element = element.firstChild;
  
        if(element) {
          do {
            var len = element.textContent.length;
  
            if(offset <= ss && offset + len > ss) {
              break;
            }
  
            offset += len;
          } while(element = element.nextSibling);
        }
  
        if(!element) {
          // It's the container's lastChild
          break;
        }
      } while(element && element.hasChildNodes() && element.nodeType != 3);
  
      if(element) {
        return {
          element: element,
          offset: ss - offset
        };
      }
      else if(container) {
        element = container;
  
        while(element && element.lastChild) {
          element = element.lastChild;
        }
  
        if(element.nodeType === 3) {
          return {
            element: element,
            offset: element.textContent.length
          };
        }
        else {
          return {
            element: element,
            offset: 0
          };
        }
      }
  
      return {
        element: root,
        offset: 0,
        error: true
      };
    }
  
    function UndoManager(editor){
      this.editor = editor;
  
      this.undoStack = [];
      this.redoStack = [];
    }
  
    UndoManager.prototype.action = function(a){
      /// sanity?
  
      if(this.undoStack.length && this.canCombine(this.undoStack[this.undoStack.length-1], a)){
        this.undoStack.push(this.combine(this.undoStack.pop(), a));
      }else{
        this.undoStack.push(a);
      }
      this.redoStack = [];
    };
  
    UndoManager.prototype.canCombine = function(a, b){
      return (
        !a.action && !b.action &&
        !Array.isArray(a) && !Array.isArray(b) &&
        !(a.del && b.add) && !(a.add && b.del) &&
        !(a.add && !b.add) && !(!a.add && b.add) &&
        !(a.add && a.del) &&
        !(b.add && b.del) &&
        a.start + a.add.length === b.start + b.del.length
      );
    };
  
    UndoManager.prototype.combine = function(a, b){
      return {
        add: a.add + b.add,
        del: b.del + a.del,
        start: Math.min(a.start, b.start)
      };
    };
  
    UndoManager.prototype.undo = function(){
      if(!this.undoStack.length) return;
  
      var a = this.undoStack.pop();
      this.redoStack.push(a);
  
      this.applyInverse(a);
    };
  
    UndoManager.prototype.redo = function(){
      if(!this.redoStack.length) return;
  
      var a = this.redoStack.pop();
      this.undoStack.push(a);
  
      this.apply(a);
    };
  
    UndoManager.prototype.apply = function apply(a){
      if(Array.isArray(a)){
        a.forEach(apply.bind(this));
        return;
      }
  
      if(a.action){
        this.editor.action(a.action, {
          inverse: a.inverse,
          start: a.start,
          end: a.end,
          noHistory: true
        });
      }else{
        this.editor.apply(a);
      }
    };
  
    UndoManager.prototype.applyInverse = function inv(a){
      if(Array.isArray(a)){
        a.forEach(inv.bind(this));
        return;
      }
  
      if(a.action){
        this.editor.action(a.action, {
          inverse: !a.inverse,
          start: a.start,
          end: a.end,
          noHistory: true
        });
      }else{
        this.editor.apply({
          start: a.start,
          end: a.end,
          del: a.add,
          add: a.del
        });
      }
    };
  
    function Editor(el, opts){
  
      if(!(this instanceof Editor)){
        return new Editor(el, opts);
      }
  
      opts = opts || {};
  
      if(el.tagName === 'PRE'){
        this.el = el;
      }else{
        this.el = document.createElement('pre');
        el.appendChild(this.el);
      }
  
      var cname = opts['className'] || '';
  
      this.el.className = this.el.className ? this.el.className + ' ' : '';
      this.el.className += 'mdedit' + (cname ? ' ' + cname : '');
      this.el.setAttribute('contenteditable', true);
  
      var inner = this.inner = document.createElement('div');
      inner.innerHTML = this.el.innerHTML;
      this.el.innerHTML = '';
      this.el.appendChild(inner);
  
      this.selMgr = new SelectionManager(el);
      this.undoMgr = new UndoManager(this);
  
      evt.bind(el, 'cut', this.cut.bind(this));
      evt.bind(el, 'paste', this.paste.bind(this));
      evt.bind(el, 'keyup', this.keyup.bind(this));
      evt.bind(el, 'input', this.changed.bind(this));
      evt.bind(el, 'keydown', this.keydown.bind(this));
      evt.bind(el, 'keypress', this.keypress.bind(this));
  
  
      var changeCb = opts['change'];
      this.changeCb = changeCb || function(){};
  
      this.changed();
    }
  
    Editor.prototype.fireChange = function(){
      var prev = this._prevValue;
      var now = this.getValue();
      if(prev !== now){
        this.changeCb(now);
        this._prevValue = now;
      }
    };
  
    Editor.prototype['setValue'] = function(val){
      this.setText(val);
      this.changed();
    };
  
    Editor.prototype['getValue'] = function(){
      return this.getText();
    };
  
    Editor.prototype.getText = function(){
      return this.inner.textContent;
    };
  
    Editor.prototype.setText = function(val){
      this.inner.textContent = normalizeLineEnd(val);
    };
  
    Editor.prototype.keyup = function(evt){
      var keyCode = evt && evt.keyCode || 0,
        code = this.getText();
  
      if([
          9, 91, 93, 16, 17, 18, // modifiers
          20, // caps lock
          13, // Enter (handled by keydown)
          112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, // F[0-12]
          27 // Esc
        ].indexOf(keyCode) > -1) {
        return;
      }
  
  //   comment out to fix issue #24: It doesn't allow me to type in Chinese characters
  //   if([
  //     33, 34, // PgUp, PgDn
  //     35, 36, // End, Home
  //     37, 39, 38, 40 // Left, Right, Up, Down
  //   ].indexOf(keyCode) === -1) {
  //     this.changed();
  //   }
    };
    
    var _log = function(obj) {
      console.log(Date.now() + ' >>> ' + obj);
    }
  
    Editor.prototype.changed = function(evt){
      _log("triggering changed");
      var code = this.getText();
  
      var ss = this.selMgr.getStart(),
        se = this.selMgr.getEnd();
  
      this.saveScrollPos();
      _log("scroll pos saved");
  
      var setHTML;
  
      if(code === this._prevCode){
        if(this.inner.innerHTML !== this._prevHTML) setHTML = this._prevHTML;
      }else{
        this._prevHTML = setHTML = Prism['highlight'](code, md);
      }
      this._prevCode = code;
      _log("_preCode set");
  
      if(setHTML !== undefined){
        if(!/\n$/.test(code)) {
          setHTML += '\n';
        }
  
        var dummy = this.inner.cloneNode(false);
        dummy.innerHTML = setHTML;
        this.el.replaceChild(dummy, this.inner);
        this.inner = dummy;
      }
      _log("inner set")
  
      this.restoreScrollPos();
      _log("scroll pos restored")
  
      if(ss !== null || se !== null) {
        this.selMgr.setRange(ss, se);
      }
  
      this.fireChange();
      _log("change fired");
    };
  
    Editor.prototype.saveScrollPos = function(){
      if(this.st === undefined) this.st = this.el.scrollTop;
      setTimeout(function(){
        this.st = undefined;
      }.bind(this), 500);
    };
  
    Editor.prototype.restoreScrollPos = function(){
      this.el.scrollTop = this.st;
      this.st = undefined;
    };
  
  
    Editor.prototype.keypress = function(evt){
      var ctrl = evt.metaKey || evt.ctrlKey;
  
      if(ctrl) return;
  
      var code = evt.charCode;
  
      if(!code) return;
  
      var start = this.selMgr.getStart();
      var end = this.selMgr.getEnd();
  
      var chr = String.fromCharCode(code);
  
      if(/[\[\{\(<"'~\*_]/.test(chr) && start !== end){
        this.action('wrap', {
          bracket: chr
        });
        evt.preventDefault();
        return;
      }
  
  
      this.undoMgr.action({
        add: chr,
        del: start === end ? '' : this.getText().slice(start, end),
        start: start
      });
    };
  
    Editor.prototype.keydown = function(evt){
      var cmdOrCtrl = evt.metaKey || evt.ctrlKey;
  
      switch(evt.keyCode) {
        case 8: // Backspace
        case 46: // Delete
          var start = this.selMgr.getStart();
          var end = this.selMgr.getEnd();
          var length = start === end ? 1 : Math.abs(end - start);
          start = evt.keyCode === 8 ? end - length : start;
          this.undoMgr.action({
            add: '',
            del: this.getText().slice(start, start + length),
            start: start
          });
          break;
        case 9: // Tab
          if(!cmdOrCtrl) {
            this.action('indent', {
              inverse: evt.shiftKey
            });
            evt.preventDefault();
          }
          break;
        case 219: // [
        case 221: // ]
          if(cmdOrCtrl && !evt.shiftKey) {
            this.action('indent', {
              inverse: evt.keyCode === 219,
              ctrl: true
            });
            evt.preventDefault();
          }
          break;
        case 13:
          this.action('newline');
          evt.preventDefault();
          break;
        case 89:
          if(cmdOrCtrl){
            this.undoMgr.redo();
            evt.preventDefault();
          }
          break;
        case 90:
          if(cmdOrCtrl) {
            evt.shiftKey ? this.undoMgr.redo() : this.undoMgr.undo();
            evt.preventDefault();
          }
  
          break;
      }
    };
  
    Editor.prototype.apply = function(action){
      this.setText(spliceString(this.getText(), action.start, action.del.length, action.add));
      this.selMgr.setRange(action.start, action.start + action.add.length);
      this.changed();
    };
  
    Editor.prototype.action = function(act, opts){
      opts = opts || {};
      var text = this.getText();
      var start = opts.start || this.selMgr.getStart();
      var end = opts.end || this.selMgr.getEnd();
  
      var state = {
        start: start,
        end: end,
        before: text.slice(0, start),
        after: text.slice(end),
        sel: text.slice(start, end)
      };
  
      var a = actions[act](state, opts);
  
      this.saveScrollPos();
  
      this.setText(state.before + state.sel + state.after);
  
      if(a && !opts.noHistory){
        this.undoMgr.action(a);
      }
      this.selMgr.setRange(state.start, state.end, false);
  
      this.changed();
  
    };
  
    Editor.prototype.cut = function(){
      var start = this.selMgr.getStart();
      var end = this.selMgr.getEnd();
      if(start === end) return;
  
      this.undoMgr.action({
        add: '',
        del: this.getText().slice(start, end),
        start: start
      });
    };
  
    Editor.prototype.paste = function(evt){
      var start = this.selMgr.getStart();
      var end = this.selMgr.getEnd();
      var selection = start === end ? '' : this.getText().slice(start, end);
  
      var self = this;
  
      function applyPasted(pasted){
        self.undoMgr.action({
          add: pasted,
          del: selection,
          start: start
        });
  
        start += pasted.length;
        self.selMgr.setRange(start, start);
        self.changed();
      }
  
      var insert = typeof evt == 'string';
  
      if(insert || evt.clipboardData){
        if (!insert) {
          evt.preventDefault();
        }
  
        var pasted = insert ? evt : evt.clipboardData.getData('text/plain');
        pasted = normalizeLineEnd(pasted);
  
        this.apply({
          add: pasted,
          del: selection,
          start: start
        });
  
        applyPasted(pasted);
      }else{
        // handle IE9 with no clipboardData. Flickers a bit if styles have changed :(
        setTimeout(function(){
          var newEnd = self.selMgr.getEnd();
  
          applyPasted(self.getText().slice(start, newEnd));
        }, 0);
      }
  
    };
  
    return Editor;
  
  }));
  