54 changes: 42 additions & 12 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="alternate" href="http://twitter.com/statuses/user_timeline/242283288.rss" type="application/rss+xml"/>
</head>
<body>
<body style="margin-top: 9em">

<div style="background: #eee; border-bottom: 2px solid #df0019; position: absolute; left: 0; top: 0; right: 0; padding: 18px 0;">
<div style="max-width: 64.3em; margin: 0 auto;">
<a href="http://www.pledgie.com/campaigns/17784">
<img style="vertical-align: bottom" alt='Click here to lend your support to: Fund CodeMirror development and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/17784.png?skin_name=chrome' border='0' />
</a> &nbsp; <span style="font-size: 1.23em; font-weight: bold;">Please check out our development fundraiser.
</div>
</div>

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

Expand Down Expand Up @@ -42,6 +50,7 @@ <h2 style="margin-top: 0">Supported modes:</h2>
<li><a href="mode/go/index.html">Go</a></li>
<li><a href="mode/groovy/index.html">Groovy</a></li>
<li><a href="mode/haskell/index.html">Haskell</a></li>
<li><a href="mode/haxe/index.html">Haxe</a></li>
<li><a href="mode/htmlembedded/index.html">HTML embedded scripts</a></li>
<li><a href="mode/htmlmixed/index.html">HTML mixed-mode</a></li>
<li><a href="mode/clike/index.html">Java</a></li>
Expand All @@ -52,6 +61,7 @@ <h2 style="margin-top: 0">Supported modes:</h2>
<li><a href="mode/markdown/index.html">Markdown</a> (<a href="mode/gfm/index.html">Github-flavour</a>)</li>
<li><a href="mode/mysql/index.html">MySQL</a></li>
<li><a href="mode/ntriples/index.html">NTriples</a></li>
<li><a href="mode/ocaml/index.html">OCaml</a></li>
<li><a href="mode/pascal/index.html">Pascal</a></li>
<li><a href="mode/perl/index.html">Perl</a></li>
<li><a href="mode/php/index.html">PHP</a></li>
Expand All @@ -73,6 +83,7 @@ <h2 style="margin-top: 0">Supported modes:</h2>
<li><a href="mode/stex/index.html">sTeX, LaTeX</a></li>
<li><a href="mode/tiddlywiki/index.html">Tiddlywiki</a></li>
<li><a href="mode/tiki/index.html">Tiki wiki</a></li>
<li><a href="mode/vb/index.html">VB.NET</a></li>
<li><a href="mode/vbscript/index.html">VBScript</a></li>
<li><a href="mode/velocity/index.html">Velocity</a></li>
<li><a href="mode/verilog/index.html">Verilog</a></li>
Expand All @@ -86,10 +97,11 @@ <h2 style="margin-top: 0">Supported modes:</h2>
<h2 style="margin-top: 0">Usage demos:</h2>

<ul>
<li><a href="demo/complete.html">Autocompletion</a></li>
<li><a href="demo/mustache.html">Mode overlays</a></li>
<li><a href="demo/complete.html">Autocompletion</a> (<a href="demo/xmlcomplete.html">XML</a>)</li>
<li><a href="demo/search.html">Search/replace</a></li>
<li><a href="demo/folding.html">Code folding</a></li>
<li><a href="demo/mustache.html">Mode overlays</a></li>
<li><a href="demo/multiplex.html">Mode multiplexer</a></li>
<li><a href="demo/preview.html">HTML editor with preview</a></li>
<li><a href="demo/resize.html">Auto-resizing editor</a></li>
<li><a href="demo/marker.html">Setting breakpoints</a></li>
Expand All @@ -111,25 +123,29 @@ <h2>Real-world uses:</h2>

<ul>
<li><a href="http://jsbin.com">jsbin.com</a> (JS playground)</li>
<li><a href="http://buzzard.ups.edu/">Sage demo</a> (math system)</li>
<li><a href="http://www.sourcelair.com/">sourceLair</a> (online IDE)</li>
<li><a href="http://eloquentjavascript.net/chapter1.html">Eloquent JavaScript</a> (book)</a></li>
<li><a href="http://www.chris-granger.com/2012/04/12/light-table---a-new-ide-concept/">Light Table</a> (experimental IDE)</li>
<li><a href="http://brackets.io">Adobe Brackets</a> (code editor)</li>
<li><a href="http://www.mergely.com/">Mergely</a> (interactive diffing)</li>
<li><a href="https://script.google.com/">Google Apps Script</a></li>
<li><a href="http://eloquentjavascript.net/chapter1.html">Eloquent JavaScript</a> (book)</li>
<li><a href="http://media.chikuyonok.ru/codemirror2/">Zen Coding</a> (fast XML editing)</li>
<li><a href="http://paperjs.org/">Paper.js</a> (graphics scripting)</li>
<li><a href="http://tour.golang.org">Go language tour</a></li>
<li><a href="http://enjalot.com/tributary/2636296/sinwaves.js">Tributary</a> (augmented editing)</li>
<li><a href="http://prose.io/">Prose.io</a> (github content editor)</li>
<li><a href="http://www.wescheme.org/">WeScheme</a> (learning tool)</li>
<li><a href="http://webglplayground.net/">WebGL playground</a></li>
<li><a href="http://ql.io/">ql.io</a> (http API query helper)</li>
<li><a href="http://elm-lang.org/Examples.elm">Elm language examples</a></li>
<li><a href="https://thefiletree.com">The File Tree</a> (collab editor)</li>
<li><a href="http://bluegriffon.org/">BlueGriffon</a> (HTML editor)</li>
<li><a href="http://www.jshint.com/">JSHint</a> (JS linter)</li>
<li><a href="http://kl1p.com/cmtest/1">kl1p</a> (paste service)</li>
<li><a href="http://sqlfiddle.com">SQLFiddle</a> (SQL playground)</li>
<li><a href="http://tour.golang.org">Go language tour</a></li>
<li><a href="http://try.haxe.org">Try Haxe</a> (Haxe Playground) </li>
<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://thefiletree.com">The File Tree</a> (collab editor)</li>
<li><a href="http://enjalot.com/tributary/2636296/sinwaves.js">Tributary</a> (augmented editing)</li>
</ul>

</div></div>
Expand Down Expand Up @@ -187,8 +203,11 @@ <h2 id="supported">Supported browsers</h2>
<li>Firefox 2 or higher</li>
<li>Chrome, any version</li>
<li>Safari 3 or higher</li>
<li>Internet Explorer 7 or higher in standards (<strong>non-quirks</strong>) mode</li>
<li>Opera 9 or higher (with some key-handling problems on OS X)</li>
<li>Internet Explorer 7 or higher in standards mode<br>
<em>(So not quirks mode. But quasi-standards mode with a
transitional doctype is also flaky. <code>&lt;!doctype
html></code> is recommended.)</em></li>
</ul>

<p>I am not actively testing against every new browser release, and
Expand Down Expand Up @@ -248,7 +267,19 @@ <h2>Reading material</h2>
<li><a href="http://github.com/marijnh/CodeMirror2">Browse the code</a></li>
</ul>

<h2>Releases</h2>
<h2 id=releases>Releases</h2>

<p class="rel">20-07-2012: <a href="http://codemirror.net/codemirror-2.31.zip">Version 2.31</a>:</p>

<ul class="rel-note">
<li>New modes: <a href="mode/ocaml/index.html">OCaml</a>, <a href="mode/haxe/index.html">Haxe</a>, and <a href="mode/vb/index.html">VB.NET</a>.</li>
<li>Several fixes to the new scrolling model.</li>
<li>Add a <a href="doc/manual.html#setSize"><code>setSize</code></a> method for programmatic resizing.</li>
<li>Add <a href="doc/manual.html#getHistory"><code>getHistory</code></a> and <a href="doc/manual.html#setHistory"><code>setHistory</code></a> methods.</li>
<li>Allow custom line separator string in <a href="doc/manual.html#getValue"><code>getValue</code></a> and <a href="doc/manual.html#getRange"><code>getRange</code></a>.</li>
<li>Support double- and triple-click drag, double-clicking whitespace.</li>
<li>And more... <a href="https://github.com/marijnh/CodeMirror2/compare/v2.3...v2.31">(all patches)</a></li>
</ul>

<p class="rel">22-06-2012: <a href="http://codemirror.net/codemirror-2.3.zip">Version 2.3</a>:</p>

Expand Down Expand Up @@ -414,4 +445,3 @@ <h2>Releases</h2>

</body>
</html>

19 changes: 19 additions & 0 deletions keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@
cm.setOption("keyMap", "vim-insert");
}

function dialog(cm, text, shortText, f) {
if (cm.openDialog) cm.openDialog(text, f);
else f(prompt(shortText, ""));
}
function showAlert(cm, text) {
if (cm.openDialog) cm.openDialog(CodeMirror.htmlEscape(text) + " <button type=button>OK</button>");
else alert(text);
}

// main keymap
var map = CodeMirror.keyMap.vim = {
// Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces?
Expand Down Expand Up @@ -294,6 +303,16 @@
popCount();
CodeMirror.commands.goLineStart(cm);
},
"':'": function(cm) {
var exModeDialog = ': <input type="text" style="width: 90%"/>';
dialog(cm, exModeDialog, ':', function(command) {
if (command.match(/^\d+$/)) {
cm.setCursor(command - 1, cm.getCursor().ch);
} else {
showAlert(cm, "Bad command: " + command);
}
});
},
nofallthrough: true, style: "fat-cursor"
};

Expand Down
1 change: 1 addition & 0 deletions lib/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
text-align: right;
padding: .4em .2em .4em .4em;
white-space: pre !important;
cursor: default;
}
.CodeMirror-lines {
padding: .4em;
Expand Down
227 changes: 136 additions & 91 deletions lib/codemirror.js

Large diffs are not rendered by default.

30 changes: 24 additions & 6 deletions lib/util/closetag.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,25 @@
/** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */
CodeMirror.defaults['closeTagIndent'] = ['applet', 'blockquote', 'body', 'button', 'div', 'dl', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'iframe', 'layer', 'legend', 'object', 'ol', 'p', 'select', 'table', 'ul'];

/** 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'];

/**
* Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass.
* - cm: The editor instance.
* - ch: The character being processed.
* - indent: Optional. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option.
* - indent: Optional. An array of tag names to indent when closing. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option.
* Pass false to disable indentation. Pass an array to override the default list of tag names.
* - vd: Optional. An array of tag names that should not be closed. Omit to use the default void (end tag forbidden) tag list defined in the 'closeTagVoid' option. Ignored in xml mode.
*/
CodeMirror.defineExtension("closeTag", function(cm, ch, indent) {
CodeMirror.defineExtension("closeTag", function(cm, ch, indent, vd) {
if (!cm.getOption('closeTagEnabled')) {
throw CodeMirror.Pass;
}

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

if (mode == 'text/html') {
if (mode == 'text/html' || mode == 'xml') {

/*
* Relevant structure of token:
Expand All @@ -47,6 +51,7 @@
* state
* htmlState
* type
* tagName
* context
* tagName
* mode
Expand Down Expand Up @@ -82,8 +87,8 @@
type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml

if (tok.className == 'tag' && type != 'selfcloseTag') {
var tagName = state.htmlState ? state.htmlState.context.tagName : state.tagName; // htmlmixed : xml
if (tagName.length > 0) {
var tagName = state.htmlState ? state.htmlState.tagName : state.tagName; // htmlmixed : xml
if (tagName.length > 0 && shouldClose(cm, vd, tagName)) {
insertEndTag(cm, indent, pos, tagName);
}
return;
Expand All @@ -95,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.tagName; // htmlmixed : xml # extra htmlmized check is for '</' edge case
var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : (state.context ? state.context.tagName : ''); // htmlmixed : xml
if (tagName.length > 0) {
completeEndTag(cm, pos, tagName);
return;
Expand Down Expand Up @@ -130,6 +135,19 @@
return indexOf(indent, tagName.toLowerCase()) != -1;
}

function shouldClose(cm, vd, tagName) {
if (cm.getOption('mode') == 'xml') {
return true; // always close xml tags
}
if (typeof vd == 'undefined' || vd == null) {
vd = cm.getOption('closeTagVoid');
}
if (!vd) {
vd = [];
}
return indexOf(vd, tagName.toLowerCase()) == -1;
}

// C&P from codemirror.js...would be nice if this were visible to utilities.
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
Expand Down
4 changes: 4 additions & 0 deletions lib/util/dialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@
color: inherit;
font-family: monospace;
}

.CodeMirror-dialog button {
font-size: 70%;
}
6 changes: 5 additions & 1 deletion lib/util/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
closed = true;
dialog.parentNode.removeChild(dialog);
}
var inp = dialog.getElementsByTagName("input")[0];
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
CodeMirror.connect(inp, "keydown", function(e) {
if (e.keyCode == 13 || e.keyCode == 27) {
Expand All @@ -29,6 +29,10 @@
});
inp.focus();
CodeMirror.connect(inp, "blur", close);
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.connect(button, "click", close);
button.focus();
CodeMirror.connect(button, "blur", close);
}
return close;
});
Expand Down
2 changes: 2 additions & 0 deletions lib/util/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ CodeMirror.modeExtensions["javascript"] = {
getNonBreakableBlocks: function (text) {
var nonBreakableRegexes = [
new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"),
new RegExp("\\\\\"([\\s\\S]*?)(\\\\\"|$)"),
new RegExp("\\\\\'([\\s\\S]*?)(\\\\\'|$)"),
new RegExp("'([\\s\\S]*?)('|$)"),
new RegExp("\"([\\s\\S]*?)(\"|$)"),
new RegExp("//.*([\r\n]|$)")
Expand Down
35 changes: 22 additions & 13 deletions lib/util/multiplex.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
var others = Array.prototype.slice.call(arguments, 1);
var n_others = others.length;

function indexOf(string, pattern, from) {
if (typeof pattern == "string") return string.indexOf(pattern, from);
var m = pattern.exec(from ? string.slice(from) : string);
return m ? m.index + from : -1;
}

return {
startState: function() {
return {
Expand All @@ -22,39 +28,42 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {

token: function(stream, state) {
if (!state.innerActive) {
var cutOff = Infinity, oldContent = stream.string;
for (var i = 0; i < n_others; ++i) {
var other = others[i];
if (stream.match(other.open)) {
var found = indexOf(oldContent, other.open, stream.pos);
if (found == stream.pos) {
stream.match(other.open);
state.innerActive = other;
state.inner = CodeMirror.startState(other.mode);
state.inner = CodeMirror.startState(other.mode, outer.indent(state.outer, ""));
return other.delimStyle;
} else if (found != -1 && found < cutOff) {
cutOff = found;
}
}
if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
var outerToken = outer.token(stream, state.outer);
var cur = stream.current();
for (var i = 0; i < n_others; ++i) {
var other = others[i], found = cur.indexOf(other.open);
if (found > -1) {
stream.backUp(cur.length - found);
cur = cur.slice(0, found);
}
}
if (cutOff != Infinity) stream.string = oldContent;
return outerToken;
} else {
var curInner = state.innerActive;
if (stream.match(curInner.close)) {
var curInner = state.innerActive, oldContent = stream.string;
var found = indexOf(oldContent, curInner.close, stream.pos);
if (found == stream.pos) {
stream.match(curInner.close);
state.innerActive = state.inner = null;
return curInner.delimStyle;
}
if (found > -1) stream.string = oldContent.slice(0, found);
var innerToken = curInner.mode.token(stream, state.inner);
if (found > -1) stream.string = oldContent;
var cur = stream.current(), found = cur.indexOf(curInner.close);
if (found > -1) stream.backUp(cur.length - found);
return innerToken;
}
},

indent: function(state, textAfter) {
var mode = state.innerActive || outer;
var mode = state.innerActive ? state.innerActive.mode : outer;
if (!mode.indent) return CodeMirror.Pass;
return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
},
Expand Down
2 changes: 1 addition & 1 deletion lib/util/pig-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
if (!context) var context = [];
context.push(tprop);

completionList = getCompletions(token, context);
var completionList = getCompletions(token, context);
completionList = completionList.sort();
//prevent autocomplete for last word, instead show dropdown with one word
if(completionList.length == 1) {
Expand Down
4 changes: 4 additions & 0 deletions lib/util/simple-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
CodeMirror.simpleHint = function(editor, getHints) {
// We want a single cursor position.
if (editor.somethingSelected()) return;
//don't show completion if the token is empty
var tempToken = editor.getTokenAt(editor.getCursor());
if(!(/[\S]/gi.test(tempToken.string))) return;

var result = getHints(editor);
if (!result || !result.list.length) return;
var completions = result.list;
Expand Down
137 changes: 137 additions & 0 deletions lib/util/xml-hint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@

(function() {

CodeMirror.xmlHints = [];

CodeMirror.xmlHint = function(cm, simbol) {

if(simbol.length > 0) {
var cursor = cm.getCursor();
cm.replaceSelection(simbol);
cursor = {line: cursor.line, ch: cursor.ch + 1};
cm.setCursor(cursor);
}

// dirty hack for simple-hint to receive getHint event on space
var getTokenAt = editor.getTokenAt;

editor.getTokenAt = function() { return 'disabled'; }
CodeMirror.simpleHint(cm, getHint);

editor.getTokenAt = getTokenAt;
};

var getHint = function(cm) {

var cursor = cm.getCursor();

if (cursor.ch > 0) {

var text = cm.getRange({line: 0, ch: 0}, cursor);
var typed = '';
var simbol = '';
for(var i = text.length - 1; i >= 0; i--) {
if(text[i] == ' ' || text[i] == '<') {
simbol = text[i];
break;
}
else {
typed = text[i] + typed;
}
}

text = text.substr(0, text.length - typed.length);

var path = getActiveElement(cm, text) + simbol;
var hints = CodeMirror.xmlHints[path];

if(typeof hints === 'undefined')
hints = [''];
else {
hints = hints.slice(0);
for (var i = hints.length - 1; i >= 0; i--) {
if(hints[i].indexOf(typed) != 0)
hints.splice(i, 1);
}
}

return {
list: hints,
from: { line: cursor.line, ch: cursor.ch - typed.length },
to: cursor,
};
};
}

var getActiveElement = function(codeMirror, text) {

var element = '';

if(text.length >= 0) {

var regex = new RegExp('<([^!?][^\\s/>]*).*?>', 'g');

var matches = [];
var match;
while ((match = regex.exec(text)) != null) {
matches.push({
tag: match[1],
selfclose: (match[0].substr(-1) === '/>')
});
}

for (var i = matches.length - 1, skip = 0; i >= 0; i--) {

var item = matches[i];

if (item.tag[0] == '/')
{
skip++;
}
else if (item.selfclose == false)
{
if (skip > 0)
{
skip--;
}
else
{
element = '<' + item.tag + '>' + element;
}
}
}

element += getOpenTag(text);
}

return element;
};

var getOpenTag = function(text) {

var open = text.lastIndexOf('<');
var close = text.lastIndexOf('>');

if (close < open)
{
text = text.substr(open);

if(text != '<') {

var space = text.indexOf(' ');
if(space < 0)
space = text.indexOf('\t');
if(space < 0)
space = text.indexOf('\n');

if (space < 0)
space = text.length;

return text.substr(0, space);
}
}

return '';
};

})();
23 changes: 16 additions & 7 deletions mode/clike/clike.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CodeMirror.defineMode("clike", function(config, parserConfig) {
var indentUnit = config.indentUnit,
keywords = parserConfig.keywords || {},
builtin = parserConfig.builtin || {},
blockKeywords = parserConfig.blockKeywords || {},
atoms = parserConfig.atoms || {},
hooks = parserConfig.hooks || {},
Expand Down Expand Up @@ -47,8 +48,12 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "keyword";
}
if (builtin.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "builtin";
}
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
return "variable";
}

function tokenString(quote) {
Expand Down Expand Up @@ -211,14 +216,18 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
});
CodeMirror.defineMIME("text/x-csharp", {
name: "clike",
keywords: words("abstract as base bool break byte case catch char checked class const continue decimal" +
" default delegate do double else enum event explicit extern finally fixed float for" +
" foreach goto if implicit in int interface internal is lock long namespace new object" +
" operator out override params private protected public readonly ref return sbyte sealed short" +
" sizeof stackalloc static string struct switch this throw try typeof uint ulong unchecked" +
" unsafe ushort using virtual void volatile while add alias ascending descending dynamic from get" +
keywords: words("abstract as base break case catch checked class const continue" +
" default delegate do else enum event explicit extern finally fixed for" +
" foreach goto if implicit in interface internal is lock namespace new" +
" operator out override params private protected public readonly ref return sealed" +
" sizeof stackalloc static struct switch this throw try typeof unchecked" +
" unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
" global group into join let orderby partial remove select set value var yield"),
blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" +
" Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" +
" UInt64 bool byte char decimal double short int long object" +
" sbyte float string ushort uint ulong"),
atoms: words("true false null"),
hooks: {
"@": function(stream, state) {
Expand Down
13 changes: 6 additions & 7 deletions mode/coffeescript/coffeescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ CodeMirror.defineMode('coffeescript', function(conf) {
}

var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]");
var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]');
var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))");
var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))");
var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*");
var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*");

var wordOperators = wordRegexp(['and', 'or', 'not',
'is', 'isnt', 'in',
Expand Down Expand Up @@ -157,6 +158,10 @@ CodeMirror.defineMode('coffeescript', function(conf) {
if (stream.match(identifiers)) {
return 'variable';
}

if (stream.match(properties)) {
return 'property';
}

// Handle non-detected items
stream.next();
Expand Down Expand Up @@ -261,12 +266,6 @@ CodeMirror.defineMode('coffeescript', function(conf) {
}
}

// Handle properties
if (current === '@') {
stream.eat('@');
return 'keyword';
}

// Handle scope changes.
if (current === 'return') {
state.dedent += 1;
Expand Down
2 changes: 1 addition & 1 deletion mode/ecl/ecl.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ CodeMirror.defineMode("ecl", function(config) {
}
}
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
return null;
}

function tokenString(quote) {
Expand Down
354 changes: 283 additions & 71 deletions mode/erlang/erlang.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions mode/erlang/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ <h1>CodeMirror: Erlang mode</h1>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
extraKeys: {"Tab": "indentAuto"},
theme: "erlang-dark"
});
</script>
Expand Down
2 changes: 1 addition & 1 deletion mode/go/go.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ CodeMirror.defineMode("go", function(config, parserConfig) {
return "keyword";
}
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
return "variable";
}

function tokenString(quote) {
Expand Down
2 changes: 1 addition & 1 deletion mode/groovy/groovy.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ CodeMirror.defineMode("groovy", function(config, parserConfig) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "keyword";
}
return "word";
return "variable";
}
tokenBase.isBase = true;

Expand Down
432 changes: 432 additions & 0 deletions mode/haxe/haxe.js

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions mode/haxe/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Haxe mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="haxe.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
</head>
<body>
<h1>CodeMirror: Haxe mode</h1>

<div><textarea id="code" name="code">
import one.two.Three;

@attr("test")
class Foo&lt;T&gt; extends Three
{
public function new()
{
noFoo = 12;
}

public static inline function doFoo(obj:{k:Int, l:Float}):Int
{
for(i in 0...10)
{
obj.k++;
trace(i);
var var1 = new Array();
if(var1.length > 1)
throw "Error";
}
// The following line should not be colored, the variable is scoped out
var1;
/* Multi line
* Comment test
*/
return obj.k;
}
private function bar():Void
{
#if flash
var t1:String = "1.21";
#end
try {
doFoo({k:3, l:1.2});
}
catch (e : String) {
trace(e);
}
var t2:Float = cast(3.2);
var t3:haxe.Timer = new haxe.Timer();
var t4 = {k:Std.int(t2), l:Std.parseFloat(t1)};
var t5 = ~/123+.*$/i;
doFoo(t4);
untyped t1 = 4;
bob = new Foo&lt;Int&gt;
}
public var okFoo(default, never):Float;
var noFoo(getFoo, null):Int;
function getFoo():Int {
return noFoo;
}

public var three:Int;
}
enum Color
{
red;
green;
blue;
grey( v : Int );
rgb (r:Int,g:Int,b:Int);
}
</textarea></div>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
indentUnit: 4,
indentWithTabs: true
});
</script>

<p><strong>MIME types defined:</strong> <code>text/x-haxe</code>.</p>
</body>
</html>
184 changes: 92 additions & 92 deletions mode/less/less.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
LESS mode - http://www.lesscss.org/
Ported to CodeMirror by Peter Kroon
LESS mode - http://www.lesscss.org/
Ported to CodeMirror by Peter Kroon
*/

CodeMirror.defineMode("less", function(config) {
Expand All @@ -10,17 +10,17 @@ CodeMirror.defineMode("less", function(config) {
var tags = ["a","abbr","acronym","address","applet","area","article","aside","audio","b","base","basefont","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","command","datalist","dd","del","details","dfn","dir","div","dl","dt","em","embed","fieldset","figcaption","figure","font","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","keygen","kbd","label","legend","li","link","map","mark","menu","meta","meter","nav","noframes","noscript","object","ol","optgroup","option","output","p","param","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strike","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr"];

function inTagsArray(val){
for(var i=0; i<tags.length; i++){
if(val === tags[i]){
return true;
}
}
for(var i=0; i<tags.length; i++){
if(val === tags[i]){
return true;
}
}
}

function tokenBase(stream, state) {
var ch = stream.next();

if (ch == "@") {stream.eatWhile(/[\w\-]/); return ret("meta", stream.current());}
if (ch == "@") {stream.eatWhile(/[\w\-]/); return ret("meta", stream.current());}
else if (ch == "/" && stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
Expand All @@ -35,15 +35,15 @@ CodeMirror.defineMode("less", function(config) {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
else if (ch == "/") { // lesscss e.g.: .png will not be parsed as a class
if(stream.eat("/")){
state.tokenize = tokenSComment
return tokenSComment(stream, state);
}else{
stream.eatWhile(/[\a-zA-Z0-9\-_.\s]/);
if(/\/|\)|#/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == ")")))return ret("string", "string");//let url(/images/logo.png) without quotes return as string
else if (ch == "/") { // lesscss e.g.: .png will not be parsed as a class
if(stream.eat("/")){
state.tokenize = tokenSComment;
return tokenSComment(stream, state);
}else{
stream.eatWhile(/[\a-zA-Z0-9\-_.\s]/);
if(/\/|\)|#/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == ")")))return ret("string", "string");//let url(/images/logo.png) without quotes return as string
return ret("number", "unit");
}
}
}
else if (ch == "!") {
stream.match(/^\s*\w*/);
Expand All @@ -58,93 +58,93 @@ CodeMirror.defineMode("less", function(config) {
}
else if (/[;{}:\[\]()]/.test(ch)) { //added () char for lesscss original was [;{}:\[\]]
if(ch == ":"){
stream.eatWhile(/[active|hover|link|visited]/);
if( stream.current().match(/active|hover|link|visited/)){
return ret("tag", "tag");
}else{
return ret(null, ch);
}
}else{
return ret(null, ch);
}
stream.eatWhile(/[active|hover|link|visited]/);
if( stream.current().match(/active|hover|link|visited/)){
return ret("tag", "tag");
}else{
return ret(null, ch);
}
}else{
return ret(null, ch);
}
}
else if (ch == ".") { // lesscss
stream.eatWhile(/[\a-zA-Z0-9\-_]/);
else if (ch == ".") { // lesscss
stream.eatWhile(/[\a-zA-Z0-9\-_]/);
return ret("tag", "tag");
}
else if (ch == "#") { // lesscss
//we don't eat white-space, we want the hex color and or id only
stream.eatWhile(/[A-Za-z0-9]/);
//check if there is a proper hex color length e.g. #eee || #eeeEEE
if(stream.current().length ===4 || stream.current().length ===7){
if(stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false) != null){//is there a valid hex color value present in the current stream
//when not a valid hex value, parse as id
if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag");
//eat white-space
stream.eatSpace();
//when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,]
if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) )return ret("atom", "tag");
//#time { color: #aaa }
else if(stream.peek() == "}" )return ret("number", "unit");
//we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa
else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag");
//when a hex value is on the end of a line, parse as id
else if(stream.eol())return ret("atom", "tag");
//default
else return ret("number", "unit");
}else{//when not a valid hexvalue in the current stream e.g. #footer
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "tag");
}
}else{
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "tag");
}
else if (ch == "#") { // lesscss
//we don't eat white-space, we want the hex color and or id only
stream.eatWhile(/[A-Za-z0-9]/);
//check if there is a proper hex color length e.g. #eee || #eeeEEE
if(stream.current().length ===4 || stream.current().length ===7){
if(stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false) != null){//is there a valid hex color value present in the current stream
//when not a valid hex value, parse as id
if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag");
//eat white-space
stream.eatSpace();
//when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,]
if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) )return ret("atom", "tag");
//#time { color: #aaa }
else if(stream.peek() == "}" )return ret("number", "unit");
//we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa
else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag");
//when a hex value is on the end of a line, parse as id
else if(stream.eol())return ret("atom", "tag");
//default
else return ret("number", "unit");
}else{//when not a valid hexvalue in the current stream e.g. #footer
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "tag");
}
}else{
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "tag");
}
}
else if (ch == "&") {
stream.eatWhile(/[\w\-]/);
return ret(null, ch);
}
else if (ch == "&") {
stream.eatWhile(/[\w\-]/);
return ret(null, ch);
}
else {
stream.eatWhile(/[\w\\\-_%.{]/);
if(stream.current().match(/http|https/) != null){
stream.eatWhile(/[\w\\\-_%.{:\/]/);
return ret("string", "string");
}else if(stream.peek() == "<" || stream.peek() == ">"){
return ret("tag", "tag");
}else if( stream.peek().match(/\(/) != null ){// lessc
return ret(null, ch);
}else if (stream.peek() == "/" && state.stack[state.stack.length-1] != undefined){ // url(dir/center/image.png)
return ret("string", "string");
}else if( stream.current().match(/\-\d|\-.\d/) ){ // lesscss match e.g.: -5px -0.4 etc... only colorize the minus sign
//stream.backUp(stream.current().length-1); //commment out these 2 comment if you want the minus sign to be parsed as null -500px
//return ret(null, ch);
return ret("number", "unit");
}else if( inTagsArray(stream.current()) ){ // lesscss match html tags
return ret("tag", "tag");
}else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){
if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){
stream.backUp(1);
return ret("tag", "tag");
}//end if
if( (stream.eatSpace() && stream.peek().match(/[{<>.a-zA-Z]/) != null) || stream.eol() )return ret("tag", "tag");//e.g. button.icon-plus
return ret("string", "string");//let url(/images/logo.png) without quotes return as string
}else if( stream.eol() ){
if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1);
return ret("tag", "tag");
}else{
if(stream.current().match(/http|https/) != null){
stream.eatWhile(/[\w\\\-_%.{:\/]/);
return ret("string", "string");
}else if(stream.peek() == "<" || stream.peek() == ">"){
return ret("tag", "tag");
}else if( stream.peek().match(/\(/) != null ){// lessc
return ret(null, ch);
}else if (stream.peek() == "/" && state.stack[state.stack.length-1] != undefined){ // url(dir/center/image.png)
return ret("string", "string");
}else if( stream.current().match(/\-\d|\-.\d/) ){ // lesscss match e.g.: -5px -0.4 etc... only colorize the minus sign
//stream.backUp(stream.current().length-1); //commment out these 2 comment if you want the minus sign to be parsed as null -500px
//return ret(null, ch);
return ret("number", "unit");
}else if( inTagsArray(stream.current()) ){ // lesscss match html tags
return ret("tag", "tag");
}else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){
if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){
stream.backUp(1);
return ret("tag", "tag");
}//end if
if( (stream.eatSpace() && stream.peek().match(/[{<>.a-zA-Z]/) != null) || stream.eol() )return ret("tag", "tag");//e.g. button.icon-plus
return ret("string", "string");//let url(/images/logo.png) without quotes return as string
}else if( stream.eol() ){
if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1);
return ret("tag", "tag");
}else{
return ret("variable", "variable");
}
}
}

}

function tokenSComment(stream, state) {// SComment = Slash comment
stream.skipToEnd();
state.tokenize = tokenBase;
state.tokenize = tokenBase;
return ret("comment", "comment");
}

function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
Expand Down Expand Up @@ -198,10 +198,10 @@ CodeMirror.defineMode("less", function(config) {
else if (style == "variable") {
if (context == "rule") style = null; //"tag"
else if (!context || context == "@media{"){
style = stream.current() == "when" ? "variable" :
stream.string.match(/#/g) != undefined ? null :
/[\s,|\s\)]/.test(stream.peek()) ? "tag" : null;
}
style = stream.current() == "when" ? "variable" :
stream.string.match(/#/g) != undefined ? null :
/[\s,|\s\)]/.test(stream.peek()) ? "tag" : null;
}
}

if (context == "rule" && /^[\{\};]$/.test(type))
Expand Down
2 changes: 2 additions & 0 deletions mode/markdown/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ <h1>CodeMirror: Markdown mode</h1>
});
</script>

<p>Optionally depends on the XML mode for properly highlighted inline XML blocks.</p>

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

</body>
Expand Down
11 changes: 8 additions & 3 deletions mode/markdown/markdown.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

var htmlMode = CodeMirror.getMode(cmCfg, { name: 'xml', htmlMode: true });
var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html");
var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain");

var header = 'header'
, code = 'comment'
Expand Down Expand Up @@ -37,6 +38,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
state.em = false;
// Reset STRONG state
state.strong = false;
if (!htmlFound && state.f == htmlBlock) {
state.f = inlineNormal;
state.block = blockNormal;
}
return null;
}

Expand Down Expand Up @@ -67,7 +72,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

function htmlBlock(stream, state) {
var style = htmlMode.token(stream, state.htmlState);
if (style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
state.f = inlineNormal;
state.block = blockNormal;
}
Expand Down Expand Up @@ -188,7 +193,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
f: blockNormal,

block: blockNormal,
htmlState: htmlMode.startState(),
htmlState: CodeMirror.startState(htmlMode),
indentation: 0,

inline: inlineNormal,
Expand Down
14 changes: 6 additions & 8 deletions mode/mysql/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ CodeMirror.defineMode("mysql", function(config) {
var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
"isblank", "isliteral", "union", "a"]);
var keywords = wordRegexp([
('ACCESSIBLE'),('ALTER'),('AS'),('BEFORE'),('BINARY'),('BY'),('CASE'),('CHARACTER'),('COLUMN'),('CONTINUE'),('CROSS'),('CURRENT_TIMESTAMP'),('DATABASE'),('DAY_MICROSECOND'),('DEC'),('DEFAULT'),
('ACCESSIBLE'),('ALTER'),('AS'),('BEFORE'),('BINARY'),('BY'),('CASE'),('CHARACTER'),('COLUMN'),('CONTINUE'),('CROSS'),('CURRENT_TIMESTAMP'),('DATABASE'),('DAY_MICROSECOND'),('DEC'),('DEFAULT'),
('DESC'),('DISTINCT'),('DOUBLE'),('EACH'),('ENCLOSED'),('EXIT'),('FETCH'),('FLOAT8'),('FOREIGN'),('GRANT'),('HIGH_PRIORITY'),('HOUR_SECOND'),('IN'),('INNER'),('INSERT'),('INT2'),('INT8'),
('INTO'),('JOIN'),('KILL'),('LEFT'),('LINEAR'),('LOCALTIME'),('LONG'),('LOOP'),('MATCH'),('MEDIUMTEXT'),('MINUTE_SECOND'),('NATURAL'),('NULL'),('OPTIMIZE'),('OR'),('OUTER'),('PRIMARY'),
('RANGE'),('READ_WRITE'),('REGEXP'),('REPEAT'),('RESTRICT'),('RIGHT'),('SCHEMAS'),('SENSITIVE'),('SHOW'),('SPECIFIC'),('SQLSTATE'),('SQL_CALC_FOUND_ROWS'),('STARTING'),('TERMINATED'),
Expand Down Expand Up @@ -57,13 +57,11 @@ CodeMirror.defineMode("mysql", function(config) {
return null;
}
else if (ch == "-") {
ch2 = stream.next();
if(ch2=="-")
{
stream.skipToEnd();
return "comment";
}

var ch2 = stream.next();
if (ch2=="-") {
stream.skipToEnd();
return "comment";
}
}
else if (operatorChars.test(ch)) {
stream.eatWhile(operatorChars);
Expand Down
130 changes: 130 additions & 0 deletions mode/ocaml/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<!doctype html>
<meta charset=utf-8>
<title>CodeMirror: OCaml mode</title>

<link rel=stylesheet href=../../lib/codemirror.css>
<link rel=stylesheet href=../../doc/docs.css>

<style type=text/css>
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
</style>

<script src=../../lib/codemirror.js></script>
<script src=ocaml.js></script>

<h1>CodeMirror: OCaml mode</h1>

<textarea id=code>
(* Summing a list of integers *)
let rec sum xs =
match xs with
| [] -&gt; 0
| x :: xs' -&gt; x + sum xs'

(* Quicksort *)
let rec qsort = function
| [] -&gt; []
| pivot :: rest -&gt;
let is_less x = x &lt; pivot in
let left, right = List.partition is_less rest in
qsort left @ [pivot] @ qsort right

(* Fibonacci Sequence *)
let rec fib_aux n a b =
match n with
| 0 -&gt; a
| _ -&gt; fib_aux (n - 1) (a + b) a
let fib n = fib_aux n 0 1

(* Birthday paradox *)
let year_size = 365.

let rec birthday_paradox prob people =
let prob' = (year_size -. float people) /. year_size *. prob in
if prob' &lt; 0.5 then
Printf.printf "answer = %d\n" (people+1)
else
birthday_paradox prob' (people+1) ;;

birthday_paradox 1.0 1

(* Church numerals *)
let zero f x = x
let succ n f x = f (n f x)
let one = succ zero
let two = succ (succ zero)
let add n1 n2 f x = n1 f (n2 f x)
let to_string n = n (fun k -&gt; "S" ^ k) "0"
let _ = to_string (add (succ two) two)

(* Elementary functions *)
let square x = x * x;;
let rec fact x =
if x &lt;= 1 then 1 else x * fact (x - 1);;

(* Automatic memory management *)
let l = 1 :: 2 :: 3 :: [];;
[1; 2; 3];;
5 :: l;;

(* Polymorphism: sorting lists *)
let rec sort = function
| [] -&gt; []
| x :: l -&gt; insert x (sort l)

and insert elem = function
| [] -&gt; [elem]
| x :: l -&gt;
if elem &lt; x then elem :: x :: l else x :: insert elem l;;

(* Imperative features *)
let add_polynom p1 p2 =
let n1 = Array.length p1
and n2 = Array.length p2 in
let result = Array.create (max n1 n2) 0 in
for i = 0 to n1 - 1 do result.(i) &lt;- p1.(i) done;
for i = 0 to n2 - 1 do result.(i) &lt;- result.(i) + p2.(i) done;
result;;
add_polynom [| 1; 2 |] [| 1; 2; 3 |];;

(* We may redefine fact using a reference cell and a for loop *)
let fact n =
let result = ref 1 in
for i = 2 to n do
result := i * !result
done;
!result;;
fact 5;;

(* Triangle (graphics) *)
let () =
ignore( Glut.init Sys.argv );
Glut.initDisplayMode ~double_buffer:true ();
ignore (Glut.createWindow ~title:"OpenGL Demo");
let angle t = 10. *. t *. t in
let render () =
GlClear.clear [ `color ];
GlMat.load_identity ();
GlMat.rotate ~angle: (angle (Sys.time ())) ~z:1. ();
GlDraw.begins `triangles;
List.iter GlDraw.vertex2 [-1., -1.; 0., 1.; 1., -1.];
GlDraw.ends ();
Glut.swapBuffers () in
GlMat.mode `modelview;
Glut.displayFunc ~cb:render;
Glut.idleFunc ~cb:(Some Glut.postRedisplay);
Glut.mainLoop ()

(* A Hundred Lines of Caml - http://caml.inria.fr/about/taste.en.html *)
(* OCaml page on Wikipedia - http://en.wikipedia.org/wiki/OCaml *)
</textarea>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
mode: 'ocaml',
lineNumbers: true,
matchBrackets: true
});
</script>

<p><strong>MIME types defined:</strong> <code>text/x-ocaml</code>.</p>
114 changes: 114 additions & 0 deletions mode/ocaml/ocaml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
CodeMirror.defineMode('ocaml', function(config) {

var words = {
'true': 'atom',
'false': 'atom',
'let': 'keyword',
'rec': 'keyword',
'in': 'keyword',
'of': 'keyword',
'and': 'keyword',
'succ': 'keyword',
'if': 'keyword',
'then': 'keyword',
'else': 'keyword',
'for': 'keyword',
'to': 'keyword',
'while': 'keyword',
'do': 'keyword',
'done': 'keyword',
'fun': 'keyword',
'function': 'keyword',
'val': 'keyword',
'type': 'keyword',
'mutable': 'keyword',
'match': 'keyword',
'with': 'keyword',
'try': 'keyword',
'raise': 'keyword',
'begin': 'keyword',
'end': 'keyword',
'open': 'builtin',
'trace': 'builtin',
'ignore': 'builtin',
'exit': 'builtin',
'print_string': 'builtin',
'print_endline': 'builtin'
};

function tokenBase(stream, state) {
var sol = stream.sol();
var ch = stream.next();

if (ch === '"') {
state.tokenize = tokenString;
return state.tokenize(stream, state);
}
if (ch === '(') {
if (stream.eat('*')) {
state.commentLevel++;
state.tokenize = tokenComment;
return state.tokenize(stream, state);
}
}
if (ch === '~') {
stream.eatWhile(/\w/);
return 'variable-2';
}
if (ch === '`') {
stream.eatWhile(/\w/);
return 'quote';
}
if (/\d/.test(ch)) {
stream.eatWhile(/[\d]/);
if (stream.eat('.')) {
stream.eatWhile(/[\d]/);
}
return 'number';
}
if ( /[+\-*&%=<>!?|]/.test(ch)) {
return 'operator';
}
stream.eatWhile(/\w/);
var cur = stream.current();
return words[cur] || 'variable';
}

function tokenString(stream, state) {
var next, end = false, escaped = false;
while ((next = stream.next()) != null) {
if (next === '"' && !escaped) {
end = true;
break;
}
escaped = !escaped && next === '\\';
}
if (end && !escaped) {
state.tokenize = tokenBase;
}
return 'string';
};

function tokenComment(stream, state) {
var prev, next;
while(state.commentLevel > 0 && (next = stream.next()) != null) {
if (prev === '(' && next === '*') state.commentLevel++;
if (prev === '*' && next === ')') state.commentLevel--;
prev = next;
}
if (state.commentLevel <= 0) {
state.tokenize = tokenBase;
}
return 'comment';
}

return {
startState: function() {return {tokenize: tokenBase, commentLevel: 0}},
token: function(stream, state) {
if (stream.eatSpace()) return null;
return state.tokenize(stream, state);
}
};
});

CodeMirror.defineMIME('text/x-ocaml', 'ocaml');
2 changes: 1 addition & 1 deletion mode/pascal/pascal.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ CodeMirror.defineMode("pascal", function(config) {
var cur = stream.current();
if (keywords.propertyIsEnumerable(cur)) return "keyword";
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
return "variable";
}

function tokenString(quote) {
Expand Down
4 changes: 2 additions & 2 deletions mode/pig/pig.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ CodeMirror.defineMode("pig", function(config, parserConfig) {
// is it one of the listed types?
if (types && types.propertyIsEnumerable(stream.current().toUpperCase()))
return ("keyword", "variable-3")
// default is a 'word'
return ret("word", "pig-word");
// default is a 'variable'
return ret("variable", "pig-word");
}
}

Expand Down
4 changes: 2 additions & 2 deletions mode/plsql/plsql.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ CodeMirror.defineMode("plsql", function(config, parserConfig) {
if (types && types.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-2");
// is it one of the listed sqlplus keywords?
if (sqlplus && sqlplus.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-3");
// default: just a "word"
return ret("word", "plsql-word");
// default: just a "variable"
return ret("word", "variable");
}
}

Expand Down
29 changes: 22 additions & 7 deletions mode/shell/shell.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
CodeMirror.defineMode('shell', function(config) {

var atoms = ['true','false'],
keywords = ['if','then','do','else','elif','while','until','for','in','esac','fi','fin','fil','done','exit','set','unset','export','function'],
commands = ['ab','awk','bash','beep','cat','cc','cd','chown','chmod','chroot','clear','cp','curl','cut','diff','echo','find','gawk','gcc','get','git','grep','kill','killall','ls','make','mkdir','openssl','mv','nc','node','npm','ping','ps','restart','rm','rmdir','sed','service','sh','shopt','shred','source','sort','sleep','ssh','start','stop','su','sudo','tee','telnet','top','touch','vi','vim','wall','wc','wget','who','write','yes','zsh'];
var words = {};
function define(style, string) {
var split = string.split(' ');
for(var i = 0; i < split.length; i++) {
words[split[i]] = style;
}
};

// Atoms
define('atom', 'true false');

// Keywords
define('keyword', 'if then do else elif while until for in esac fi fin ' +
'fil done exit set unset export function');

// Commands
define('builtin', 'ab awk bash beep cat cc cd chown chmod chroot clear cp ' +
'curl cut diff echo find gawk gcc get git grep kill killall ln ls make ' +
'mkdir openssl mv nc node npm ping ps restart rm rmdir sed service sh ' +
'shopt shred source sort sleep ssh start stop su sudo tee telnet top ' +
'touch vi vim wall wc wget who write yes zsh');

function tokenBase(stream, state) {

Expand Down Expand Up @@ -42,10 +60,7 @@ CodeMirror.defineMode('shell', function(config) {
stream.eatWhile(/\w/);
var cur = stream.current();
if (stream.peek() === '=' && /\w+/.test(cur)) return 'def';
if (atoms.indexOf(cur) !== -1) return 'atom';
if (commands.indexOf(cur) !== -1) return 'builtin';
if (keywords.indexOf(cur) !== -1) return 'keyword';
return 'word';
return words[cur] || null;
}

function tokenString(quote) {
Expand Down
2 changes: 1 addition & 1 deletion mode/stex/stex.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,5 +178,5 @@ CodeMirror.defineMode("stex", function(cmCfg, modeCfg)
};
});


CodeMirror.defineMIME("text/x-stex", "stex");
CodeMirror.defineMIME("text/x-latex", "stex");
21 changes: 21 additions & 0 deletions mode/vb/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License

Copyright (c) 2012 Codility Limited, 107 Cheapside, London EC2V 6DN, UK

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.
88 changes: 88 additions & 0 deletions mode/vb/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<html>
<head>
<title>CodeMirror: VB.NET mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="vb.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<link href="http://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet" type="text/css">
<style>
.CodeMirror {border: 1px solid #aaa; height:210px;}
.CodeMirror-scroll { overflow-x: auto; height: 100%;}
.CodeMirror pre { font-family: Inconsolata; font-size: 14px}
</style>
<script type="text/javascript" src="../../lib/util/runmode.js"></script>
</head>
<body onload="init()">
<h1>CodeMirror: VB.NET mode</h1>
<script type="text/javascript">
function test(golden, text) {
var ok = true;
var i = 0;
function callback(token, style, lineNo, pos){
//console.log(String(token) + " " + String(style) + " " + String(lineNo) + " " + String(pos));
var result = [String(token), String(style)];
if (golden[i][0] != result[0] || golden[i][1] != result[1]){
return "Error, expected: " + String(golden[i]) + ", got: " + String(result);
ok = false;
}
i++;
}
CodeMirror.runMode(text, "text/x-vb",callback);

if (ok) return "Tests OK";
}
function testTypes() {
var golden = [['Integer','keyword'],[' ','null'],['Float','keyword']]
var text = "Integer Float";
return test(golden,text);
}
function testIf(){
var golden = [['If','keyword'],[' ','null'],['True','keyword'],[' ','null'],['End','keyword'],[' ','null'],['If','keyword']];
var text = 'If True End If';
return test(golden, text);
}
function testDecl(){
var golden = [['Dim','keyword'],[' ','null'],['x','variable'],[' ','null'],['as','keyword'],[' ','null'],['Integer','keyword']];
var text = 'Dim x as Integer';
return test(golden, text);
}
function testAll(){
var result = "";

result += testTypes() + "\n";
result += testIf() + "\n";
result += testDecl() + "\n";
return result;

}
function initText(editor) {
var content = 'Class rocket\nPrivate quality as Double\nPublic Sub launch() as String\nif quality > 0.8\nlaunch = "Successful"\nElse\nlaunch = "Failed"\nEnd If\nEnd sub\nEnd class\n';
editor.setValue(content);
for (var i =0; i< editor.lineCount(); i++) editor.indentLine(i);
}
function init() {
editor = CodeMirror.fromTextArea(document.getElementById("solution"), {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-vb",
readOnly: false,
tabMode: "shift"
});
runTest();
}
function runTest() {
document.getElementById('testresult').innerHTML = testAll();
initText(editor);

}
</script>


<div id="edit">
<textarea style="width:95%;height:200px;padding:5px;" name="solution" id="solution" ></textarea>
</div>
<pre id="testresult"></pre>
<p>MIME type defined: <code>text/x-vb</code>.</p>

</body></html>
260 changes: 260 additions & 0 deletions mode/vb/vb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
CodeMirror.defineMode("vb", function(conf, parserConf) {
var ERRORCLASS = 'error';

function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b", "i");
}

var singleOperators = new RegExp("^[\\+\\-\\*/%&\\\\|\\^~<>!]");
var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
var doubleOperators = new RegExp("^((==)|(<>)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
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 endKeywords = ['next','loop'];

var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'in']);
var commonkeywords = ['as', 'dim', 'break', 'continue','optional', 'then', 'until',
'goto', 'byval','byref','new','handles','property', 'return',
'const','private', 'protected', 'friend', 'public', 'shared', 'static', 'true','false'];
var commontypes = ['integer','string','double','decimal','boolean','short','char', 'float','single'];

var keywords = wordRegexp(commonkeywords);
var types = wordRegexp(commontypes);
var stringPrefixes = '"';

var opening = wordRegexp(openingKeywords);
var middle = wordRegexp(middleKeywords);
var closing = wordRegexp(endKeywords);
var doubleClosing = wordRegexp(['end']);
var doOpening = wordRegexp(['do']);

var indentInfo = null;




function indent(stream, state) {
state.currentIndent++;
}

function dedent(stream, state) {
state.currentIndent--;
}
// tokenizers
function tokenBase(stream, state) {
if (stream.eatSpace()) {
return null;
}

var ch = stream.peek();

// Handle Comments
if (ch === "'") {
stream.skipToEnd();
return 'comment';
}


// Handle Number Literals
if (stream.match(/^((&H)|(&O))?[0-9\.a-f]/i, false)) {
var floatLiteral = false;
// Floats
if (stream.match(/^\d*\.\d+F?/i)) { floatLiteral = true; }
else if (stream.match(/^\d+\.\d*F?/)) { floatLiteral = true; }
else if (stream.match(/^\.\d+F?/)) { floatLiteral = true; }

if (floatLiteral) {
// Float literals may be "imaginary"
stream.eat(/J/i);
return 'number';
}
// Integers
var intLiteral = false;
// Hex
if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; }
// Octal
else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; }
// Decimal
else if (stream.match(/^[1-9]\d*F?/)) {
// Decimal literals may be "imaginary"
stream.eat(/J/i);
// TODO - Can you have imaginary longs?
intLiteral = true;
}
// Zero by itself with no other piece of number.
else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
if (intLiteral) {
// Integer literals may be "long"
stream.eat(/L/i);
return 'number';
}
}

// Handle Strings
if (stream.match(stringPrefixes)) {
state.tokenize = tokenStringFactory(stream.current());
return state.tokenize(stream, state);
}

// Handle operators and Delimiters
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
return null;
}
if (stream.match(doubleOperators)
|| stream.match(singleOperators)
|| stream.match(wordOperators)) {
return 'operator';
}
if (stream.match(singleDelimiters)) {
return null;
}
if (stream.match(doOpening)) {
indent(stream,state);
state.doInCurrentLine = true;
return 'keyword';
}
if (stream.match(opening)) {
if (! state.doInCurrentLine)
indent(stream,state);
else
state.doInCurrentLine = false;
return 'keyword';
}
if (stream.match(middle)) {
return 'keyword';
}

if (stream.match(doubleClosing)) {
dedent(stream,state);
dedent(stream,state);
return 'keyword';
}
if (stream.match(closing)) {
dedent(stream,state);
return 'keyword';
}

if (stream.match(types)) {
return 'keyword';
}

if (stream.match(keywords)) {
return 'keyword';
}

if (stream.match(identifiers)) {
return 'variable';
}

// Handle non-detected items
stream.next();
return ERRORCLASS;
}

function tokenStringFactory(delimiter) {
var singleline = delimiter.length == 1;
var OUTCLASS = 'string';

return function tokenString(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"]/);
if (stream.match(delimiter)) {
state.tokenize = tokenBase;
return OUTCLASS;
} else {
stream.eat(/['"]/);
}
}
if (singleline) {
if (parserConf.singleLineStringErrors) {
return ERRORCLASS;
} else {
state.tokenize = tokenBase;
}
}
return OUTCLASS;
};
}


function tokenLexer(stream, state) {
var style = state.tokenize(stream, state);
var current = stream.current();

// Handle '.' connected identifiers
if (current === '.') {
style = state.tokenize(stream, state);
current = stream.current();
if (style === 'variable') {
return 'variable';
} else {
return ERRORCLASS;
}
}


var delimiter_index = '[({'.indexOf(current);
if (delimiter_index !== -1) {
indent(stream, state );
}
if (indentInfo === 'dedent') {
if (dedent(stream, state)) {
return ERRORCLASS;
}
}
delimiter_index = '])}'.indexOf(current);
if (delimiter_index !== -1) {
if (dedent(stream, state)) {
return ERRORCLASS;
}
}

return style;
}

var external = {
electricChars:"dDpPtTfFeE ",
startState: function(basecolumn) {
return {
tokenize: tokenBase,
lastToken: null,
currentIndent: 0,
nextLineIndent: 0,
doInCurrentLine: false


};
},

token: function(stream, state) {
if (stream.sol()) {
state.currentIndent += state.nextLineIndent;
state.nextLineIndent = 0;
state.doInCurrentLine = 0;
}
var style = tokenLexer(stream, state);

state.lastToken = {style:style, content: stream.current()};



return style;
},

indent: function(state, textAfter) {
var trueText = textAfter.replace(/^\s+|\s+$/g, '') ;
if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1);
if(state.currentIndent < 0) return 0;
return state.currentIndent * conf.indentUnit;
}

};
return external;
});

CodeMirror.defineMIME("text/x-vb", "vb");

2 changes: 1 addition & 1 deletion mode/verilog/verilog.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) {
return "keyword";
}
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
return "variable";
}

function tokenString(quote) {
Expand Down
3 changes: 2 additions & 1 deletion mode/xml/xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,15 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
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) return false;
if (ca.tagName != cb.tagName || ca.indent != cb.indent) return false;
}
},

electricChars: "/"
};
});

CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
6 changes: 3 additions & 3 deletions mode/xquery/xquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {
return ret("tag", "tag");
}
else
return ret("word", "word");
return ret("word", "variable");
}
// if a number
else if (/\d/.test(ch)) {
Expand Down Expand Up @@ -213,15 +213,15 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {
// if the previous word was element, attribute, axis specifier, this word should be the name of that
if(isInXmlConstructor(state)) {
popStateStack(state);
return ret("word", "word", word);
return ret("word", "variable", word);
}
// as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and
// push the stack so we know to look for it on the next word
if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"});

// if the word is known, return the details of that else just call this a generic 'word'
return known ? ret(known.type, known.style, word) :
ret("word", "word", word);
ret("word", "variable", word);
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "CodeMirror",
"version":"2.30.0",
"version":"2.31.0",
"main": "codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [
Expand Down
42 changes: 42 additions & 0 deletions test/driver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
var tests = [], runOnly = null;

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

function test(name, run) {tests.push({name: name, func: run}); return name;}
function testCM(name, run, opts) {
return test(name, function() {
var place = document.getElementById("testground"), cm = CodeMirror(place, opts);
try {run(cm);}
finally {place.removeChild(cm.getWrapperElement());}
});
}

function runTests(callback) {
function step(i) {
if (i == tests.length) return callback("done");
var test = tests[i];
if (runOnly != null && runOnly != test.name) return step(i + 1);
try {test.func(); callback("ok", test.name);}
catch(e) {
if (e instanceof Failure)
callback("fail", test.name, e.message);
else
callback("error", test.name, e.toString());
}
setTimeout(function(){step(i + 1);}, 20);
}
step(0);
}

function eq(a, b, msg) {
if (a != b) throw new Failure(a + " != " + b + (msg ? " (" + msg + ")" : ""));
}
function eqPos(a, b, msg) {
if (a == b) return;
if (a == null || b == null) throw new Failure("comparing point to null");
eq(a.line, b.line, msg);
eq(a.ch, b.ch, msg);
}
function is(a, msg) {
if (!a) throw new Failure("assertion failed" + (msg ? " (" + msg + ")" : ""));
}
33 changes: 30 additions & 3 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<script src="../mode/javascript/javascript.js"></script>

<style type="text/css">
.ok {color: #0e0;}
.failure {color: #e00;}
.ok {color: #090;}
.fail {color: #e00;}
.error {color: #c90;}
</style>
</head>
Expand All @@ -17,12 +17,39 @@ <h1>CodeMirror: Test Suite</h1>

<p>A limited set of programmatic sanity tests for CodeMirror.</p>

<pre id=output></pre>
<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>
<pre style="padding-left: 5px" id=output></pre>

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

<script src="driver.js"></script>
<script src="test.js"></script>
<script>
window.onload = function() {
runTests(displayTest);
};

var output = document.getElementById("output"), progress = document.getElementById("progress");
var count = 0, failed = 0, bad = "";
function displayTest(type, name, msg) {
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";
if (type == "ok") {
output.innerHTML = bad + "<span class=ok>Test '" + CodeMirror.htmlEscape(name) + "' succeeded</span>";
} else if (type == "error" || type == "fail") {
++failed;
bad += CodeMirror.htmlEscape(name) + ": <span class=" + type + ">" + CodeMirror.htmlEscape(msg) + "</span>\n";
output.innerHTML = bad;
} else if (type == "done") {
output.innerHTML = bad + (failed ? "<span class=fail>" + failed + " failure" + (failed > 1 ? "s" : "") + "</span>"
: "<span class=ok>All passed</span>");
}
}
</script>
</body>
</html>
243 changes: 183 additions & 60 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
var tests = [];
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}

function addDoc(cm, width, height) {
var content = [], line = "";
for (var i = 0; i < width; ++i) line += "x";
for (var i = 0; i < height; ++i) content.push(line);
cm.setValue(content.join("\n"));
}

function byClassName(elt, cls) {
if (elt.getElementsByClassName) return elt.getElementsByClassName(cls);
var found = [], re = new RegExp("\\b" + cls + "\\b");
function search(elt) {
if (elt.nodeType == 3) return;
if (re.test(elt.className)) found.push(elt);
for (var i = 0, e = elt.childNodes.length; i < e; ++i)
search(elt.childNodes[i]);
}
search(elt);
return found;
}

test("fromTextArea", function() {
var te = document.getElementById("code");
Expand Down Expand Up @@ -116,11 +138,8 @@ testCM("lineInfo", function(cm) {
}, {value: "111111\n222222\n333333"});

testCM("coords", function(cm) {
var scroller = cm.getScrollerElement();
scroller.style.height = "100px";
var content = [];
for (var i = 0; i < 200; ++i) content.push("------------------------------" + i);
cm.setValue(content.join("\n"));
cm.setSize(null, 100);
addDoc(cm, 32, 200);
var top = cm.charCoords({line: 0, ch: 0});
var bot = cm.charCoords({line: 200, ch: 30});
is(top.x < bot.x);
Expand All @@ -133,10 +152,8 @@ testCM("coords", function(cm) {
});

testCM("coordsChar", function(cm) {
var content = [];
for (var i = 0; i < 70; ++i) content.push("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
cm.setValue(content.join("\n"));
for (var ch = 0; ch < 35; ch += 2) {
addDoc(cm, 35, 70);
for (var ch = 0; ch <= 35; ch += 5) {
for (var line = 0; line < 70; line += 5) {
cm.setCursor(line, ch);
var coords = cm.charCoords({line: line, ch: ch});
Expand Down Expand Up @@ -262,6 +279,13 @@ testCM("markTextMultiLine", function(cm) {
});
});

testCM("markClearBetween", function(cm) {
cm.setValue("aaa\nbbb\nccc\nddd\n");
cm.markText({line: 0, ch: 0}, {line: 2}, "foo");
cm.replaceRange("aaa\nbbb\nccc", {line: 0, ch: 0}, {line: 2});
eq(cm.findMarksAt({line: 1, ch: 1}).length, 0);
});

testCM("bookmark", function(cm) {
function p(v) { return v && {line: v[0], ch: v[1]}; }
forEach([{a: [1, 0], b: [1, 1], c: "", d: [1, 4]},
Expand All @@ -286,59 +310,158 @@ testCM("bug577", function(cm) {
cm.undo();
});

// Scaffolding
testCM("scrollSnap", function(cm) {
cm.setSize(100, 100);
addDoc(cm, 200, 200);
cm.setCursor({line: 100, ch: 180});
var info = cm.getScrollInfo();
is(info.x > 0 && info.y > 0);
cm.setCursor({line: 0, ch: 0});
info = cm.getScrollInfo();
is(info.x == 0 && info.y == 0, "scrolled clean to top");
cm.setCursor({line: 100, ch: 180});
cm.setCursor({line: 199, ch: 0});
info = cm.getScrollInfo();
is(info.x == 0 && info.y == info.height - 100, "scrolled clean to bottom");
});

function htmlEscape(str) {
return str.replace(/[<&]/g, function(str) {return str == "&" ? "&amp;" : "&lt;";});
}
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}
testCM("selectionPos", function(cm) {
cm.setSize(100, 100);
addDoc(cm, 200, 100);
cm.setSelection({line: 1, ch: 100}, {line: 98, ch: 100});
var lineWidth = cm.charCoords({line: 0, ch: 200}, "local").x;
var lineHeight = cm.charCoords({line: 1}).y - cm.charCoords({line: 0}).y;
cm.scrollTo(0, 0);
var selElt = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
var outer = cm.getWrapperElement().getBoundingClientRect();
var sawMiddle, sawTop, sawBottom;
for (var i = 0, e = selElt.length; i < e; ++i) {
var box = selElt[i].getBoundingClientRect();
var atLeft = box.left - outer.left < 30;
var width = box.right - box.left;
var atRight = box.right - outer.left > .8 * lineWidth;
if (atLeft && atRight) {
sawMiddle = true;
is(box.bottom - box.top > 95 * lineHeight, "middle high");
is(width > .9 * lineWidth, "middle wide");
} else {
is(width > .4 * lineWidth, "top/bot wide enough");
is(width < .6 * lineWidth, "top/bot slim enough");
if (atLeft) {
sawBottom = true;
is(box.top - outer.top > 96 * lineHeight, "bot below");
} else if (atRight) {
sawTop = true;
is(box.top - outer.top < 2 * lineHeight, "top above");
}
}
}
is(sawTop && sawBottom && sawMiddle, "all parts");
});

function Failure(why) {this.message = why;}
testCM("restoreHistory", function(cm) {
cm.setValue("abc\ndef");
cm.compoundChange(function() {cm.setLine(1, "hello");});
cm.compoundChange(function() {cm.setLine(0, "goop");});
cm.undo();
var storedVal = cm.getValue(), storedHist = cm.getHistory();
if (window.JSON) storedHist = JSON.parse(JSON.stringify(storedHist));
eq(storedVal, "abc\nhello");
cm.setValue("");
cm.clearHistory();
eq(cm.historySize().undo, 0);
cm.setValue(storedVal);
cm.setHistory(storedHist);
cm.redo();
eq(cm.getValue(), "goop\nhello");
cm.undo(); cm.undo();
eq(cm.getValue(), "abc\ndef");
});

function test(name, run) {tests.push({name: name, func: run});}
function testCM(name, run, opts) {
test(name, function() {
var place = document.getElementById("testground"), cm = CodeMirror(place, opts);
try {run(cm);}
finally {place.removeChild(cm.getWrapperElement());}
});
}
testCM("doubleScrollbar", function(cm) {
var dummy = document.body.appendChild(document.createElement("p"));
dummy.style.cssText = "height: 50px; overflow: scroll; width: 50px";
var scrollbarWidth = dummy.offsetWidth + 1 - dummy.clientWidth;
document.body.removeChild(dummy);
cm.setSize(null, 100);
addDoc(cm, 1, 300);
var wrap = cm.getWrapperElement();
is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth);
});

function runTests() {
var failures = [], run = 0;
for (var i = 0; i < tests.length; ++i) {
var test = tests[i];
try {test.func();}
catch(e) {
if (e instanceof Failure)
failures.push({type: "failure", test: test.name, text: e.message});
else
failures.push({type: "error", test: test.name, text: e.toString()});
}
run++;
}
var html = [run + " tests run."];
if (failures.length)
forEach(failures, function(fail) {
html.push(fail.test + ': <span class="' + fail.type + '">' + htmlEscape(fail.text) + "</span>");
});
else html.push('<span class="ok">All passed.</span>');
document.getElementById("output").innerHTML = html.join("\n");
}
testCM("weirdLinebreaks", function(cm) {
cm.setValue("foo\nbar\rbaz\r\nquux\n\rplop");
is(cm.getValue(), "foo\nbar\nbaz\nquux\n\nplop");
is(cm.lineCount(), 6);
cm.setValue("\n\n");
is(cm.lineCount(), 3);
});

function eq(a, b, msg) {
if (a != b) throw new Failure(a + " != " + b + (msg ? " (" + msg + ")" : ""));
}
function eqPos(a, b, msg) {
if (a == b) return;
if (a == null || b == null) throw new Failure("comparing point to null");
eq(a.line, b.line, msg);
eq(a.ch, b.ch, msg);
}
function is(a, msg) {
if (!a) throw new Failure("assertion failed" + (msg ? " (" + msg + ")" : ""));
}
testCM("setSize", function(cm) {
cm.setSize(100, 100);
is(cm.getWrapperElement().offsetWidth, 100);
is(cm.getWrapperElement().offsetHeight, 100);
cm.setSize("100%", "3em");
is(cm.getWrapperElement().style.width, "100%");
is(cm.getScrollerElement().style.height, "3em");
cm.setSize(null, 40);
is(cm.getWrapperElement().style.width, "100%");
is(cm.getScrollerElement().style.height, "40px");
});

window.onload = runTests;
testCM("hiddenLines", function(cm) {
addDoc(cm, 4, 10);
cm.hideLine(4);
cm.setCursor({line: 3, ch: 0});
CodeMirror.commands.goLineDown(cm);
eqPos(cm.getCursor(), {line: 5, ch: 0});
cm.setLine(3, "abcdefg");
cm.setCursor({line: 3, ch: 6});
CodeMirror.commands.goLineDown(cm);
eqPos(cm.getCursor(), {line: 5, ch: 4});
cm.setLine(3, "ab");
cm.setCursor({line: 3, ch: 2});
CodeMirror.commands.goLineDown(cm);
eqPos(cm.getCursor(), {line: 5, ch: 2});
});

testCM("hiddenLinesSelectAll", function(cm) { // Issue #484
addDoc(cm, 4, 20);
for (var i = 0; i < 20; ++i)
if (i != 10) cm.hideLine(i);
CodeMirror.commands.selectAll(cm);
eqPos(cm.getCursor(true), {line: 10, ch: 0});
eqPos(cm.getCursor(false), {line: 10, ch: 4});
});

testCM("wrappingAndResizing", function(cm) {
cm.setSize(null, "auto");
cm.setOption("lineWrapping", true);
var wrap = cm.getWrapperElement(), h0 = wrap.offsetHeight, w = 50;
var doc = "xxx xxx xxx xxx xxx";
cm.setValue(doc);
for (var step = 10;; w += step) {
cm.setSize(w);
if (wrap.offsetHeight == h0) {
if (step == 10) { w -= 10; step = 1; }
else { w--; break; }
}
}
// Ensure that putting the cursor at the end of the maximally long
// line doesn't cause wrapping to happen.
cm.setCursor({line: 0, ch: doc.length});
eq(wrap.offsetHeight, h0);
cm.replaceSelection("x");
is(wrap.offsetHeight > h0);
// Now add a max-height and, in a document consisting of
// almost-wrapped lines, go over it so that a scrollbar appears.
cm.setValue(doc + "\n" + doc + "\n");
cm.getScrollerElement().style.maxHeight = "100px";
cm.replaceRange("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n!\n", {line: 2, ch: 0});
forEach([{line: 0, ch: doc.length}, {line: 0, ch: doc.length - 1},
{line: 0, ch: 0}, {line: 1, ch: doc.length}, {line: 1, ch: doc.length - 1}],
function(pos) {
var coords = cm.charCoords(pos);
eqPos(pos, cm.coordsChar({x: coords.x + 2, y: coords.y + 2}));
});
});
2 changes: 1 addition & 1 deletion theme/vibrant-ink.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Taken from the popular Visual Studio Vibrant Ink Schema */
/* Taken from the popular Visual Studio Vibrant Ink Schema */

.cm-s-vibrant-ink { background: black; color: white; }
.cm-s-vibrant-ink .CodeMirror-selected { background: #35493c !important; }
Expand Down