62 changes: 36 additions & 26 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@

<h1><span class="logo-braces">{ }</span> <a href="http://codemirror.net/">CodeMirror</a></h1>

<pre class="grey">
<img src="doc/baboon.png" class="logo" alt="logo"/>/* In-browser code editing
<div class="grey">
<img src="doc/baboon.png" class="logo" alt="logo"/>
<pre>
/* In-browser code editing
made bearable */
</pre>
</div>

<div class="clear"><div class="left blk">

Expand All @@ -35,6 +38,7 @@ <h2 style="margin-top: 0">Supported modes:</h2>
<li><a href="mode/clike/index.html">C, C++, C#</a></li>
<li><a href="mode/clojure/index.html">Clojure</a></li>
<li><a href="mode/coffeescript/index.html">CoffeeScript</a></li>
<li><a href="mode/commonlisp/index.html">Common Lisp</a></li>
<li><a href="mode/css/index.html">CSS</a></li>
<li><a href="mode/diff/index.html">diff</a></li>
<li><a href="mode/ecl/index.html">ECL</a></li>
Expand Down Expand Up @@ -141,6 +145,7 @@ <h2>Real-world uses:</h2>
<li><a href="http://cssdeck.com/">CSSDeck</a> (CSS showcase)</li>
<li><a href="http://www.ckwnc.com/">CKWNC</a> (UML editor)</li>
<li><a href="http://www.sketchpatch.net/labs/livecodelabIntro.html">sketchPatch Livecodelab</a></li>
<li><a href="https://notex.ch">NoTex</a> (rST authoring)</li>
</ul>

</div></div>
Expand Down Expand Up @@ -276,10 +281,38 @@ <h2>Reading material</h2>

<h2 id=releases>Releases</h2>

<p class="rel">19-09-2012: <a href="http://codemirror.net/codemirror-2.34.zip">Version 2.34</a>:</p>

<ul class="rel-note">
<li>New mode: <a href="mode/commonlisp/index.html">Common Lisp</a>.</li>
<li>Fix right-click select-all on most browsers.</li>
<li>Change the way highlighting happens:<br>&nbsp; Saves memory and CPU cycles.<br>&nbsp; <code>compareStates</code> is no longer needed.<br>&nbsp; <code>onHighlightComplete</code> no longer works.</li>
<li>Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite.</li>
<li>Add a <a href="doc/manual.html#version"><code>CodeMirror.version</code></a> property.</li>
<li>More robust handling of nested modes in <a href="demo/formatting.html">formatting</a> and <a href="demo/closetag.html">closetag</a> plug-ins.</li>
<li>Un/redo now preserves <a href="doc/manual.html#markText">marked text</a> and bookmarks.</li>
<li><a href="https://github.com/marijnh/CodeMirror/compare/v2.33...v2.34">Full list</a> of patches.</li>
</ul>

<p class="rel">19-09-2012: <a href="http://codemirror.net/codemirror-3.0beta1.zip">Version 3.0, beta 1</a>:</p>

<p class="rel-note"><strong>BETA release, new major version</strong>. Only partially
backwards-compatible. See
the <a href="http://codemirror.net/3/doc/upgrade_v3.html">upgrading
guide</a> for more information. Major new features are:</p>

<ul class="rel-note">
<li>Bi-directional text support.</li>
<li>More powerful gutter model.</li>
<li>Support for arbitrary text/widget height.</li>
<li>In-line widgets.</li>
<li>Generalized event handling.</li>
</ul>

<p class="rel">23-08-2012: <a href="http://codemirror.net/codemirror-2.33.zip">Version 2.33</a>:</p>

<ul class="rel-note">
<li>New mode: <a href="mode/sieve/">Sieve</a>.</li>
<li>New mode: <a href="mode/sieve/index.html">Sieve</a>.</li>
<li>New <a href="doc/manual.html#getViewport"><code>getViewPort</code></a> and <a href="doc/manual.html#option_onViewportChange"><code>onViewportChange</code></a> API.</li>
<li><a href="doc/manual.html#option_cursorBlinkRate">Configurable</a> cursor blink rate.</li>
<li>Make binding a key to <code>false</code> disabling handling (again).</li>
Expand Down Expand Up @@ -424,29 +457,6 @@ <h2 id=releases>Releases</h2>
method.</li>
</ul>

<p class="rel">21-11-2011: <a href="http://codemirror.net/codemirror-2.18.zip">Version 2.18</a>:</p>
<p class="rel-note">Fixes <code>TextMarker.clear</code>, which is broken in 2.17.</p>

<p class="rel">21-11-2011: <a href="http://codemirror.net/codemirror-2.17.zip">Version 2.17</a>:</p>
<ul class="rel-note">
<li>Add support for <a href="doc/manual.html#option_lineWrapping">line
wrapping</a> and <a href="doc/manual.html#hideLine">code
folding</a>.</li>
<li>Add <a href="mode/gfm/index.html">Github-style Markdown</a> mode.</li>
<li>Add <a href="theme/monokai.css">Monokai</a>
and <a href="theme/rubyblue.css">Rubyblue</a> themes.</li>
<li>Add <a href="doc/manual.html#setBookmark"><code>setBookmark</code></a> method.</li>
<li>Move some of the demo code into reusable components
under <a href="lib/util/"><code>lib/util</code></a>.</li>
<li>Make screen-coord-finding code faster and more reliable.</li>
<li>Fix drag-and-drop in Firefox.</li>
<li>Improve support for IME.</li>
<li>Speed up content rendering.</li>
<li>Fix browser's built-in search in Webkit.</li>
<li>Make double- and triple-click work in IE.</li>
<li>Various fixes to modes.</li>
</ul>

<p><a href="doc/oldrelease.html">Older releases...</a></p>

</div></div>
Expand Down
5 changes: 4 additions & 1 deletion keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,10 @@
setupPrefixBindingForKey("Space");

CodeMirror.keyMap["vim-prefix-y"] = {
"Y": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; }),
"Y": countTimes(function(cm) {
pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++;
cm.setOption("keyMap", "vim");
}),
"'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();},
nofallthrough: true, style: "fat-cursor"
};
Expand Down
4 changes: 2 additions & 2 deletions lib/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ div.CodeMirror-selected { background: #d9d9d9; }
.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-bracket {color: #997;}
.cm-s-default span.cm-tag {color: #170;}
.cm-s-default span.cm-attribute {color: #00c;}
.cm-s-default span.cm-header {color: blue;}
Expand All @@ -170,4 +170,4 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
visibility: hidden;
}

}
}
808 changes: 357 additions & 451 deletions lib/codemirror.js

Large diffs are not rendered by default.

70 changes: 35 additions & 35 deletions lib/util/closetag.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
/** Array of tag names where an end tag is forbidden. */
CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];

function innerState(cm, state) {
return CodeMirror.innerMode(cm.getMode(), state).state;
}


/**
* Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass.
* - cm: The editor instance.
Expand All @@ -39,40 +44,34 @@
throw CodeMirror.Pass;
}

var mode = cm.getOption('mode');

if (mode == 'text/html' || mode == 'xml') {
/*
* Relevant structure of token:
*
* htmlmixed
* className
* state
* htmlState
* type
* tagName
* context
* tagName
* mode
*
* xml
* className
* state
* tagName
* type
*/

/*
* Relevant structure of token:
*
* htmlmixed
* className
* state
* htmlState
* type
* tagName
* context
* tagName
* mode
*
* xml
* className
* state
* tagName
* type
*/

var pos = cm.getCursor();
var tok = cm.getTokenAt(pos);
var state = tok.state;

if (state.mode && state.mode != 'html') {
throw CodeMirror.Pass; // With htmlmixed, we only care about the html sub-mode.
}
var pos = cm.getCursor();
var tok = cm.getTokenAt(pos);
var state = innerState(cm, tok.state);

if (state) {

if (ch == '>') {
var type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
var type = state.type;

if (tok.className == 'tag' && type == 'closeTag') {
throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag.
Expand All @@ -83,11 +82,12 @@
cm.setCursor(pos);

tok = cm.getTokenAt(cm.getCursor());
state = tok.state;
type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
state = innerState(cm, tok.state);
if (!state) throw CodeMirror.Pass;
var type = state.type;

if (tok.className == 'tag' && type != 'selfcloseTag') {
var tagName = state.htmlState ? state.htmlState.tagName : state.tagName; // htmlmixed : xml
var tagName = state.tagName;
if (tagName.length > 0 && shouldClose(cm, vd, tagName)) {
insertEndTag(cm, indent, pos, tagName);
}
Expand All @@ -100,7 +100,7 @@

} else if (ch == '/') {
if (tok.className == 'tag' && tok.string == '<') {
var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : (state.context ? state.context.tagName : ''); // htmlmixed : xml
var ctx = state.context, tagName = ctx ? ctx.tagName : '';
if (tagName.length > 0) {
completeEndTag(cm, pos, tagName);
return;
Expand Down
400 changes: 147 additions & 253 deletions lib/util/formatting.js

Large diffs are not rendered by default.

12 changes: 4 additions & 8 deletions lib/util/multiplex.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,10 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
},

compareStates: function(a, b) {
if (a.innerActive != b.innerActive) return false;
var mode = a.innerActive || outer;
if (!mode.compareStates) return CodeMirror.Pass;
return mode.compareStates(a.innerActive ? a.inner : a.outer,
b.innerActive ? b.inner : b.outer);
},
electricChars: outer.electricChars,

electricChars: outer.electricChars
innerMode: function(state) {
return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer};
}
};
};
4 changes: 3 additions & 1 deletion lib/util/overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, comb
indent: base.indent && function(state, textAfter) {
return base.indent(state.base, textAfter);
},
electricChars: base.electricChars
electricChars: base.electricChars,

innerMode: function(state) { return {state: state.base, mode: base}; }
};
};
90 changes: 90 additions & 0 deletions lib/util/runmode-standalone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* Just enough of CodeMirror to run runMode under node.js */

function splitLines(string){ return string.split(/\r?\n|\r/); };

function StringStream(string) {
this.pos = this.start = 0;
this.string = string;
}
StringStream.prototype = {
eol: function() {return this.pos >= this.string.length;},
sol: function() {return this.pos == 0;},
peek: function() {return this.string.charAt(this.pos) || null;},
next: function() {
if (this.pos < this.string.length)
return this.string.charAt(this.pos++);
},
eat: function(match) {
var ch = this.string.charAt(this.pos);
if (typeof match == "string") var ok = ch == match;
else var ok = ch && (match.test ? match.test(ch) : match(ch));
if (ok) {++this.pos; return ch;}
},
eatWhile: function(match) {
var start = this.pos;
while (this.eat(match)){}
return this.pos > start;
},
eatSpace: function() {
var start = this.pos;
while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
return this.pos > start;
},
skipToEnd: function() {this.pos = this.string.length;},
skipTo: function(ch) {
var found = this.string.indexOf(ch, this.pos);
if (found > -1) {this.pos = found; return true;}
},
backUp: function(n) {this.pos -= n;},
column: function() {return this.start;},
indentation: function() {return 0;},
match: function(pattern, consume, caseInsensitive) {
if (typeof pattern == "string") {
function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
if (consume !== false) this.pos += pattern.length;
return true;
}
}
else {
var match = this.string.slice(this.pos).match(pattern);
if (match && consume !== false) this.pos += match[0].length;
return match;
}
},
current: function(){return this.string.slice(this.start, this.pos);}
};
exports.StringStream = StringStream;

exports.startState = function(mode, a1, a2) {
return mode.startState ? mode.startState(a1, a2) : true;
};

var modes = exports.modes = {}, mimeModes = exports.mimeModes = {};
exports.defineMode = function(name, mode) { modes[name] = mode; };
exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; };
exports.getMode = function(options, spec) {
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
spec = mimeModes[spec];
if (typeof spec == "string")
var mname = spec, config = {};
else if (spec != null)
var mname = spec.name, config = spec;
var mfactory = modes[mname];
if (!mfactory) throw new Error("Unknown mode: " + spec);
return mfactory(options, config || {});
};

exports.runMode = function(string, modespec, callback) {
var mode = exports.getMode({indentUnit: 2}, modespec);
var lines = splitLines(string), state = exports.startState(mode);
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new exports.StringStream(lines[i]);
while (!stream.eol()) {
var style = mode.token(stream, state);
callback(stream.current(), style, i, stream.start);
stream.start = stream.pos;
}
}
};
2 changes: 1 addition & 1 deletion lib/util/simple-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
if (code == 13) {CodeMirror.e_stop(event); pick();}
// Escape
else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();}
else if (code != 38 && code != 40) {
else if (code != 38 && code != 40 && code != 33 && code != 34) {
close(); editor.focus();
// Pass the event to the CodeMirror instance so that it can handle things like backspace properly.
editor.triggerOnKeyDown(event);
Expand Down
15 changes: 7 additions & 8 deletions mode/clojure/clojure.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ CodeMirror.defineMode("clojure", function (config, mode) {
var tests = {
digit: /\d/,
digit_or_colon: /[\d:]/,
hex: /[0-9a-fA-F]/,
hex: /[0-9a-f]/i,
sign: /[+-]/,
exponent: /[eE]/,
exponent: /e/i,
keyword_char: /[^\s\(\[\;\)\]]/,
basic: /[\w\$_\-]/,
lang_keyword: /[\w*+!\-_?:\/]/
Expand All @@ -64,14 +64,13 @@ CodeMirror.defineMode("clojure", function (config, mode) {

function isNumber(ch, stream){
// hex
if ( ch === '0' && 'x' == stream.peek().toLowerCase() ) {
stream.eat('x');
if ( ch === '0' && stream.eat(/x/i) ) {
stream.eatWhile(tests.hex);
return true;
}

// leading sign
if ( ch == '+' || ch == '-' ) {
if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
stream.eat(tests.sign);
ch = stream.next();
}
Expand All @@ -85,8 +84,7 @@ CodeMirror.defineMode("clojure", function (config, mode) {
stream.eatWhile(tests.digit);
}

if ( 'e' == stream.peek().toLowerCase() ) {
stream.eat(tests.exponent);
if ( stream.eat(tests.exponent) ) {
stream.eat(tests.sign);
stream.eatWhile(tests.digit);
}
Expand Down Expand Up @@ -157,7 +155,8 @@ CodeMirror.defineMode("clojure", function (config, mode) {
keyWord += letter;
}

if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word
if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
/^(?:def|with)/.test(keyWord))) { // indent-word
pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
} else { // non-indent word
// we continue eating the spaces
Expand Down
101 changes: 101 additions & 0 deletions mode/commonlisp/commonlisp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
CodeMirror.defineMode("commonlisp", function (config) {
var assumeBody = /^with|^def|^do|^prog|case$|^cond$|bind$|when$|unless$/;
var numLiteral = /^(?:[+\-]?(?:\d+|\d*\.\d+)(?:[efd][+\-]?\d+)?|[+\-]?\d+(?:\/[+\-]?\d+)?|#b[+\-]?[01]+|#o[+\-]?[0-7]+|#x[+\-]?[\da-f]+)/;
var symbol = /[^\s'`,@()\[\]";]/;
var type;

function readSym(stream) {
var ch;
while (ch = stream.next()) {
if (ch == "\\") stream.next();
else if (!symbol.test(ch)) { stream.backUp(1); break; }
}
return stream.current();
}

function base(stream, state) {
if (stream.eatSpace()) {type = "ws"; return null;}
if (stream.match(numLiteral)) return "number";
var ch = stream.next();
if (ch == "\\") ch = stream.next();

if (ch == '"') return (state.tokenize = inString)(stream, state);
else if (ch == "(") { type = "open"; return "bracket"; }
else if (ch == ")" || ch == "]") { type = "close"; return "bracket"; }
else if (ch == ";") { stream.skipToEnd(); type = "ws"; return "comment"; }
else if (/['`,@]/.test(ch)) return null;
else if (ch == "|") {
if (stream.skipTo("|")) { stream.next(); return "symbol"; }
else { stream.skipToEnd(); return "error"; }
} else if (ch == "#") {
var ch = stream.next();
if (ch == "[") { type = "open"; return "bracket"; }
else if (/[+\-=\.']/.test(ch)) return null;
else if (/\d/.test(ch) && stream.match(/^\d*#/)) return null;
else if (ch == "|") return (state.tokenize = inComment)(stream, state);
else if (ch == ":") { readSym(stream); return "meta"; }
else return "error";
} else {
var name = readSym(stream);
if (name == ".") return null;
type = "symbol";
if (name == "nil" || name == "t") return "atom";
if (name.charAt(0) == ":") return "keyword";
if (name.charAt(0) == "&") return "variable-2";
return "variable";
}
}

function inString(stream, state) {
var escaped = false, next;
while (next = stream.next()) {
if (next == '"' && !escaped) { state.tokenize = base; break; }
escaped = !escaped && next == "\\";
}
return "string";
}

function inComment(stream, state) {
var next, last;
while (next = stream.next()) {
if (next == "#" && last == "|") { state.tokenize = base; break; }
last = next;
}
type = "ws";
return "comment";
}

return {
startState: function () {
return {ctx: {prev: null, start: 0, indentTo: 0}, tokenize: base};
},

token: function (stream, state) {
if (stream.sol() && typeof state.ctx.indentTo != "number")
state.ctx.indentTo = state.ctx.start + 1;

type = null;
var style = state.tokenize(stream, state);
if (type != "ws") {
if (state.ctx.indentTo == null) {
if (type == "symbol" && assumeBody.test(stream.current()))
state.ctx.indentTo = state.ctx.start + config.indentUnit;
else
state.ctx.indentTo = "next";
} else if (state.ctx.indentTo == "next") {
state.ctx.indentTo = stream.column();
}
}
if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null};
else if (type == "close") state.ctx = state.ctx.prev || state.ctx;
return style;
},

indent: function (state, textAfter) {
var i = state.ctx.indentTo;
return typeof i == "number" ? i : state.ctx.start + 1;
}
};
});

CodeMirror.defineMIME("text/x-common-lisp", "commonlisp");
165 changes: 165 additions & 0 deletions mode/commonlisp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Common Lisp mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="commonlisp.js"></script>
<style>.CodeMirror {background: #f8f8f8;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Common Lisp mode</h1>
<form><textarea id="code" name="code">(in-package :cl-postgres)

;; These are used to synthesize reader and writer names for integer
;; reading/writing functions when the amount of bytes and the
;; signedness is known. Both the macro that creates the functions and
;; some macros that use them create names this way.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun integer-reader-name (bytes signed)
(intern (with-standard-io-syntax
(format nil "~a~a~a~a" '#:read- (if signed "" '#:u) '#:int bytes))))
(defun integer-writer-name (bytes signed)
(intern (with-standard-io-syntax
(format nil "~a~a~a~a" '#:write- (if signed "" '#:u) '#:int bytes)))))

(defmacro integer-reader (bytes)
"Create a function to read integers from a binary stream."
(let ((bits (* bytes 8)))
(labels ((return-form (signed)
(if signed
`(if (logbitp ,(1- bits) result)
(dpb result (byte ,(1- bits) 0) -1)
result)
`result))
(generate-reader (signed)
`(defun ,(integer-reader-name bytes signed) (socket)
(declare (type stream socket)
#.*optimize*)
,(if (= bytes 1)
`(let ((result (the (unsigned-byte 8) (read-byte socket))))
(declare (type (unsigned-byte 8) result))
,(return-form signed))
`(let ((result 0))
(declare (type (unsigned-byte ,bits) result))
,@(loop :for byte :from (1- bytes) :downto 0
:collect `(setf (ldb (byte 8 ,(* 8 byte)) result)
(the (unsigned-byte 8) (read-byte socket))))
,(return-form signed))))))
`(progn
;; This causes weird errors on SBCL in some circumstances. Disabled for now.
;; (declaim (inline ,(integer-reader-name bytes t)
;; ,(integer-reader-name bytes nil)))
(declaim (ftype (function (t) (signed-byte ,bits))
,(integer-reader-name bytes t)))
,(generate-reader t)
(declaim (ftype (function (t) (unsigned-byte ,bits))
,(integer-reader-name bytes nil)))
,(generate-reader nil)))))

(defmacro integer-writer (bytes)
"Create a function to write integers to a binary stream."
(let ((bits (* 8 bytes)))
`(progn
(declaim (inline ,(integer-writer-name bytes t)
,(integer-writer-name bytes nil)))
(defun ,(integer-writer-name bytes nil) (socket value)
(declare (type stream socket)
(type (unsigned-byte ,bits) value)
#.*optimize*)
,@(if (= bytes 1)
`((write-byte value socket))
(loop :for byte :from (1- bytes) :downto 0
:collect `(write-byte (ldb (byte 8 ,(* byte 8)) value)
socket)))
(values))
(defun ,(integer-writer-name bytes t) (socket value)
(declare (type stream socket)
(type (signed-byte ,bits) value)
#.*optimize*)
,@(if (= bytes 1)
`((write-byte (ldb (byte 8 0) value) socket))
(loop :for byte :from (1- bytes) :downto 0
:collect `(write-byte (ldb (byte 8 ,(* byte 8)) value)
socket)))
(values)))))

;; All the instances of the above that we need.

(integer-reader 1)
(integer-reader 2)
(integer-reader 4)
(integer-reader 8)

(integer-writer 1)
(integer-writer 2)
(integer-writer 4)

(defun write-bytes (socket bytes)
"Write a byte-array to a stream."
(declare (type stream socket)
(type (simple-array (unsigned-byte 8)) bytes)
#.*optimize*)
(write-sequence bytes socket))

(defun write-str (socket string)
"Write a null-terminated string to a stream \(encoding it when UTF-8
support is enabled.)."
(declare (type stream socket)
(type string string)
#.*optimize*)
(enc-write-string string socket)
(write-uint1 socket 0))

(declaim (ftype (function (t unsigned-byte)
(simple-array (unsigned-byte 8) (*)))
read-bytes))
(defun read-bytes (socket length)
"Read a byte array of the given length from a stream."
(declare (type stream socket)
(type fixnum length)
#.*optimize*)
(let ((result (make-array length :element-type '(unsigned-byte 8))))
(read-sequence result socket)
result))

(declaim (ftype (function (t) string) read-str))
(defun read-str (socket)
"Read a null-terminated string from a stream. Takes care of encoding
when UTF-8 support is enabled."
(declare (type stream socket)
#.*optimize*)
(enc-read-string socket :null-terminated t))

(defun skip-bytes (socket length)
"Skip a given number of bytes in a binary stream."
(declare (type stream socket)
(type (unsigned-byte 32) length)
#.*optimize*)
(dotimes (i length)
(read-byte socket)))

(defun skip-str (socket)
"Skip a null-terminated string."
(declare (type stream socket)
#.*optimize*)
(loop :for char :of-type fixnum = (read-byte socket)
:until (zerop char)))

(defun ensure-socket-is-closed (socket &amp;key abort)
(when (open-stream-p socket)
(handler-case
(close socket :abort abort)
(error (error)
(warn "Ignoring the error which happened while trying to close PostgreSQL socket: ~A" error)))))
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {lineNumbers: true});
</script>

<p><strong>MIME types defined:</strong> <code>text/x-common-lisp</code>.</p>

</body>
</html>
398 changes: 336 additions & 62 deletions mode/css/css.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions mode/css/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,7 @@ <h1>CodeMirror: CSS mode</h1>

<p><strong>MIME types defined:</strong> <code>text/css</code>.</p>

<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#css_*">normal</a>, <a href="../../test/index.html#verbose,css_*">verbose</a>.</p>

</body>
</html>
501 changes: 501 additions & 0 deletions mode/css/test.js

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion mode/gfm/gfm.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ CodeMirror.defineMode("gfm", function(config, parserConfig) {
},

copyState: function(state) {
return {token: state.token, mode: state.mode, mdState: CodeMirror.copyState(mdMode, state.mdState),
return {token: state.token, mdState: CodeMirror.copyState(mdMode, state.mdState),
localMode: state.localMode,
localState: state.localMode ? CodeMirror.copyState(state.localMode, state.localState) : null};
},
Expand Down Expand Up @@ -140,6 +140,11 @@ CodeMirror.defineMode("gfm", function(config, parserConfig) {
}

return state.token(stream, state);
},

innerMode: function(state) {
if (state.token == markdown) return {state: state.mdState, mode: mdMode};
else return {state: state.localState, mode: state.localMode};
}
};
}, "markdown");
3 changes: 0 additions & 3 deletions mode/haxe/haxe.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,6 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
compareStates: function(state1, state2) {
return (state1.localVars == state2.localVars) && (state1.context == state2.context);
},

electricChars: "{}"
};
Expand Down
6 changes: 5 additions & 1 deletion mode/htmlembedded/htmlembedded.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@ CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
};
},

electricChars: "/{}:",

electricChars: "/{}:"
innerMode: function(state) {
if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode};
else return {state: state.htmlState, mode: htmlMixedMode};
}
};
}, "htmlmixed");

Expand Down
23 changes: 11 additions & 12 deletions mode/htmlmixed/htmlmixed.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
CodeMirror.defineMode("htmlmixed", function(config) {
var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
var jsMode = CodeMirror.getMode(config, "javascript");
var cssMode = CodeMirror.getMode(config, "css");
Expand All @@ -9,27 +9,28 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
if (/^script$/i.test(state.htmlState.context.tagName)) {
state.token = javascript;
state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
state.mode = "javascript";
}
else if (/^style$/i.test(state.htmlState.context.tagName)) {
state.token = css;
state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
state.mode = "css";
}
}
return style;
}
function maybeBackup(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat);
var close = cur.search(pat), m;
if (close > -1) stream.backUp(cur.length - close);
else if (m = cur.match(/<\/?$/)) {
stream.backUp(cur[0].length);
if (!stream.match(pat, false)) stream.match(cur[0]);
}
return style;
}
function javascript(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
state.localState = null;
state.mode = "html";
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*script\s*>/,
Expand All @@ -39,7 +40,6 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
if (stream.match(/^<\/\s*style\s*>/i, false)) {
state.token = html;
state.localState = null;
state.mode = "html";
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*style\s*>/,
Expand Down Expand Up @@ -72,13 +72,12 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
return cssMode.indent(state.localState, textAfter);
},

compareStates: function(a, b) {
if (a.mode != b.mode) return false;
if (a.localState) return CodeMirror.Pass;
return htmlMode.compareStates(a.htmlState, b.htmlState);
},
electricChars: "/{}:",

electricChars: "/{}:"
innerMode: function(state) {
var mode = state.token == html ? htmlMode : state.token == javascript ? jsMode : cssMode;
return {state: state.localState || state.htmlState, mode: mode};
}
};
}, "xml", "javascript", "css");

Expand Down
2 changes: 1 addition & 1 deletion mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {

var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
if (!cx.state.context) cx.state.localVars = defaultVars;
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
Expand Down
2 changes: 2 additions & 0 deletions mode/markdown/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -337,5 +337,7 @@ <h1>CodeMirror: Markdown mode</h1>

<p><strong>MIME types defined:</strong> <code>text/x-markdown</code>.</p>

<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#markdown_*">normal</a>, <a href="../../test/index.html#verbose,markdown_*">verbose</a>.</p>

</body>
</html>
148 changes: 131 additions & 17 deletions mode/markdown/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html");
var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain");

var codeDepth = 0;
var prevLineHasContent = false
, thisLineHasContent = false;

var header = 'header'
, code = 'comment'
, quote = 'quote'
, list = 'string'
, hr = 'hr'
, linkinline = 'link'
, linkemail = 'link'
, linktext = 'link'
, linkhref = 'string'
, em = 'em'
Expand All @@ -17,8 +23,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/
, ulRE = /^[*\-+]\s+/
, olRE = /^[0-9]+\.\s+/
, headerRE = /^(?:\={3,}|-{3,})$/
, textRE = /^[^\[*_\\<>`]+/;
, headerRE = /^(?:\={1,}|-{1,})$/
, textRE = /^[^\[*_\\<>` "'(]+/;

function switchInline(stream, state, f) {
state.f = state.inline = f;
Expand All @@ -34,6 +40,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
// Blocks

function blankLine(state) {
// Reset linkTitle state
state.linkTitle = false;
// Reset CODE state
state.code = false;
// Reset EM state
state.em = false;
// Reset STRONG state
Expand All @@ -49,13 +59,23 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

function blockNormal(stream, state) {
var match;

if (state.list !== false && state.indentationDiff >= 0) { // Continued list
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
state.indentation -= state.indentationDiff;
}
state.list = null;
} else { // No longer a list
state.list = false;
}

if (state.indentationDiff >= 4) {
state.indentation -= state.indentationDiff;
state.indentation -= 4;
stream.skipToEnd();
return code;
} else if (stream.eatSpace()) {
return null;
} else if (stream.peek() === '#' || stream.match(headerRE)) {
} else if (stream.peek() === '#' || (prevLineHasContent && stream.match(headerRE)) ) {
state.header = true;
} else if (stream.eat('>')) {
state.indentation++;
Expand All @@ -65,8 +85,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} else if (stream.match(hrRE, true)) {
return hr;
} else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) {
state.indentation += match[0].length;
return list;
state.indentation += 4;
state.list = true;
}

return switchInline(stream, state, state.inline);
Expand Down Expand Up @@ -94,8 +114,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (state.strong) { styles.push(state.em ? emstrong : strong); }
else if (state.em) { styles.push(em); }

if (state.code) { styles.push(code); }

if (state.header) { styles.push(header); }
if (state.quote) { styles.push(quote); }
if (state.list !== false) { styles.push(list); }

return styles.length ? styles.join(' ') : null;
}
Expand All @@ -112,18 +135,64 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (typeof style !== 'undefined')
return style;

if (state.list) { // List marker (*, +, -, 1., etc)
state.list = null;
return list;
}

var ch = stream.next();

if (ch === '\\') {
stream.next();
return getType(state);
}

// Matches link titles present on next line
if (state.linkTitle) {
state.linkTitle = false;
var matchCh = ch;
if (ch === '(') {
matchCh = ')';
}
matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
if (stream.match(new RegExp(regex), true)) {
return linkhref;
}
}

if (ch === '`') {
return switchInline(stream, state, inlineElement(code, '`'));
var t = getType(state);
var before = stream.pos;
stream.eatWhile('`');
var difference = 1 + stream.pos - before;
if (!state.code) {
codeDepth = difference;
state.code = true;
return getType(state);
} else {
if (difference === codeDepth) { // Must be exact
state.code = false;
return t;
}
return getType(state);
}
} else if (state.code) {
return getType(state);
}
if (ch === '[' && stream.match(/.*\](?:\(|\[)/, false)) {

if (ch === '[' && stream.match(/.*\] ?(?:\(|\[)/, false)) {
return switchInline(stream, state, linkText);
}

if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) {
return switchInline(stream, state, inlineElement(linkinline, '>'));
}

if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) {
return switchInline(stream, state, inlineElement(linkemail, '>'));
}

if (ch === '<' && stream.match(/^\w/, false)) {
var md_inside = false;
if (stream.string.indexOf(">")!=-1) {
Expand All @@ -143,10 +212,27 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

var t = getType(state);
if (ch === '*' || ch === '_') {
if (stream.eat(ch)) {
return (state.strong = !state.strong) ? getType(state) : t;
if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
state.strong = false;
return t;
} else if (!state.strong && stream.eat(ch)) { // Add STRONG
state.strong = ch;
return getType(state);
} else if (state.em === ch) { // Remove EM
state.em = false;
return t;
} else if (!state.em) { // Add EM
state.em = ch;
return getType(state);
}
} else if (ch === ' ') {
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state);
} else { // Not surrounded by spaces, back up pointer
stream.backUp(1);
}
}
return (state.em = !state.em) ? getType(state) : t;
}

return getType(state);
Expand All @@ -165,7 +251,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
}

function linkHref(stream, state) {
stream.eatSpace();
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
var ch = stream.next();
if (ch === '(' || ch === '[') {
return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
Expand All @@ -182,17 +271,29 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
}

function footnoteUrl(stream, state) {
stream.eatSpace();
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
// Match URL
stream.match(/^[^\s]+/, true);
// Check for link title
if (stream.peek() === undefined) { // End of line, set flag to check next line
state.linkTitle = true;
} else { // More content on line, check if link title
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
}
state.f = state.inline = inlineNormal;
return linkhref;
}

function inlineRE(endChar) {
if (!inlineRE[endChar]) {
// match any not-escaped-non-endChar and any escaped char
// then match endChar or eol
inlineRE[endChar] = new RegExp('^(?:[^\\\\\\' + endChar + ']|\\\\.)*(?:\\' + endChar + '|$)');
// Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)
endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
// Match any non-endChar, escaped character, as well as the closing
// endChar.
inlineRE[endChar] = new RegExp('^(?:[^\\\\]+?|\\\\.)*?(' + endChar + ')');
}
return inlineRE[endChar];
}
Expand All @@ -217,9 +318,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

inline: inlineNormal,
text: handleText,
linkTitle: false,
em: false,
strong: false,
header: false,
list: false,
quote: false
};
},
Expand All @@ -234,17 +337,28 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

inline: s.inline,
text: s.text,
linkTitle: s.linkTitle,
em: s.em,
strong: s.strong,
header: s.header,
list: s.list,
quote: s.quote,
md_inside: s.md_inside
};
},

token: function(stream, state) {
if (stream.sol()) {
if (stream.match(/^\s*$/, true)) { return blankLine(state); }
if (stream.match(/^\s*$/, true)) {
prevLineHasContent = false;
return blankLine(state);
} else {
if(thisLineHasContent){
prevLineHasContent = true;
thisLineHasContent = false;
}
thisLineHasContent = true;
}

// Reset state.header
state.header = false;
Expand Down
1,084 changes: 1,084 additions & 0 deletions mode/markdown/test.js

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions mode/php/php.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,13 @@
var phpMode = CodeMirror.getMode(config, phpConfig);

function dispatch(stream, state) { // TODO open PHP inside text/css
var isPHP = state.mode == "php";
var isPHP = state.curMode == phpMode;
if (stream.sol() && state.pending != '"') state.pending = null;
if (state.curMode == htmlMode) {
if (stream.match(/^<\?\w*/)) {
state.curMode = phpMode;
state.curState = state.php;
state.curClose = "?>";
state.mode = "php";
return "meta";
}
if (state.pending == '"') {
Expand All @@ -86,13 +85,11 @@
state.curMode = jsMode;
state.curState = jsMode.startState(htmlMode.indent(state.curState, ""));
state.curClose = /^<\/\s*script\s*>/i;
state.mode = "javascript";
}
else if (/^style$/i.test(state.curState.context.tagName)) {
state.curMode = cssMode;
state.curState = cssMode.startState(htmlMode.indent(state.curState, ""));
state.curClose = /^<\/\s*style\s*>/i;
state.mode = "css";
}
}
return style;
Expand All @@ -101,7 +98,6 @@
state.curMode = htmlMode;
state.curState = state.html;
state.curClose = null;
state.mode = "html";
if (isPHP) return "meta";
else return dispatch(stream, state);
} else {
Expand Down Expand Up @@ -141,7 +137,9 @@
return state.curMode.indent(state.curState, textAfter);
},

electricChars: "/{}:"
electricChars: "/{}:",

innerMode: function(state) { return {state: state.curState, mode: state.curMode}; }
};
}, "xml", "clike", "javascript", "css");
CodeMirror.defineMIME("application/x-httpd-php", "php");
Expand Down
2 changes: 1 addition & 1 deletion mode/shell/shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ CodeMirror.defineMode('shell', function(config) {
stream.eatWhile(/\w/);
var cur = stream.current();
if (stream.peek() === '=' && /\w+/.test(cur)) return 'def';
return words[cur] || null;
return words.hasOwnProperty(cur) ? words[cur] : null;
}

function tokenString(quote) {
Expand Down
2 changes: 2 additions & 0 deletions mode/stex/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,7 @@ <h1>CodeMirror: sTeX mode</h1>

<p><strong>MIME types defined:</strong> <code>text/x-stex</code>.</p>

<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#stex_*">normal</a>, <a href="../../test/index.html#verbose,stex_*">verbose</a>.</p>

</body>
</html>
264 changes: 0 additions & 264 deletions mode/stex/test.html

This file was deleted.

343 changes: 343 additions & 0 deletions mode/stex/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
var MT = ModeTest;
MT.modeName = 'stex';
MT.modeOptions = {};

MT.testMode(
'word',
'foo',
[
null, 'foo'
]
);

MT.testMode(
'twoWords',
'foo bar',
[
null, 'foo bar'
]
);

MT.testMode(
'beginEndDocument',
'\\begin{document}\n\\end{document}',
[
'tag', '\\begin',
'bracket', '{',
'atom', 'document',
'bracket', '}',
'tag', '\\end',
'bracket', '{',
'atom', 'document',
'bracket', '}'
]
);

MT.testMode(
'beginEndEquation',
'\\begin{equation}\n E=mc^2\n\\end{equation}',
[
'tag', '\\begin',
'bracket', '{',
'atom', 'equation',
'bracket', '}',
null, ' E=mc^2',
'tag', '\\end',
'bracket', '{',
'atom', 'equation',
'bracket', '}'
]
);

MT.testMode(
'beginModule',
'\\begin{module}[]',
[
'tag', '\\begin',
'bracket', '{',
'atom', 'module',
'bracket', '}[]'
]
);

MT.testMode(
'beginModuleId',
'\\begin{module}[id=bbt-size]',
[
'tag', '\\begin',
'bracket', '{',
'atom', 'module',
'bracket', '}[',
null, 'id=bbt-size',
'bracket', ']'
]
);

MT.testMode(
'importModule',
'\\importmodule[b-b-t]{b-b-t}',
[
'tag', '\\importmodule',
'bracket', '[',
'string', 'b-b-t',
'bracket', ']{',
'builtin', 'b-b-t',
'bracket', '}'
]
);

MT.testMode(
'importModulePath',
'\\importmodule[\\KWARCslides{dmath/en/cardinality}]{card}',
[
'tag', '\\importmodule',
'bracket', '[',
'tag', '\\KWARCslides',
'bracket', '{',
'string', 'dmath/en/cardinality',
'bracket', '}]{',
'builtin', 'card',
'bracket', '}'
]
);

MT.testMode(
'psForPDF',
'\\PSforPDF[1]{#1}', // could treat #1 specially
[
'tag', '\\PSforPDF',
'bracket', '[',
'atom', '1',
'bracket', ']{',
null, '#1',
'bracket', '}'
]
);

MT.testMode(
'comment',
'% foo',
[
'comment', '% foo'
]
);

MT.testMode(
'tagComment',
'\\item% bar',
[
'tag', '\\item',
'comment', '% bar'
]
);

MT.testMode(
'commentTag',
' % \\item',
[
null, ' ',
'comment', '% \\item'
]
);

MT.testMode(
'commentLineBreak',
'%\nfoo',
[
'comment', '%',
null, 'foo'
]
);

MT.testMode(
'tagErrorCurly',
'\\begin}{',
[
'tag', '\\begin',
'error', '}',
'bracket', '{'
]
);

MT.testMode(
'tagErrorSquare',
'\\item]{',
[
'tag', '\\item',
'error', ']',
'bracket', '{'
]
);

MT.testMode(
'commentCurly',
'% }',
[
'comment', '% }'
]
);

MT.testMode(
'tagHash',
'the \\# key',
[
null, 'the ',
'tag', '\\#',
null, ' key'
]
);

MT.testMode(
'tagNumber',
'a \\$5 stetson',
[
null, 'a ',
'tag', '\\$',
'atom', 5,
null, ' stetson'
]
);

MT.testMode(
'tagPercent',
'100\\% beef',
[
'atom', '100',
'tag', '\\%',
null, ' beef'
]
);

MT.testMode(
'tagAmpersand',
'L \\& N',
[
null, 'L ',
'tag', '\\&',
null, ' N'
]
);

MT.testMode(
'tagUnderscore',
'foo\\_bar',
[
null, 'foo',
'tag', '\\_',
null, 'bar'
]
);

MT.testMode(
'tagBracketOpen',
'\\emph{\\{}',
[
'tag', '\\emph',
'bracket', '{',
'tag', '\\{',
'bracket', '}'
]
);

MT.testMode(
'tagBracketClose',
'\\emph{\\}}',
[
'tag', '\\emph',
'bracket', '{',
'tag', '\\}',
'bracket', '}'
]
);

MT.testMode(
'tagLetterNumber',
'section \\S1',
[
null, 'section ',
'tag', '\\S',
'atom', '1'
]
);

MT.testMode(
'textTagNumber',
'para \\P2',
[
null, 'para ',
'tag', '\\P',
'atom', '2'
]
);

MT.testMode(
'thinspace',
'x\\,y', // thinspace
[
null, 'x',
'tag', '\\,',
null, 'y'
]
);

MT.testMode(
'thickspace',
'x\\;y', // thickspace
[
null, 'x',
'tag', '\\;',
null, 'y'
]
);

MT.testMode(
'negativeThinspace',
'x\\!y', // negative thinspace
[
null, 'x',
'tag', '\\!',
null, 'y'
]
);

MT.testMode(
'periodNotSentence',
'J.\\ L.\\ is', // period not ending a sentence
[
null, 'J.\\ L.\\ is'
]
); // maybe could be better

MT.testMode(
'periodSentence',
'X\\@. The', // period ending a sentence
[
null, 'X',
'tag', '\\@',
null, '. The'
]
);

MT.testMode(
'italicCorrection',
'{\\em If\\/} I', // italic correction
[
'bracket', '{',
'tag', '\\em',
null, ' If',
'tag', '\\/',
'bracket', '}',
null, ' I'
]
);

MT.testMode(
'tagBracket',
'\\newcommand{\\pop}',
[
'tag', '\\newcommand',
'bracket', '{',
'tag', '\\pop',
'bracket', '}'
]
);
7 changes: 0 additions & 7 deletions mode/tiki/tiki.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,6 @@ CodeMirror.defineMode('tiki', function(config, parserConfig) {
if (context) return context.indent + indentUnit;
else return 0;
},
compareStates: function(a, b) {
if (a.indented != b.indented || a.pluginName != b.pluginName) return false;
for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
if (!ca || !cb) return ca == cb;
if (ca.pluginName != cb.pluginName) return false;
}
},
electricChars: "/"
};
});
Expand Down
4 changes: 2 additions & 2 deletions mode/vb/vb.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ CodeMirror.defineMode("vb", function(conf, parserConf) {
var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");

var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property'];
var middleKeywords = ['else','elseif','case'];
var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try'];
var middleKeywords = ['else','elseif','case', 'catch'];
var endKeywords = ['next','loop'];

var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'in']);
Expand Down
2 changes: 1 addition & 1 deletion mode/xml/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="xml.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<style type="text/css">.foo{border-right: 1px solid red} .CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
Expand Down
8 changes: 0 additions & 8 deletions mode/xml/xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,6 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
else return 0;
},

compareStates: function(a, b) {
if (a.indented != b.indented || a.tokenize != b.tokenize) return false;
for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
if (!ca || !cb) return ca == cb;
if (ca.tagName != cb.tagName || ca.indent != cb.indent) return false;
}
},

electricChars: "/"
};
});
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "CodeMirror",
"version":"2.33.0",
"name": "codemirror",
"version":"2.34.0",
"main": "codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
Expand Down
102 changes: 89 additions & 13 deletions test/driver.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,111 @@
var tests = [], debug = null;
var tests = [], debug = null, debugUsed = new Array(), allNames = [];

function Failure(why) {this.message = why;}

function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)
if (collection[i] == elt) return i;
return -1;
}

function test(name, run, expectedFail) {
// Force unique names
var originalName = name;
var i = 2; // Second function would be NAME_2
while (indexOf(allNames, name) !== -1){
name = originalName + "_" + i;
i++;
}
allNames.push(name);
// Add test
tests.push({name: name, func: run, expectedFail: expectedFail});
return name;
}
function testCM(name, run, opts, expectedFail) {
return test(name, function() {
return test("core_" + name, function() {
var place = document.getElementById("testground"), cm = CodeMirror(place, opts);
if (debug) place.style.visibility = "";
try {run(cm);}
finally {if (!debug) place.removeChild(cm.getWrapperElement());}
var successful = false;
try {
run(cm);
successful = true;
} finally {
if ((debug && !successful) || verbose) {
place.style.visibility = "";
} else {
place.removeChild(cm.getWrapperElement());
}
}
}, expectedFail);
}

function runTests(callback) {
if (debug) {
if (indexOf(debug, "verbose") === 0) {
verbose = true;
debug.splice(0, 1);
}
if (debug.length < 1) {
debug = null;
} else {
if (totalTests > debug.length) {
totalTests = debug.length;
}
}
}
var totalTime = 0;
function step(i) {
if (i == tests.length) return callback("done");
var test = tests[i], expFail = test.expectedFail;
if (debug != null && debug != test.name) return step(i + 1);
if (i === tests.length){
running = false;
return callback("done");
}
var test = tests[i], expFail = test.expectedFail, startTime = +new Date;
if (debug !== null) {
var debugIndex = indexOf(debug, test.name);
if (debugIndex !== -1) {
// Remove from array for reporting incorrect tests later
debug.splice(debugIndex, 1);
} else {
var wildcardName = test.name.split("_").shift() + "_*";
debugIndex = indexOf(debug, wildcardName);
if (debugIndex !== -1) {
// Remove from array for reporting incorrect tests later
debug.splice(debugIndex, 1);
debugUsed.push(wildcardName);
} else {
debugIndex = indexOf(debugUsed, wildcardName);
if (debugIndex !== -1) {
totalTests++;
} else {
return step(i + 1);
}
}
}
}
try {
test.func();
if (expFail) callback("fail", test.name, "was expected to fail, but succeeded");
else callback("ok", test.name);
var message = test.func();
if (expFail) callback("fail", test.name, message);
else callback("ok", test.name, message);
} catch(e) {
if (expFail) callback("expected", test.name);
else if (e instanceof Failure) callback("fail", test.name, e.message);
else callback("error", test.name, e.toString());
else {
var pos = /\bat .*?([^\/:]+):(\d+):/.exec(e.stack);
callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : ""));
}
}
if (!quit) { // Run next test
var delay = 0;
totalTime += (+new Date) - startTime;
if (totalTime > 500){
totalTime = 0;
delay = 50;
}
setTimeout(function(){step(i + 1);}, delay);
} else { // Quit tests
running = false;
return null;
}
setTimeout(function(){step(i + 1);}, 20);
}
step(0);
}
Expand Down
140 changes: 123 additions & 17 deletions test/index.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Test Suite</title>
<link rel="stylesheet" href="../lib/codemirror.css">
<link rel="stylesheet" href="../doc/docs.css">
<link rel="stylesheet" href="mode_test.css">
<script src="../lib/codemirror.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/xml/xml.js"></script>
Expand All @@ -11,6 +14,14 @@
.ok {color: #090;}
.fail {color: #e00;}
.error {color: #c90;}
.done {font-weight: bold;}
#progress {
background: #45d;
color: white;
text-shadow: 0 0 1px #45d, 0 0 2px #45d, 0 0 3px #45d;
font-weight: bold;
white-space: pre;
}
</style>
</head>
<body>
Expand All @@ -19,42 +30,137 @@ <h1>CodeMirror: Test Suite</h1>
<p>A limited set of programmatic sanity tests for CodeMirror.</p>

<div style="border: 1px solid black; padding: 1px; max-width: 700px;">
<div style="background: #45d; white-space: pre; width: 0px; font-weight: bold; color: white; padding: 3px;" id=progress></div>
<div style="width: 0px;" id=progress><div style="padding: 3px;">Ran <span id="progress_ran">0</span><span id="progress_total"> of 0</span> tests</div></div>
</div>
<pre style="padding-left: 5px" id=output></pre>
<p id=status>Please enable JavaScript...</p>
<div id=output></div>

<div style="visibility: hidden" id=testground>
<form><textarea id="code" name="code"></textarea><input type=submit value=ok name=submit></form>
</div>
<div style="visibility: hidden" id=testground></div>

<script src="driver.js"></script>
<script src="test.js"></script>
<script src="mode_test.js"></script>
<script src="../mode/css/css.js"></script>
<script src="../mode/css/test.js"></script>
<script src="../mode/markdown/markdown.js"></script>
<script src="../mode/markdown/test.js"></script>
<script src="../mode/stex/stex.js"></script>
<script src="../mode/stex/test.js"></script>
<script>
window.onload = function() {
runTests(displayTest);
runHarness();
};
CodeMirror.connect(window, 'hashchange', function(){
runHarness();
});

function esc(str) {
return str.replace(/[<&]/, function(ch) { return ch == "<" ? "&lt;" : "&amp;"; });
}

var output = document.getElementById("output"), progress = document.getElementById("progress");
var count = 0, failed = 0, bad = "";
function displayTest(type, name, msg) {
var output = document.getElementById("output"),
progress = document.getElementById("progress"),
progressRan = document.getElementById("progress_ran").childNodes[0],
progressTotal = document.getElementById("progress_total").childNodes[0];
var count = 0,
failed = 0,
bad = "",
running = false, // Flag that states tests are running
quit = false, // Flag to quit tests ASAP
verbose = false; // Adds message for *every* test to output

function runHarness(){
if (running) {
quit = true;
setStatus("Restarting tests...", '', true);
setTimeout(function(){runHarness();}, 500);
return;
}
if (window.location.hash.substr(1)){
debug = window.location.hash.substr(1).split(",");
} else {
debug = null;
}
quit = false;
running = true;
setStatus("Loading tests...");
count = 0;
failed = 0;
bad = "";
verbose = false;
debugUsed = Array();
totalTests = tests.length;
progressTotal.nodeValue = " of " + totalTests;
progressRan.nodeValue = count;
output.innerHTML = '';
document.getElementById("testground").innerHTML = "<form>" +
"<textarea id=\"code\" name=\"code\"></textarea>" +
"<input type=submit value=ok name=submit>" +
"</form>";
runTests(displayTest);
}

function setStatus(message, className, force){
if (quit && !force) return;
if (!message) throw("must provide message");
var status = document.getElementById("status").childNodes[0];
status.nodeValue = message;
status.parentNode.className = className;
}
function addOutput(name, className, code){
var newOutput = document.createElement("dl");
var newTitle = document.createElement("dt");
newTitle.className = className;
newTitle.appendChild(document.createTextNode(name));
newOutput.appendChild(newTitle);
var newMessage = document.createElement("dd");
newMessage.innerHTML = code;
newOutput.appendChild(newTitle);
newOutput.appendChild(newMessage);
output.appendChild(newOutput);
}
function displayTest(type, name, customMessage) {
var message = "???";
if (type != "done") ++count;
progress.style.width = (count * (progress.parentNode.clientWidth - 8) / tests.length) + "px";
progress.innerHTML = "Ran " + count + (count < tests.length ? " of " + tests.length : "") + " tests";
progress.style.width = (count * (progress.parentNode.clientWidth - 2) / totalTests) + "px";
progressTotal.nodeValue = " of " + totalTests +
(debugUsed.length && type != "done" ? "+" : "");
progressRan.nodeValue = count;
if (type == "ok") {
output.innerHTML = bad + "<span class=ok>Test '" + esc(name) + "' succeeded</span>";
message = "Test '" + name + "' succeeded";
if (!verbose) customMessage = false;
} else if (type == "expected") {
output.innerHTML = bad + "<span class=ok>Test '" + esc(name) + "' failed as expected</span>";
message = "Test '" + name + "' failed as expected";
if (!verbose) customMessage = false;
} else if (type == "error" || type == "fail") {
++failed;
bad += esc(name) + ": <span class=" + type + ">" + esc(msg) + "</span><br>";
output.innerHTML = bad;
message = "Test '" + name + "' failed";
} else if (type == "done") {
output.innerHTML = bad + (failed ? "<span class=fail>" + failed + " failure" + (failed > 1 ? "s" : "") + "</span>"
: "<span class=ok>All passed</span>");
if (failed) {
type += " fail";
message = failed + " failure" + (failed > 1 ? "s" : "");
} else if (count < totalTests) {
failed = totalTests - count;
type += " fail";
message = failed + " failure" + (failed > 1 ? "s" : "");
} else {
type += " ok";
message = "All passed";
}
if (debug && debug.length) {
var bogusTests = totalTests - count;
message += " — " + bogusTests + " nonexistent test" +
(bogusTests > 1 ? "s" : "") + " requested by location.hash: " +
"`" + debug.join("`, `") + "`";
} else {
progressTotal.nodeValue = '';
}
customMessage = true; // Hack to avoid adding to output
}
if (verbose && !customMessage) customMessage = message;
setStatus(message, type);
if (customMessage.length > 0) {
addOutput(name, type, customMessage);
}
}
</script>
Expand Down
12 changes: 0 additions & 12 deletions test/mode_test.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,3 @@
.mt-output .mt-style {
font-size: x-small;
}

.mt-test {
border-left: 10px solid #fff;
}

.mt-pass {
border-left: 10px solid #cfc;
}

.mt-fail {
border-left: 10px solid #fcc;
}
93 changes: 66 additions & 27 deletions test/mode_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,49 @@ ModeTest.modeOptions = {};
ModeTest.modeName = CodeMirror.defaults.mode;

/* keep track of results for printSummary */
ModeTest.tests = 0;
ModeTest.testCount = 0;
ModeTest.passes = 0;

/**
* Run a test; prettyprints the results using document.write().
*
* @param string to highlight
*
* @param style[i] expected style of the i'th token in string
*
* @param token[i] expected value for the i'th token in string
*
* @param name Name of test
* @param text String to highlight.
* @param expected Expected styles and tokens: Array(style, token, [style, token,...])
* @param modeName
* @param modeOptions
* @param expectedFail
*/
ModeTest.test = function() {
ModeTest.tests += 1;
ModeTest.testMode = function(name, text, expected, modeName, modeOptions, expectedFail) {
ModeTest.testCount += 1;

if (!modeName) modeName = ModeTest.modeName;

if (!modeOptions) modeOptions = ModeTest.modeOptions;

var mode = CodeMirror.getMode(ModeTest.modeOptions, ModeTest.modeName);
var mode = CodeMirror.getMode(modeOptions, modeName);

if (arguments.length < 1) {
throw "must have text for test";
if (expected.length < 0) {
throw "must have text for test (" + name + ")";
}
if (arguments.length % 2 != 1) {
throw "must have text for test plus expected (style, token) pairs";
if (expected.length % 2 != 0) {
throw "must have text for test (" + name + ") plus expected (style, token) pairs";
}
return test(
modeName + "_" + name,
function(){
return ModeTest.compare(text, expected, mode);
},
expectedFail
);

}

ModeTest.compare = function (text, arguments, mode) {

var text = arguments[0];
var expectedOutput = [];
for (var i = 1; i < arguments.length; i += 2) {
for (var i = 0; i < arguments.length; i += 2) {
arguments[i] = (arguments[i] != null ? arguments[i].split(' ').sort().join(' ') : arguments[i]);
expectedOutput.push([arguments[i],arguments[i + 1]]);
}

Expand All @@ -50,20 +66,26 @@ ModeTest.test = function() {
}

var s = '';
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + ModeTest.htmlEscape(text) + '</pre>';
s += '<div class="cm-s-default">';
if (pass || expectedOutput.length == 0) {
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + ModeTest.htmlEscape(text) + '</pre>';
s += '<div class="cm-s-default">';
s += ModeTest.prettyPrintOutputTable(observedOutput);
s += '</div>';
s += '</div>';
return s;
} else {
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + ModeTest.htmlEscape(text) + '</pre>';
s += '<div class="cm-s-default">';
s += 'expected:';
s += ModeTest.prettyPrintOutputTable(expectedOutput);
s += 'observed:';
s += ModeTest.prettyPrintOutputTable(observedOutput);
s += '</div>';
s += '</div>';
throw s;
}
s += '</div>';
s += '</div>';
document.write(s);
}

/**
Expand All @@ -85,11 +107,27 @@ ModeTest.highlight = function(string, mode) {
var line = lines[i];
var stream = new CodeMirror.StringStream(line);
if (line == "" && mode.blankLine) mode.blankLine(state);
var pos = 0;
var st = [];
/* Start copied code from CodeMirror.highlight */
while (!stream.eol()) {
var style = mode.token(stream, state);
var substr = line.slice(stream.start, stream.pos);
output.push([style, substr]);
var style = mode.token(stream, state), substr = stream.current();
stream.start = stream.pos;
if (pos && st[pos-1] == style) {
st[pos-2] += substr;
} else if (substr) {
st[pos++] = substr; st[pos++] = style;
}
// Give up when line is ridiculously long
if (stream.pos > 5000) {
st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
break;
}
}
/* End copied code from CodeMirror.highlight */
for (var x = 0; x < st.length; x += 2) {
st[x + 1] = (st[x + 1] != null ? st[x + 1].split(' ').sort().join(' ') : st[x + 1]);
output.push([st[x + 1], st[x]]);
}
}

Expand Down Expand Up @@ -131,7 +169,7 @@ ModeTest.prettyPrintOutputTable = function(output) {
var token = output[i];
s +=
'<td class="mt-token">' +
'<span class="cm-' + token[0] + '">' +
'<span class="cm-' + String(token[0]).replace(/ +/g, " cm-") + '">' +
ModeTest.htmlEscape(token[1]).replace(/ /g,'&middot;') +
'</span>' +
'</td>';
Expand All @@ -150,7 +188,8 @@ ModeTest.prettyPrintOutputTable = function(output) {
* Print how many tests have run so far and how many of those passed.
*/
ModeTest.printSummary = function() {
document.write(ModeTest.passes + ' passes for ' + ModeTest.tests + ' tests');
ModeTest.runTests(ModeTest.displayTest);
document.write(ModeTest.passes + ' passes for ' + ModeTest.testCount + ' tests');
}

/**
Expand Down
8 changes: 4 additions & 4 deletions test/phantom_driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ page.open("http://localhost:3000/test/index.html", function (status) {
}
waitFor(function () {
return page.evaluate(function () {
var output = document.getElementById('output');
var output = document.getElementById('status');
if (!output) { return false; }
return (/(\d+ failures?|all passed)$/i).test(output.innerText);
return (/^(\d+ failures?|all passed)/i).test(output.innerText);
});
}, function () {
var failed = page.evaluate(function () { return window.failed; });
var output = page.evaluate(function () {
return document.getElementById('output').innerText;
return document.getElementById('status').innerText;
});
console.log(output);
phantom.exit(failed > 0 ? 1 : 0);
Expand All @@ -27,4 +27,4 @@ function waitFor (test, cb) {
} else {
setTimeout(function () { waitFor(test, cb); }, 250);
}
}
}
30 changes: 24 additions & 6 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function byClassName(elt, cls) {

var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);

test("fromTextArea", function() {
test("core_fromTextArea", function() {
var te = document.getElementById("code");
te.value = "CONTENT";
var cm = CodeMirror.fromTextArea(te);
Expand Down Expand Up @@ -108,7 +108,7 @@ testCM("indent", function(cm) {
eq(cm.getLine(1), "\t\t blah();");
}, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8});

test("defaults", function() {
test("core_defaults", function() {
var olddefaults = CodeMirror.defaults, defs = CodeMirror.defaults = {};
for (var opt in olddefaults) defs[opt] = olddefaults[opt];
defs.indentUnit = 5;
Expand Down Expand Up @@ -257,13 +257,14 @@ testCM("markTextSingleLine", function(cm) {
var r = cm.markText({line: 0, ch: 3}, {line: 0, ch: 6}, "foo");
cm.replaceRange(test.c, {line: 0, ch: test.a}, {line: 0, ch: test.b});
var f = r.find();
eq(f.from && f.from.ch, test.f); eq(f.to && f.to.ch, test.t);
eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t);
});
});

testCM("markTextMultiLine", function(cm) {
function p(v) { return v && {line: v[0], ch: v[1]}; }
forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]},
{a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]},
{a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]},
{a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]},
{a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]},
Expand All @@ -275,16 +276,33 @@ testCM("markTextMultiLine", function(cm) {
{a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]},
{a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]},
{a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]},
{a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 4]},
{a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]},
{a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) {
cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n");
var r = cm.markText({line: 0, ch: 5}, {line: 2, ch: 5}, "foo");
var r = cm.markText({line: 0, ch: 5}, {line: 2, ch: 5}, "CodeMirror-matchingbracket");
cm.replaceRange(test.c, p(test.a), p(test.b));
var f = r.find();
eqPos(f.from, p(test.f)); eqPos(f.to, p(test.t));
eqPos(f && f.from, p(test.f)); eqPos(f && f.to, p(test.t));
});
});

testCM("markTextUndo", function(cm) {
var marker1 = cm.markText({line: 0, ch: 1}, {line: 0, ch: 3}, "CodeMirror-matchingbracket");
var marker2 = cm.markText({line: 0, ch: 0}, {line: 2, ch: 1}, "CodeMirror-matchingbracket");
var bookmark = cm.setBookmark({line: 1, ch: 5});
cm.replaceRange("foo", {line: 0, ch: 2});
cm.replaceRange("bar\baz\bug\n", {line: 2, ch: 0}, {line: 3, ch: 0});
cm.setValue("");
eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null);
cm.undo();
eqPos(bookmark.find(), {line: 1, ch: 5});
cm.undo(); cm.undo();
var m1Pos = marker1.find(), m2Pos = marker2.find();
eqPos(m1Pos.from, {line: 0, ch: 1}); eqPos(m1Pos.to, {line: 0, ch: 3});
eqPos(m2Pos.from, {line: 0, ch: 0}); eqPos(m2Pos.to, {line: 2, ch: 1});
eqPos(bookmark.find(), {line: 1, ch: 5});
}, {value: "1234\n56789\n00\n"});

testCM("markClearBetween", function(cm) {
cm.setValue("aaa\nbbb\nccc\nddd\n");
cm.markText({line: 0, ch: 0}, {line: 2}, "foo");
Expand Down