From 3a754133dbc138390503341fd2e9beba3e43aa4b Mon Sep 17 00:00:00 2001 From: Jose Antonio Marquez Date: Fri, 27 Jan 2012 12:05:17 -0800 Subject: Merged old FileIO --- imports/codemirror/LICENSE | 19 + imports/codemirror/keymap/emacs.js | 29 + imports/codemirror/keymap/vim.js | 76 + imports/codemirror/lib/codemirror.css | 104 + imports/codemirror/lib/codemirror.js | 2761 ++++++++++++++++++++ imports/codemirror/lib/util/dialog.css | 23 + imports/codemirror/lib/util/dialog.js | 63 + imports/codemirror/lib/util/foldcode.js | 66 + imports/codemirror/lib/util/formatting.js | 291 +++ imports/codemirror/lib/util/javascript-hint.js | 83 + imports/codemirror/lib/util/overlay.js | 51 + imports/codemirror/lib/util/runmode.js | 27 + imports/codemirror/lib/util/search.js | 114 + imports/codemirror/lib/util/searchcursor.js | 117 + imports/codemirror/lib/util/simple-hint.css | 16 + imports/codemirror/lib/util/simple-hint.js | 66 + imports/codemirror/mode/clike/clike.js | 249 ++ imports/codemirror/mode/clike/index.html | 101 + imports/codemirror/mode/clojure/clojure.js | 207 ++ imports/codemirror/mode/clojure/index.html | 66 + imports/codemirror/mode/coffeescript/LICENSE | 22 + .../codemirror/mode/coffeescript/coffeescript.js | 325 +++ imports/codemirror/mode/coffeescript/index.html | 721 +++++ imports/codemirror/mode/css/css.js | 124 + imports/codemirror/mode/css/index.html | 55 + imports/codemirror/mode/diff/diff.css | 3 + imports/codemirror/mode/diff/diff.js | 13 + imports/codemirror/mode/diff/index.html | 99 + imports/codemirror/mode/gfm/gfm.js | 108 + imports/codemirror/mode/gfm/index.html | 47 + imports/codemirror/mode/groovy/groovy.js | 210 ++ imports/codemirror/mode/groovy/index.html | 71 + imports/codemirror/mode/haskell/haskell.js | 242 ++ imports/codemirror/mode/haskell/index.html | 60 + .../codemirror/mode/htmlembedded/htmlembedded.js | 68 + imports/codemirror/mode/htmlembedded/index.html | 49 + imports/codemirror/mode/htmlmixed/htmlmixed.js | 83 + imports/codemirror/mode/htmlmixed/index.html | 51 + imports/codemirror/mode/javascript/index.html | 77 + imports/codemirror/mode/javascript/javascript.js | 360 +++ imports/codemirror/mode/jinja2/index.html | 37 + imports/codemirror/mode/jinja2/jinja2.js | 42 + imports/codemirror/mode/lua/index.html | 72 + imports/codemirror/mode/lua/lua.js | 140 + imports/codemirror/mode/markdown/index.html | 339 +++ imports/codemirror/mode/markdown/markdown.js | 242 ++ imports/codemirror/mode/ntriples/index.html | 32 + imports/codemirror/mode/ntriples/ntriples.js | 172 ++ imports/codemirror/mode/pascal/LICENSE | 7 + imports/codemirror/mode/pascal/index.html | 48 + imports/codemirror/mode/pascal/pascal.js | 138 + imports/codemirror/mode/perl/LICENSE | 19 + imports/codemirror/mode/perl/index.html | 62 + imports/codemirror/mode/perl/perl.js | 816 ++++++ imports/codemirror/mode/php/index.html | 48 + imports/codemirror/mode/php/php.js | 120 + imports/codemirror/mode/plsql/index.html | 62 + imports/codemirror/mode/plsql/plsql.js | 217 ++ imports/codemirror/mode/python/LICENSE.txt | 21 + imports/codemirror/mode/python/index.html | 122 + imports/codemirror/mode/python/python.js | 333 +++ imports/codemirror/mode/r/LICENSE | 24 + imports/codemirror/mode/r/index.html | 73 + imports/codemirror/mode/r/r.js | 141 + imports/codemirror/mode/rpm/changes/changes.js | 19 + imports/codemirror/mode/rpm/changes/index.html | 53 + imports/codemirror/mode/rpm/spec/index.html | 99 + imports/codemirror/mode/rpm/spec/spec.css | 5 + imports/codemirror/mode/rpm/spec/spec.js | 66 + imports/codemirror/mode/rst/index.html | 525 ++++ imports/codemirror/mode/rst/rst.js | 326 +++ imports/codemirror/mode/ruby/LICENSE | 24 + imports/codemirror/mode/ruby/index.html | 171 ++ imports/codemirror/mode/ruby/ruby.js | 195 ++ imports/codemirror/mode/rust/index.html | 48 + imports/codemirror/mode/rust/rust.js | 411 +++ imports/codemirror/mode/scheme/index.html | 64 + imports/codemirror/mode/scheme/scheme.js | 202 ++ imports/codemirror/mode/smalltalk/index.html | 55 + imports/codemirror/mode/smalltalk/smalltalk.js | 139 + imports/codemirror/mode/sparql/index.html | 40 + imports/codemirror/mode/sparql/sparql.js | 143 + imports/codemirror/mode/stex/index.html | 95 + imports/codemirror/mode/stex/stex.js | 167 ++ imports/codemirror/mode/tiddlywiki/index.html | 183 ++ imports/codemirror/mode/tiddlywiki/tiddlywiki.css | 21 + imports/codemirror/mode/tiddlywiki/tiddlywiki.js | 374 +++ imports/codemirror/mode/velocity/index.html | 103 + imports/codemirror/mode/velocity/velocity.js | 146 ++ imports/codemirror/mode/xml/index.html | 44 + imports/codemirror/mode/xml/xml.js | 252 ++ imports/codemirror/mode/xmlpure/index.html | 59 + imports/codemirror/mode/xmlpure/xmlpure.js | 485 ++++ imports/codemirror/mode/yaml/index.html | 67 + imports/codemirror/mode/yaml/yaml.js | 95 + imports/codemirror/notice.txt | 4 + imports/codemirror/theme/cobalt.css | 18 + imports/codemirror/theme/eclipse.css | 25 + imports/codemirror/theme/elegant.css | 10 + imports/codemirror/theme/monokai.css | 28 + imports/codemirror/theme/neat.css | 9 + imports/codemirror/theme/night.css | 21 + imports/codemirror/theme/rubyblue.css | 21 + imports/codemirror/version.txt | 1 + 104 files changed, 15457 insertions(+) create mode 100755 imports/codemirror/LICENSE create mode 100755 imports/codemirror/keymap/emacs.js create mode 100755 imports/codemirror/keymap/vim.js create mode 100755 imports/codemirror/lib/codemirror.css create mode 100755 imports/codemirror/lib/codemirror.js create mode 100755 imports/codemirror/lib/util/dialog.css create mode 100755 imports/codemirror/lib/util/dialog.js create mode 100755 imports/codemirror/lib/util/foldcode.js create mode 100755 imports/codemirror/lib/util/formatting.js create mode 100755 imports/codemirror/lib/util/javascript-hint.js create mode 100755 imports/codemirror/lib/util/overlay.js create mode 100755 imports/codemirror/lib/util/runmode.js create mode 100755 imports/codemirror/lib/util/search.js create mode 100755 imports/codemirror/lib/util/searchcursor.js create mode 100755 imports/codemirror/lib/util/simple-hint.css create mode 100755 imports/codemirror/lib/util/simple-hint.js create mode 100755 imports/codemirror/mode/clike/clike.js create mode 100755 imports/codemirror/mode/clike/index.html create mode 100755 imports/codemirror/mode/clojure/clojure.js create mode 100755 imports/codemirror/mode/clojure/index.html create mode 100755 imports/codemirror/mode/coffeescript/LICENSE create mode 100755 imports/codemirror/mode/coffeescript/coffeescript.js create mode 100755 imports/codemirror/mode/coffeescript/index.html create mode 100755 imports/codemirror/mode/css/css.js create mode 100755 imports/codemirror/mode/css/index.html create mode 100755 imports/codemirror/mode/diff/diff.css create mode 100755 imports/codemirror/mode/diff/diff.js create mode 100755 imports/codemirror/mode/diff/index.html create mode 100755 imports/codemirror/mode/gfm/gfm.js create mode 100755 imports/codemirror/mode/gfm/index.html create mode 100755 imports/codemirror/mode/groovy/groovy.js create mode 100755 imports/codemirror/mode/groovy/index.html create mode 100755 imports/codemirror/mode/haskell/haskell.js create mode 100755 imports/codemirror/mode/haskell/index.html create mode 100755 imports/codemirror/mode/htmlembedded/htmlembedded.js create mode 100755 imports/codemirror/mode/htmlembedded/index.html create mode 100755 imports/codemirror/mode/htmlmixed/htmlmixed.js create mode 100755 imports/codemirror/mode/htmlmixed/index.html create mode 100755 imports/codemirror/mode/javascript/index.html create mode 100755 imports/codemirror/mode/javascript/javascript.js create mode 100755 imports/codemirror/mode/jinja2/index.html create mode 100755 imports/codemirror/mode/jinja2/jinja2.js create mode 100755 imports/codemirror/mode/lua/index.html create mode 100755 imports/codemirror/mode/lua/lua.js create mode 100755 imports/codemirror/mode/markdown/index.html create mode 100755 imports/codemirror/mode/markdown/markdown.js create mode 100755 imports/codemirror/mode/ntriples/index.html create mode 100755 imports/codemirror/mode/ntriples/ntriples.js create mode 100755 imports/codemirror/mode/pascal/LICENSE create mode 100755 imports/codemirror/mode/pascal/index.html create mode 100755 imports/codemirror/mode/pascal/pascal.js create mode 100755 imports/codemirror/mode/perl/LICENSE create mode 100755 imports/codemirror/mode/perl/index.html create mode 100755 imports/codemirror/mode/perl/perl.js create mode 100755 imports/codemirror/mode/php/index.html create mode 100755 imports/codemirror/mode/php/php.js create mode 100755 imports/codemirror/mode/plsql/index.html create mode 100755 imports/codemirror/mode/plsql/plsql.js create mode 100755 imports/codemirror/mode/python/LICENSE.txt create mode 100755 imports/codemirror/mode/python/index.html create mode 100755 imports/codemirror/mode/python/python.js create mode 100755 imports/codemirror/mode/r/LICENSE create mode 100755 imports/codemirror/mode/r/index.html create mode 100755 imports/codemirror/mode/r/r.js create mode 100755 imports/codemirror/mode/rpm/changes/changes.js create mode 100755 imports/codemirror/mode/rpm/changes/index.html create mode 100755 imports/codemirror/mode/rpm/spec/index.html create mode 100755 imports/codemirror/mode/rpm/spec/spec.css create mode 100755 imports/codemirror/mode/rpm/spec/spec.js create mode 100755 imports/codemirror/mode/rst/index.html create mode 100755 imports/codemirror/mode/rst/rst.js create mode 100755 imports/codemirror/mode/ruby/LICENSE create mode 100755 imports/codemirror/mode/ruby/index.html create mode 100755 imports/codemirror/mode/ruby/ruby.js create mode 100755 imports/codemirror/mode/rust/index.html create mode 100755 imports/codemirror/mode/rust/rust.js create mode 100755 imports/codemirror/mode/scheme/index.html create mode 100755 imports/codemirror/mode/scheme/scheme.js create mode 100755 imports/codemirror/mode/smalltalk/index.html create mode 100755 imports/codemirror/mode/smalltalk/smalltalk.js create mode 100755 imports/codemirror/mode/sparql/index.html create mode 100755 imports/codemirror/mode/sparql/sparql.js create mode 100755 imports/codemirror/mode/stex/index.html create mode 100755 imports/codemirror/mode/stex/stex.js create mode 100755 imports/codemirror/mode/tiddlywiki/index.html create mode 100755 imports/codemirror/mode/tiddlywiki/tiddlywiki.css create mode 100755 imports/codemirror/mode/tiddlywiki/tiddlywiki.js create mode 100755 imports/codemirror/mode/velocity/index.html create mode 100755 imports/codemirror/mode/velocity/velocity.js create mode 100755 imports/codemirror/mode/xml/index.html create mode 100755 imports/codemirror/mode/xml/xml.js create mode 100755 imports/codemirror/mode/xmlpure/index.html create mode 100755 imports/codemirror/mode/xmlpure/xmlpure.js create mode 100755 imports/codemirror/mode/yaml/index.html create mode 100755 imports/codemirror/mode/yaml/yaml.js create mode 100755 imports/codemirror/notice.txt create mode 100755 imports/codemirror/theme/cobalt.css create mode 100755 imports/codemirror/theme/eclipse.css create mode 100755 imports/codemirror/theme/elegant.css create mode 100755 imports/codemirror/theme/monokai.css create mode 100755 imports/codemirror/theme/neat.css create mode 100755 imports/codemirror/theme/night.css create mode 100755 imports/codemirror/theme/rubyblue.css create mode 100755 imports/codemirror/version.txt (limited to 'imports') diff --git a/imports/codemirror/LICENSE b/imports/codemirror/LICENSE new file mode 100755 index 00000000..3f7c0bb1 --- /dev/null +++ b/imports/codemirror/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2011 by Marijn Haverbeke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/imports/codemirror/keymap/emacs.js b/imports/codemirror/keymap/emacs.js new file mode 100755 index 00000000..8fd3564e --- /dev/null +++ b/imports/codemirror/keymap/emacs.js @@ -0,0 +1,29 @@ +// TODO number prefixes +(function() { + // Really primitive kill-ring implementation. + var killRing = []; + function addToRing(str) { + killRing.push(str); + if (killRing.length > 50) killRing.shift(); + } + function getFromRing() { return killRing[killRing.length - 1] || ""; } + function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); } + + CodeMirror.keyMap.emacs = { + "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");}, + "Ctrl-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");}, + "Ctrl-Alt-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");}, + "Alt-W": function(cm) {addToRing(cm.getSelection());}, + "Ctrl-Y": function(cm) {cm.replaceSelection(getFromRing());}, + "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());}, + "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", + "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace", + "Ctrl-Z": "undo", "Cmd-Z": "undo", + fallthrough: ["basic", "emacsy"] + }; + + CodeMirror.keyMap["emacs-Ctrl-X"] = { + "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close", + auto: "emacs", catchall: function(cm) {/*ignore*/} + }; +})(); diff --git a/imports/codemirror/keymap/vim.js b/imports/codemirror/keymap/vim.js new file mode 100755 index 00000000..e03e0128 --- /dev/null +++ b/imports/codemirror/keymap/vim.js @@ -0,0 +1,76 @@ +(function() { + var count = ""; + function pushCountDigit(digit) { return function(cm) {count += digit;} } + function popCount() { var i = parseInt(count); count = ""; return i || 1; } + function countTimes(func) { + if (typeof func == "string") func = CodeMirror.commands[func]; + return function(cm) { for (var i = 0, c = popCount(); i < c; ++i) func(cm); } + } + + function iterObj(o, f) { + for (var prop in o) if (o.hasOwnProperty(prop)) f(prop, o[prop]); + } + + var word = [/\w/, /[^\w\s]/], bigWord = [/\S/]; + function findWord(line, pos, dir, regexps) { + var stop = 0, next = -1; + if (dir > 0) { stop = line.length; next = 0; } + var start = stop, end = stop; + // Find bounds of next one. + outer: for (; pos != stop; pos += dir) { + for (var i = 0; i < regexps.length; ++i) { + if (regexps[i].test(line.charAt(pos + next))) { + start = pos; + for (; pos != stop; pos += dir) { + if (!regexps[i].test(line.charAt(pos + next))) break; + } + end = pos; + break outer; + } + } + } + return {from: Math.min(start, end), to: Math.max(start, end)}; + } + function moveToWord(cm, regexps, dir, where) { + var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line), word; + while (true) { + word = findWord(line, ch, dir, regexps); + ch = word[where == "end" ? "to" : "from"]; + if (ch == cur.ch && word.from != word.to) ch = word[dir < 0 ? "from" : "to"]; + else break; + } + cm.setCursor(cur.line, word[where == "end" ? "to" : "from"], true); + } + + var map = CodeMirror.keyMap.vim = { + "0": function(cm) {count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);}, + "I": function(cm) {popCount(); cm.setOption("keyMap", "vim-insert");}, + "G": function(cm) {cm.setOption("keyMap", "vim-prefix-g");}, + catchall: function(cm) {/*ignore*/} + }; + // Add bindings for number keys + for (var i = 1; i < 10; ++i) map[i] = pushCountDigit(i); + // Add bindings that are influenced by number keys + iterObj({"H": "goColumnLeft", "L": "goColumnRight", "J": "goLineDown", "K": "goLineUp", + "Left": "goColumnLeft", "Right": "goColumnRight", "Down": "goLineDown", "Up": "goLineUp", + "Backspace": "goCharLeft", "Space": "goCharRight", + "B": function(cm) {moveToWord(cm, word, -1, "end");}, + "E": function(cm) {moveToWord(cm, word, 1, "end");}, + "W": function(cm) {moveToWord(cm, word, 1, "start");}, + "Shift-B": function(cm) {moveToWord(cm, bigWord, -1, "end");}, + "Shift-E": function(cm) {moveToWord(cm, bigWord, 1, "end");}, + "Shift-W": function(cm) {moveToWord(cm, bigWord, 1, "start");}, + "U": "undo", "Ctrl-R": "redo", "Shift-4": "goLineEnd"}, + function(key, cmd) { map[key] = countTimes(cmd); }); + + CodeMirror.keyMap["vim-prefix-g"] = { + "E": countTimes(function(cm) { moveToWord(cm, word, -1, "start");}), + "Shift-E": countTimes(function(cm) { moveToWord(cm, bigWord, -1, "start");}), + auto: "vim", catchall: function(cm) {/*ignore*/} + }; + + CodeMirror.keyMap["vim-insert"] = { + "Esc": function(cm) {cm.setOption("keyMap", "vim");}, + fallthrough: ["default"] + }; +})(); diff --git a/imports/codemirror/lib/codemirror.css b/imports/codemirror/lib/codemirror.css new file mode 100755 index 00000000..11614251 --- /dev/null +++ b/imports/codemirror/lib/codemirror.css @@ -0,0 +1,104 @@ +.CodeMirror { + line-height: 1em; + font-family: monospace; +} + +.CodeMirror-scroll { + overflow: auto; + height: 300px; + /* This is needed to prevent an IE[67] bug where the scrolled content + is visible outside of the scrolling box. */ + position: relative; +} + +.CodeMirror-gutter { + position: absolute; left: 0; top: 0; + z-index: 10; + background-color: #f7f7f7; + border-right: 1px solid #eee; + min-width: 2em; + height: 100%; +} +.CodeMirror-gutter-text { + color: #aaa; + text-align: right; + padding: .4em .2em .4em .4em; + white-space: pre !important; +} +.CodeMirror-lines { + padding: .4em; +} + +.CodeMirror pre { + -moz-border-radius: 0; + -webkit-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + border-width: 0; margin: 0; padding: 0; background: transparent; + font-family: inherit; + font-size: inherit; + padding: 0; margin: 0; + white-space: pre; + word-wrap: normal; +} + +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; +} +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror textarea { + outline: none !important; +} + +.CodeMirror pre.CodeMirror-cursor { + z-index: 10; + position: absolute; + visibility: hidden; + border-left: 1px solid black; +} +.CodeMirror-focused pre.CodeMirror-cursor { + visibility: visible; +} + +span.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused span.CodeMirror-selected { background: #d2dcf8; } + +.CodeMirror-searching {background: #ffa;} + +/* Default theme */ + +.cm-s-default span.cm-keyword {color: #708;} +.cm-s-default span.cm-atom {color: #219;} +.cm-s-default span.cm-number {color: #164;} +.cm-s-default span.cm-def {color: #00f;} +.cm-s-default span.cm-variable {color: black;} +.cm-s-default span.cm-variable-2 {color: #05a;} +.cm-s-default span.cm-variable-3 {color: #085;} +.cm-s-default span.cm-property {color: black;} +.cm-s-default span.cm-operator {color: black;} +.cm-s-default span.cm-comment {color: #a50;} +.cm-s-default span.cm-string {color: #a11;} +.cm-s-default span.cm-string-2 {color: #f50;} +.cm-s-default span.cm-meta {color: #555;} +.cm-s-default span.cm-error {color: #f00;} +.cm-s-default span.cm-qualifier {color: #555;} +.cm-s-default span.cm-builtin {color: #30a;} +.cm-s-default span.cm-bracket {color: #cc7;} +.cm-s-default span.cm-tag {color: #170;} +.cm-s-default span.cm-attribute {color: #00c;} +.cm-s-default span.cm-header {color: #a0a;} +.cm-s-default span.cm-quote {color: #090;} +.cm-s-default span.cm-hr {color: #999;} +.cm-s-default span.cm-link {color: #00c;} + +span.cm-header, span.cm-strong {font-weight: bold;} +span.cm-em {font-style: italic;} +span.cm-emstrong {font-style: italic; font-weight: bold;} +span.cm-link {text-decoration: underline;} + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} diff --git a/imports/codemirror/lib/codemirror.js b/imports/codemirror/lib/codemirror.js new file mode 100755 index 00000000..79eb457d --- /dev/null +++ b/imports/codemirror/lib/codemirror.js @@ -0,0 +1,2761 @@ +// CodeMirror version 2.2 +// +// All functions that need access to the editor's state live inside +// the CodeMirror function. Below that, at the bottom of the file, +// some utilities are defined. + +// CodeMirror is the only global var we claim +var CodeMirror = (function() { + // This is the function that produces an editor instance. It's + // closure is used to store the editor state. + function CodeMirror(place, givenOptions) { + // Determine effective options based on given values and defaults. + var options = {}, defaults = CodeMirror.defaults; + for (var opt in defaults) + if (defaults.hasOwnProperty(opt)) + options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; + + var targetDocument = options["document"]; + // The element in which the editor lives. + var wrapper = targetDocument.createElement("div"); + wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""); + // This mess creates the base DOM structure for the editor. + wrapper.innerHTML = + '
' + // Wraps and hides input textarea + '
' + + '
' + + '
' + // Set to the height of the text, causes scrolling + '
' + // Moved around its parent to cover visible view + '
' + + // Provides positioning relative to (visible) text origin + '
' + + '
' + + '
 
' + // Absolutely positioned blinky cursor + '
' + // This DIV contains the actual code + '
'; + if (place.appendChild) place.appendChild(wrapper); else place(wrapper); + // I've never seen more elegant code in my life. + var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, + scroller = wrapper.lastChild, code = scroller.firstChild, + mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, + lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, + cursor = measure.nextSibling, lineDiv = cursor.nextSibling; + themeChanged(); + // Needed to hide big blue blinking cursor on Mobile Safari + if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) input.style.width = "0px"; + if (!webkit) lineSpace.draggable = true; + if (options.tabindex != null) input.tabIndex = options.tabindex; + if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + + // Check for problem with IE innerHTML not working when we have a + // P (or similar) parent node. + try { stringWidth("x"); } + catch (e) { + if (e.message.match(/runtime/i)) + e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); + throw e; + } + + // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. + var poll = new Delayed(), highlight = new Delayed(), blinker; + + // mode holds a mode API object. doc is the tree of Line objects, + // work an array of lines that should be parsed, and history the + // undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; + loadMode(); + // The selection. These are always maintained to point at valid + // positions. Inverted is used to remember that the user is + // selecting bottom-to-top. + var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; + // Selection-related flags. shiftSelecting obviously tracks + // whether the user is holding shift. + var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false; + // Variables used by startOperation/endOperation to track what + // happened during the operation. + var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + gutterDirty, callbacks; + // Current visible range (may be bigger than the view window). + var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; + // bracketHighlighted is used to remember that a backet has been + // marked. + var bracketHighlighted; + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + var maxLine = "", maxWidth, tabText = computeTabText(); + + // Initialize the content. + operation(function(){setValue(options.value || ""); updateInput = false;})(); + var history = new History(); + + // Register our event handlers. + connect(scroller, "mousedown", operation(onMouseDown)); + connect(scroller, "dblclick", operation(onDoubleClick)); + connect(lineSpace, "dragstart", onDragStart); + connect(lineSpace, "selectstart", e_preventDefault); + // Gecko browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for Gecko. + if (!gecko) connect(scroller, "contextmenu", onContextMenu); + connect(scroller, "scroll", function() { + updateDisplay([]); + if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; + if (options.onScroll) options.onScroll(instance); + }); + connect(window, "resize", function() {updateDisplay(true);}); + connect(input, "keyup", operation(onKeyUp)); + connect(input, "input", fastPoll); + connect(input, "keydown", operation(onKeyDown)); + connect(input, "keypress", operation(onKeyPress)); + connect(input, "focus", onFocus); + connect(input, "blur", onBlur); + + connect(scroller, "dragenter", e_stop); + connect(scroller, "dragover", e_stop); + connect(scroller, "drop", operation(onDrop)); + connect(scroller, "paste", function(){focusInput(); fastPoll();}); + connect(input, "paste", fastPoll); + connect(input, "cut", operation(function(){replaceSelection("");})); + + // IE throws unspecified error in certain cases, when + // trying to access activeElement before onload + var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } + if (hasFocus) setTimeout(onFocus, 20); + else onBlur(); + + function isLine(l) {return l >= 0 && l < doc.size;} + // The instance object that we'll return. Mostly calls out to + // local functions in the CodeMirror function. Some do some extra + // range checking and/or clipping. operation is used to wrap the + // call so that changes it makes are tracked, and the display is + // updated afterwards. + var instance = wrapper.CodeMirror = { + getValue: getValue, + setValue: operation(setValue), + getSelection: getSelection, + replaceSelection: operation(replaceSelection), + focus: function(){focusInput(); onFocus(); fastPoll();}, + setOption: function(option, value) { + var oldVal = options[option]; + options[option] = value; + if (option == "mode" || option == "indentUnit") loadMode(); + else if (option == "readOnly" && value) {onBlur(); input.blur();} + else if (option == "theme") themeChanged(); + else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); + else if (option == "tabSize") operation(tabsChanged)(); + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") + operation(gutterChanged)(); + }, + getOption: function(option) {return options[option];}, + undo: operation(undo), + redo: operation(redo), + indentLine: operation(function(n, dir) { + if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract"); + }), + indentSelection: operation(indentSelected), + historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, + clearHistory: function() {history = new History();}, + matchBrackets: operation(function(){matchBrackets(true);}), + getTokenAt: operation(function(pos) { + pos = clipPos(pos); + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch); + }), + getStateAfter: function(line) { + line = clipLine(line == null ? doc.size - 1: line); + return getStateBefore(line + 1); + }, + cursorCoords: function(start){ + if (start == null) start = sel.inverted; + return pageCoords(start ? sel.from : sel.to); + }, + charCoords: function(pos){return pageCoords(clipPos(pos));}, + coordsChar: function(coords) { + var off = eltOffset(lineSpace); + return coordsChar(coords.x - off.left, coords.y - off.top); + }, + markText: operation(markText), + setBookmark: setBookmark, + setMarker: operation(addGutterMarker), + clearMarker: operation(removeGutterMarker), + setLineClass: operation(setLineClass), + hideLine: operation(function(h) {return setLineHidden(h, true);}), + showLine: operation(function(h) {return setLineHidden(h, false);}), + onDeleteLine: function(line, f) { + if (typeof line == "number") { + if (!isLine(line)) return null; + line = getLine(line); + } + (line.handlers || (line.handlers = [])).push(f); + return line; + }, + lineInfo: lineInfo, + addWidget: function(pos, node, scroll, vert, horiz) { + pos = localCoords(clipPos(pos)); + var top = pos.yBot, left = pos.x; + node.style.position = "absolute"; + code.appendChild(node); + if (vert == "over") top = pos.y; + else if (vert == "near") { + var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), + hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); + if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) + top = pos.y - node.offsetHeight; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = (top + paddingTop()) + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = code.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; + node.style.left = (left + paddingLeft()) + "px"; + } + if (scroll) + scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + lineCount: function() {return doc.size;}, + clipPos: clipPos, + getCursor: function(start) { + if (start == null) start = sel.inverted; + return copyPos(start ? sel.from : sel.to); + }, + somethingSelected: function() {return !posEq(sel.from, sel.to);}, + setCursor: operation(function(line, ch, user) { + if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); + else setCursor(line, ch, user); + }), + setSelection: operation(function(from, to, user) { + (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); + }), + getLine: function(line) {if (isLine(line)) return getLine(line).text;}, + getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, + setLine: operation(function(line, text) { + if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + }), + removeLine: operation(function(line) { + if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); + }), + replaceRange: operation(replaceRange), + getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, + + execCommand: function(cmd) {return commands[cmd](instance);}, + // Stuff used by commands, probably not much use to outside code. + moveH: operation(moveH), + deleteH: operation(deleteH), + moveV: operation(moveV), + toggleOverwrite: function() {overwrite = !overwrite;}, + + posFromIndex: function(off) { + var lineNo = 0, ch; + doc.iter(0, doc.size, function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos({line: lineNo, ch: ch}); + }, + indexFromPos: function (coords) { + if (coords.line < 0 || coords.ch < 0) return 0; + var index = coords.ch; + doc.iter(0, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + operation: function(f){return operation(f)();}, + refresh: function(){updateDisplay(true);}, + getInputField: function(){return input;}, + getWrapperElement: function(){return wrapper;}, + getScrollerElement: function(){return scroller;}, + getGutterElement: function(){return gutter;} + }; + + function getLine(n) { return getLineAt(doc, n); } + function updateLineHeight(line, height) { + gutterDirty = true; + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + + function setValue(code) { + var top = {line: 0, ch: 0}; + updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, + splitLines(code), top, top); + updateInput = true; + } + function getValue(code) { + var text = []; + doc.iter(0, doc.size, function(line) { text.push(line.text); }); + return text.join("\n"); + } + + function onMouseDown(e) { + setShift(e.shiftKey); + // Check whether this is a click in a widget + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == code && n != mover) return; + + // See if this is a click in the gutter + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) { + if (options.onGutterClick) + options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); + return e_preventDefault(e); + } + + var start = posFromMouse(e); + + switch (e_button(e)) { + case 3: + if (gecko && !mac) onContextMenu(e); + return; + case 2: + if (start) setCursor(start.line, start.ch, true); + return; + } + // For button 1, if it was clicked inside the editor + // (posFromMouse returning non-null), we have to adjust the + // selection. + if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} + + if (!focused) onFocus(); + + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + e_preventDefault(e); + setTimeout(focusInput, 20); + return selectLine(start.line); + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + lastDoubleClick = {time: now, pos: start}; + e_preventDefault(e); + return selectWordAt(start); + } else { lastClick = {time: now, pos: start}; } + + var last = start, going; + if (dragAndDrop && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start)) { + // Let the drag handler handle this. + if (webkit) lineSpace.draggable = true; + var up = connect(targetDocument, "mouseup", operation(function(e2) { + if (webkit) lineSpace.draggable = false; + draggingText = false; + up(); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + setCursor(start.line, start.ch, true); + focusInput(); + } + }), true); + draggingText = true; + return; + } + e_preventDefault(e); + setCursor(start.line, start.ch, true); + + function extend(e) { + var cur = posFromMouse(e, true); + if (cur && !posEq(cur, last)) { + if (!focused) onFocus(); + last = cur; + setSelectionUser(start, cur); + updateInput = false; + var visible = visibleLines(); + if (cur.line >= visible.to || cur.line < visible.from) + going = setTimeout(operation(function(){extend(e);}), 150); + } + } + + var move = connect(targetDocument, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + extend(e); + }), true); + var up = connect(targetDocument, "mouseup", operation(function(e) { + clearTimeout(going); + var cur = posFromMouse(e); + if (cur) setSelectionUser(start, cur); + e_preventDefault(e); + focusInput(); + updateInput = true; + move(); up(); + }), true); + } + function onDoubleClick(e) { + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) return e_preventDefault(e); + var start = posFromMouse(e); + if (!start) return; + lastDoubleClick = {time: +new Date, pos: start}; + e_preventDefault(e); + selectWordAt(start); + } + function onDrop(e) { + e.preventDefault(); + var pos = posFromMouse(e, true), files = e.dataTransfer.files; + if (!pos || options.readOnly) return; + if (files && files.length && window.FileReader && window.File) { + function loadFile(file, i) { + var reader = new FileReader; + reader.onload = function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(pos); + operation(function() { + var end = replaceRange(text.join(""), pos, pos); + setSelectionUser(pos, end); + })(); + } + }; + reader.readAsText(file); + } + var n = files.length, text = Array(n), read = 0; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } + else { + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + var end = replaceRange(text, pos, pos); + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, end); + if (draggingText) replaceRange("", curFrom, curTo); + focusInput(); + } + } + catch(e){} + } + } + function onDragStart(e) { + var txt = getSelection(); + // This will reset escapeElement + htmlEscape(txt); + e.dataTransfer.setDragImage(escapeElement, 0, 0); + e.dataTransfer.setData("Text", txt); + } + function handleKeyBinding(e) { + var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift; + if (name == null || e.altGraphKey) { + if (next) options.keyMap = next; + return null; + } + if (e.altKey) name = "Alt-" + name; + if (e.ctrlKey) name = "Ctrl-" + name; + if (e.metaKey) name = "Cmd-" + name; + if (e.shiftKey && (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) { + dropShift = true; + } else { + bound = lookupKey(name, options.extraKeys, options.keyMap); + } + if (typeof bound == "string") { + if (commands.propertyIsEnumerable(bound)) bound = commands[bound]; + else bound = null; + } + if (next && (bound || !isModifierKey(e))) options.keyMap = next; + if (!bound) return false; + if (dropShift) { + var prevShift = shiftSelecting; + shiftSelecting = null; + bound(instance); + shiftSelecting = prevShift; + } else bound(instance); + e_preventDefault(e); + return true; + } + var lastStoppedKey = null; + function onKeyDown(e) { + if (!focused) onFocus(); + var code = e.keyCode; + // IE does strange things with escape. + if (ie && code == 27) { e.returnValue = false; } + setShift(code == 16 || e.shiftKey); + // First give onKeyEvent option a chance to handle this. + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var handled = handleKeyBinding(e); + if (window.opera) { + lastStoppedKey = handled ? e.keyCode : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88) + replaceSelection(""); + } + } + function onKeyPress(e) { + if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (window.opera && !e.which && handleKeyBinding(e)) return; + if (options.electricChars && mode.electricChars) { + var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode); + if (mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); + } + fastPoll(); + } + function onKeyUp(e) { + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (e.keyCode == 16) shiftSelecting = null; + } + + function onFocus() { + if (options.readOnly) return; + if (!focused) { + if (options.onFocus) options.onFocus(instance); + focused = true; + if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) + wrapper.className += " CodeMirror-focused"; + if (!leaveInputAlone) resetInput(true); + } + slowPoll(); + restartBlink(); + } + function onBlur() { + if (focused) { + if (options.onBlur) options.onBlur(instance); + focused = false; + wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); + } + clearInterval(blinker); + setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); + } + + // Replace the range from from to to by the strings in newText. + // Afterwards, set the selection to selFrom, selTo. + function updateLines(from, to, newText, selFrom, selTo) { + if (history) { + var old = []; + doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); + history.addChange(from.line, newText.length, old); + while (history.done.length > options.undoDepth) history.done.shift(); + } + updateLinesNoUndo(from, to, newText, selFrom, selTo); + } + function unredoHelper(from, to) { + var change = from.pop(); + if (change) { + var replaced = [], end = change.start + change.added; + doc.iter(change.start, end, function(line) { replaced.push(line.text); }); + to.push({start: change.start, added: change.old.length, old: replaced}); + var pos = clipPos({line: change.start + change.old.length - 1, + ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); + updateInput = true; + } + } + function undo() {unredoHelper(history.done, history.undone);} + function redo() {unredoHelper(history.undone, history.done);} + + function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + var recomputeMaxLength = false, maxLineLength = maxLine.length; + if (!options.lineWrapping) + doc.iter(from.line, to.line, function(line) { + if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} + }); + if (from.line != to.line || newText.length > 1) gutterDirty = true; + + var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); + // First adjust the line structure, taking some care to leave highlighting intact. + if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = [], prevLine = null; + if (from.line) { + prevLine = getLine(from.line - 1); + prevLine.fixMarkEnds(lastLine); + } else lastLine.fixMarkStarts(); + for (var i = 0, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], prevLine)); + if (nlines) doc.remove(from.line, nlines, callbacks); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (newText.length == 1) + firstLine.replace(from.ch, to.ch, newText[0]); + else { + lastLine = firstLine.split(to.ch, newText[newText.length-1]); + firstLine.replace(from.ch, null, newText[0]); + firstLine.fixMarkEnds(lastLine); + var added = []; + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + added.push(lastLine); + doc.insert(from.line + 1, added); + } + } else if (newText.length == 1) { + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, ""); + firstLine.append(lastLine); + doc.remove(from.line + 1, nlines, callbacks); + } else { + var added = []; + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, newText[newText.length-1]); + firstLine.fixMarkEnds(lastLine); + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); + doc.insert(from.line + 1, added); + } + if (options.lineWrapping) { + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(from.line, from.line + newText.length, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + doc.iter(from.line, i + newText.length, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLine = l; maxLineLength = l.length; maxWidth = null; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { + maxLineLength = 0; maxLine = ""; maxWidth = null; + doc.iter(0, doc.size, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLineLength = l.length; maxLine = l; + } + }); + } + } + + // Add these lines to the work array, so that they will be + // highlighted. Adjust work lines if lines were added/removed. + var newWork = [], lendiff = newText.length - nlines - 1; + for (var i = 0, l = work.length; i < l; ++i) { + var task = work[i]; + if (task < from.line) newWork.push(task); + else if (task > to.line) newWork.push(task + lendiff); + } + var hlEnd = from.line + Math.min(newText.length, 500); + highlightLines(from.line, hlEnd); + newWork.push(hlEnd); + work = newWork; + startWorker(100); + // Remember that these lines changed, for updating the display + changes.push({from: from.line, to: to.line + 1, diff: lendiff}); + var changeObj = {from: from, to: to, text: newText}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; + + // Update the selection + function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} + setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); + + // Make sure the scroll-size div has the correct height. + code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; + } + + function replaceRange(code, from, to) { + from = clipPos(from); + if (!to) to = from; else to = clipPos(to); + code = splitLines(code); + function adjustPos(pos) { + if (posLess(pos, from)) return pos; + if (!posLess(to, pos)) return end; + var line = pos.line + code.length - (to.line - from.line) - 1; + var ch = pos.ch; + if (pos.line == to.line) + ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0)); + return {line: line, ch: ch}; + } + var end; + replaceRange1(code, from, to, function(end1) { + end = end1; + return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; + }); + return end; + } + function replaceSelection(code, collapse) { + replaceRange1(splitLines(code), sel.from, sel.to, function(end) { + if (collapse == "end") return {from: end, to: end}; + else if (collapse == "start") return {from: sel.from, to: sel.from}; + else return {from: sel.from, to: end}; + }); + } + function replaceRange1(code, from, to, computeSel) { + var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; + var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); + updateLines(from, to, code, newSel.from, newSel.to); + } + + function getRange(from, to) { + var l1 = from.line, l2 = to.line; + if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); + var code = [getLine(l1).text.slice(from.ch)]; + doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); + code.push(getLine(l2).text.slice(0, to.ch)); + return code.join("\n"); + } + function getSelection() { + return getRange(sel.from, sel.to); + } + + var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll + function slowPoll() { + if (pollingFast) return; + poll.set(options.pollInterval, function() { + startOperation(); + readInput(); + if (focused) slowPoll(); + endOperation(); + }); + } + function fastPoll() { + var missed = false; + pollingFast = true; + function p() { + startOperation(); + var changed = readInput(); + if (!changed && !missed) {missed = true; poll.set(60, p);} + else {pollingFast = false; slowPoll();} + endOperation(); + } + poll.set(20, p); + } + + // Previnput is a hack to work with IME. If we reset the textarea + // on every change, that breaks IME. So we look for changes + // compared to the previous content instead. (Modern browsers have + // events that indicate IME taking place, but these are not widely + // supported or compatible enough yet to rely on.) + var prevInput = ""; + function readInput() { + if (leaveInputAlone || !focused || hasSelection(input)) return false; + var text = input.value; + if (text == prevInput) return false; + shiftSelecting = null; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput[same] == text[same]) ++same; + if (same < prevInput.length) + sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; + else if (overwrite && posEq(sel.from, sel.to)) + sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; + replaceSelection(text.slice(same), "end"); + prevInput = text; + return true; + } + function resetInput(user) { + if (!posEq(sel.from, sel.to)) { + prevInput = ""; + input.value = getSelection(); + input.select(); + } else if (user) prevInput = input.value = ""; + } + + function focusInput() { + if (!options.readOnly) input.focus(); + } + + function scrollEditorIntoView() { + if (!cursor.getBoundingClientRect) return; + var rect = cursor.getBoundingClientRect(); + // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden + if (ie && rect.top == rect.bottom) return; + var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); + } + function scrollCursorIntoView() { + var cursor = localCoords(sel.inverted ? sel.from : sel.to); + var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; + return scrollIntoView(x, cursor.y, x, cursor.yBot); + } + function scrollIntoView(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(), lh = textHeight(); + y1 += pt; y2 += pt; x1 += pl; x2 += pl; + var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; + if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} + else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} + + var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; + var gutterw = options.fixedGutter ? gutter.clientWidth : 0; + if (x1 < screenleft + gutterw) { + if (x1 < 50) x1 = 0; + scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); + scrolled = true; + } + else if (x2 > screenw + screenleft - 3) { + scroller.scrollLeft = x2 + 10 - screenw; + scrolled = true; + if (x2 > code.clientWidth) result = false; + } + if (scrolled && options.onScroll) options.onScroll(instance); + return result; + } + + function visibleLines() { + var lh = textHeight(), top = scroller.scrollTop - paddingTop(); + var from_height = Math.max(0, Math.floor(top / lh)); + var to_height = Math.ceil((top + scroller.clientHeight) / lh); + return {from: lineAtHeight(doc, from_height), + to: lineAtHeight(doc, to_height)}; + } + // Uses a set of changes plus the current scroll position to + // determine which DOM updates have to be made, and makes the + // updates. + function updateDisplay(changes, suppressCallback) { + if (!scroller.clientWidth) { + showingFrom = showingTo = displayOffset = 0; + return; + } + // Compute the new visible window + var visible = visibleLines(); + // Bail out if the visible area is already rendered and nothing changed. + if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return; + var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + if (showingFrom < from && from - showingFrom < 20) from = showingFrom; + if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = changes === true ? [] : + computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); + // Clip off the parts that won't be visible + var intactLines = 0; + for (var i = 0; i < intact.length; ++i) { + var range = intact[i]; + if (range.from < from) {range.domStart += (from - range.from); range.from = from;} + if (range.to > to) range.to = to; + if (range.from >= range.to) intact.splice(i--, 1); + else intactLines += range.to - range.from; + } + if (intactLines == to - from) return; + intact.sort(function(a, b) {return a.domStart - b.domStart;}); + + var th = textHeight(), gutterDisplay = gutter.style.display; + lineDiv.style.display = gutter.style.display = "none"; + patchDisplay(from, to, intact); + lineDiv.style.display = ""; + + // Position the mover div to align with the lines it's supposed + // to be showing (which will cover the visible display) + var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) lastSizeC = scroller.clientHeight + th; + showingFrom = from; showingTo = to; + displayOffset = heightAtLine(doc, from); + mover.style.top = (displayOffset * th) + "px"; + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + + // Since this is all rather error prone, it is honoured with the + // only assertion in the whole file. + if (lineDiv.childNodes.length != showingTo - showingFrom) + throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + + " nodes=" + lineDiv.childNodes.length); + + if (options.lineWrapping) { + maxWidth = scroller.clientWidth; + var curNode = lineDiv.firstChild; + doc.iter(showingFrom, showingTo, function(line) { + if (!line.hidden) { + var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;} + } + curNode = curNode.nextSibling; + }); + } else { + if (maxWidth == null) maxWidth = stringWidth(maxLine); + if (maxWidth > scroller.clientWidth) { + lineSpace.style.width = maxWidth + "px"; + // Needed to prevent odd wrapping/hiding of widgets placed in here. + code.style.width = ""; + code.style.width = scroller.scrollWidth + "px"; + } else { + lineSpace.style.width = code.style.width = ""; + } + } + gutter.style.display = gutterDisplay; + if (different || gutterDirty) updateGutter(); + updateCursor(); + if (!suppressCallback && options.onUpdate) options.onUpdate(instance); + return true; + } + + function computeIntact(intact, changes) { + for (var i = 0, l = changes.length || 0; i < l; ++i) { + var change = changes[i], intact2 = [], diff = change.diff || 0; + for (var j = 0, l2 = intact.length; j < l2; ++j) { + var range = intact[j]; + if (change.to <= range.from && change.diff) + intact2.push({from: range.from + diff, to: range.to + diff, + domStart: range.domStart}); + else if (change.to <= range.from || change.from >= range.to) + intact2.push(range); + else { + if (change.from > range.from) + intact2.push({from: range.from, to: change.from, domStart: range.domStart}); + if (change.to < range.to) + intact2.push({from: change.to + diff, to: range.to + diff, + domStart: range.domStart + (change.to - range.from)}); + } + } + intact = intact2; + } + return intact; + } + + function patchDisplay(from, to, intact) { + // The first pass removes the DOM nodes that aren't intact. + if (!intact.length) lineDiv.innerHTML = ""; + else { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; + } + var domPos = 0, curNode = lineDiv.firstChild, n; + for (var i = 0; i < intact.length; ++i) { + var cur = intact[i]; + while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} + for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} + } + while (curNode) curNode = killNode(curNode); + } + // This pass fills in the lines that actually changed. + var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; + var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from; + var scratch = targetDocument.createElement("div"), newElt; + doc.iter(from, to, function(line) { + var ch1 = null, ch2 = null; + if (inSel) { + ch1 = 0; + if (sto == j) {inSel = false; ch2 = sel.to.ch;} + } else if (sfrom == j) { + if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;} + else {inSel = true; ch1 = sel.from.ch;} + } + if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); + if (!nextIntact || nextIntact.from > j) { + if (line.hidden) scratch.innerHTML = "
";
+          else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);
+          lineDiv.insertBefore(scratch.firstChild, curNode);
+        } else {
+          curNode = curNode.nextSibling;
+        }
+        ++j;
+      });
+    }
+
+    function updateGutter() {
+      if (!options.gutter && !options.lineNumbers) return;
+      var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
+      gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
+      var html = [], i = showingFrom;
+      doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
+        if (line.hidden) {
+          html.push("
");
+        } else {
+          var marker = line.gutterMarker;
+          var text = options.lineNumbers ? i + options.firstLineNumber : null;
+          if (marker && marker.text)
+            text = marker.text.replace("%N%", text != null ? text : "");
+          else if (text == null)
+            text = "\u00a0";
+          html.push((marker && marker.style ? '