@@ -0,0 +1,156 @@
/*
* See LICENSE in this directory for the license under which this code
* is released.
*/

CodeMirror.defineMode("sieve", function(config) {
function words(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}

var keywords = words("if elsif else stop require");
var atoms = words("true false not");
var indentUnit = config.indentUnit;

function tokenBase(stream, state) {

var ch = stream.next();
if (ch == "/" && stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}

if (ch === '#') {
stream.skipToEnd();
return "comment";
}

if (ch == "\"") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}

if (ch === "{")
{
state._indent++;
return null;
}

if (ch === "}")
{
state._indent--;
return null;
}

if (/[{}\(\),;]/.test(ch))
return null;

// 1*DIGIT "K" / "M" / "G"
if (/\d/.test(ch)) {
stream.eatWhile(/[\d]/);
stream.eat(/[KkMmGg]/);
return "number";
}

// ":" (ALPHA / "_") *(ALPHA / DIGIT / "_")
if (ch == ":") {
stream.eatWhile(/[a-zA-Z_]/);
stream.eatWhile(/[a-zA-Z0-9_]/);

return "operator";
}

stream.eatWhile(/[\w\$_]/);
var cur = stream.current();

// "text:" *(SP / HTAB) (hash-comment / CRLF)
// *(multiline-literal / multiline-dotstart)
// "." CRLF
if ((cur == "text") && stream.eat(":"))
{
state.tokenize = tokenMultiLineString;
return "string";
}

if (keywords.propertyIsEnumerable(cur))
return "keyword";

if (atoms.propertyIsEnumerable(cur))
return "atom";
}

function tokenMultiLineString(stream, state)
{
state._multiLineString = true;
// the first line is special it may contain a comment
if (!stream.sol()) {
stream.eatSpace();

if (stream.peek() == "#") {
stream.skipToEnd();
return "comment";
}

stream.skipToEnd();
return "string";
}

if ((stream.next() == ".") && (stream.eol()))
{
state._multiLineString = false;
state.tokenize = tokenBase;
}

return "string";
}

function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch == "/") {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return "comment";
}

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

return {
startState: function(base) {
return {tokenize: tokenBase,
baseIndent: base || 0,
_indent: 0};
},

token: function(stream, state) {
if (stream.eatSpace())
return null;

return (state.tokenize || tokenBase)(stream, state);;
},

indent: function(state, textAfter) {
return state.baseIndent + state._indent * indentUnit;
},

electricChars: "}"
};
});

CodeMirror.defineMIME("application/sieve", "sieve");
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Smalltalk mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -63,7 +63,7 @@ CodeMirror.defineMode('smalltalk', function(config, modeConfig) {

} else if (/\d/.test(aChar)) {
stream.eatWhile(/[\w\d]/);
token.name = 'number'
token.name = 'number';

} else if (/[\w_]/.test(aChar)) {
stream.eatWhile(/[\w\d_]/);
@@ -100,7 +100,7 @@ CodeMirror.defineMode('smalltalk', function(config, modeConfig) {
}

return token;
}
};

return {
startState: function() {
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Smarty mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -5,7 +5,7 @@ CodeMirror.defineMode("smarty", function(config, parserConfig) {
operatorChars: /[+\-*&%=<>!?]/,
validIdentifier: /[a-zA-Z0-9\_]/,
stringChar: /[\'\"]/
}
};
var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{";
var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}";
function ret(style, lst) { last = lst; return style; }
@@ -142,7 +142,7 @@ CodeMirror.defineMode("smarty", function(config, parserConfig) {
return style;
},
electricChars: ""
}
};
});

CodeMirror.defineMIME("text/x-smarty", "smarty");
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: SPARQL mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: sTeX mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -54,7 +54,7 @@ CodeMirror.defineMode("stex", function(cmCfg, modeCfg)
};
this.closeBracket = function(content) {
};
}
};
}

var plugins = new Array();
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: sTeX mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: TiddlyWiki mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -1,5 +1,6 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<title>CodeMirror: Tiki wiki mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -18,7 +18,7 @@ CodeMirror.defineMode('tiki', function(config, parserConfig) {
function inLine(style, terminator) {
return function(stream, state) {
while(!stream.eol()) {
stream.next()
stream.next();
}
state.tokenize = inText;
return style;
@@ -37,9 +37,9 @@ CodeMirror.defineMode('tiki', function(config, parserConfig) {
//non start of line
switch (ch) { //switch is generally much faster than if, so it is used here
case "{": //plugin
type = stream.eat("/") ? "closeTag" : "openTag";
var type = stream.eat("/") ? "closeTag" : "openTag";
stream.eatSpace();
tagName = "";
var tagName = "";
var c;
while ((c = stream.eat(/[^\s\u00a0=\"\'\/?(}]/))) tagName += c;
state.tokenize = inPlugin;
@@ -251,7 +251,7 @@ CodeMirror.defineMode('tiki', function(config, parserConfig) {
if (err) setStyle = "error";
if (type == "endPlugin") return cont();
return pass();
}
};
}

function attributes(type) {
@@ -1,5 +1,6 @@
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: VB.NET mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: VBScript mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Velocity mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -5,7 +5,7 @@ CodeMirror.defineMode("velocity", function(config) {
return obj;
}

var indentUnit = config.indentUnit
var indentUnit = config.indentUnit;
var keywords = parseWords("#end #else #break #stop #[[ #]] " +
"#{end} #{else} #{break} #{stop}");
var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " +
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Verilog mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -21,7 +21,7 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) {
}
if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
curPunc = ch;
return null
return null;
}
if (/[\d']/.test(ch)) {
stream.eatWhile(/[\w\.']/);
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: XML mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -30,7 +30,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: false
allowMissing: true
} : {
autoSelfClosers: {},
implicitlyClosed: {},
@@ -229,7 +229,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
if (type == "endTag") { popContext(); return cont(); }
setStyle = "error";
return cont(arguments.callee);
}
};
}
function maybePopContext(nextTagName) {
var parentTagName;
@@ -25,7 +25,8 @@
*/
-->
<head>
<title>CodeMirror 2: JavaScript mode</title>
<meta charset="utf-8">
<title>CodeMirror: XQuery mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="http://codemirror.net/lib/codemirror.js"></script>
<script src="xquery.js"></script>
@@ -41,7 +42,7 @@
</style>
</head>
<body>
<h1>CodeMirror 2: XQuery mode</h1>
<h1>CodeMirror: XQuery mode</h1>

<div class="cm-s-default">
<textarea id="code" name="code">
@@ -56,7 +56,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {
'preceding-sibling','processing-instruction','ref','return','returns','satisfies','schema','schema-element',
'self','some','sortby','stable','text','then','to','treat','typeswitch','union','variable','version','where',
'xquery', 'empty-sequence'];
for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i])};
for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);};

// a list of types. For each add a property to kwObj with the value of
// {type: "atom", style: "atom"}
@@ -191,7 +191,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {
if(!known) stream.eatWhile(/[\w\$_-]/);

// gobble a colon in the case that is a lib func type call fn:doc
var foundColon = stream.eat(":")
var foundColon = stream.eat(":");

// if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier
// which should get matched as a keyword
@@ -325,7 +325,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {
state.tokenize = tokenBase;
}
return ret("tag", "tag");
}
};
}

// tokenizer for XML attributes
@@ -365,6 +365,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {

// handle comments, including nested
function tokenXMLComment(stream, state) {
var ch;
while (ch = stream.next()) {
if (ch == "-" && stream.match("->", true)) {
state.tokenize = tokenBase;
@@ -376,6 +377,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {

// handle CDATA
function tokenCDATA(stream, state) {
var ch;
while (ch = stream.next()) {
if (ch == "]" && stream.match("]", true)) {
state.tokenize = tokenBase;
@@ -386,6 +388,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {

// handle preprocessing instructions
function tokenPreProcessing(stream, state) {
var ch;
while (ch = stream.next()) {
if (ch == "?" && stream.match(">", true)) {
state.tokenize = tokenBase;
@@ -422,7 +425,7 @@ CodeMirror.defineMode("xquery", function(config, parserConfig) {

function popStateStack(state) {
var popped = state.stack.pop();
var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize
var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize;
state.tokenize = reinstateTokenize || tokenBase;
}

@@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: YAML mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
@@ -1,29 +1,21 @@
{
"name": "CodeMirror",
"version":"2.32.0",
"version":"2.33.0",
"main": "codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [
{
"type": "MIT",
"url": "http://codemirror.net/LICENSE"
}
],
"directories": {
"lib": "./lib"
},
"bugs": "http://github.com/marijnh/CodeMirror2/issues",
"licenses": [{"type": "MIT",
"url": "http://codemirror.net/LICENSE"}],
"directories": {"lib": "./lib"},
"scripts": {"test": "node ./test/run.js"},
"devDependencies": {"node-static": "0.6.0"},
"bugs": "http://github.com/marijnh/CodeMirror/issues",
"keywords": ["JavaScript", "CodeMirror", "Editor"],
"homepage": "http://codemirror.net",
"maintainers":[ {
"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"web": "http://codemirror.net"
}],
"repositories": [
{
"type": "git",
"url": "https://github.com/marijnh/CodeMirror2.git"
}
]
"maintainers":[{"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"web": "http://marijnhaverbeke.nl"}],
"repositories": [{"type": "git",
"url": "http://marijnhaverbeke.nl/git/codemirror"},
{"type": "git",
"url": "https://github.com/marijnh/CodeMirror.git"}]
}
@@ -1,42 +1,53 @@
var tests = [], runOnly = null;
var tests = [], debug = null;

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

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

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());
var test = tests[i], expFail = test.expectedFail;
if (debug != null && debug != test.name) return step(i + 1);
try {
test.func();
if (expFail) callback("fail", test.name, "was expected to fail, but succeeded");
else callback("ok", test.name);
} 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());
}
setTimeout(function(){step(i + 1);}, 20);
}
step(0);
}

function label(str, msg) {
if (msg) return str + " (" + msg + ")";
return str;
}
function eq(a, b, msg) {
if (a != b) throw new Failure(a + " != " + b + (msg ? " (" + msg + ")" : ""));
if (a != b) throw new Failure(label(a + " != " + b, msg));
}
function eqPos(a, b, msg) {
function str(p) { return "{line:" + p.line + ",ch:" + p.ch + "}"; }
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);
if (a == null) throw new Failure(label("comparing null to " + str(b)));
if (b == null) throw new Failure(label("comparing " + str(a) + " to null"));
if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg));
}
function is(a, msg) {
if (!a) throw new Failure("assertion failed" + (msg ? " (" + msg + ")" : ""));
if (!a) throw new Failure(label("assertion failed", msg));
}
@@ -5,6 +5,7 @@
<link rel="stylesheet" href="../lib/codemirror.css">
<script src="../lib/codemirror.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/xml/xml.js"></script>

<style type="text/css">
.ok {color: #090;}
@@ -33,17 +34,23 @@ <h1>CodeMirror: Test Suite</h1>
runTests(displayTest);
};

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) {
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>";
output.innerHTML = bad + "<span class=ok>Test '" + esc(name) + "' succeeded</span>";
} else if (type == "expected") {
output.innerHTML = bad + "<span class=ok>Test '" + esc(name) + "' failed as expected</span>";
} else if (type == "error" || type == "fail") {
++failed;
bad += CodeMirror.htmlEscape(name) + ": <span class=" + type + ">" + CodeMirror.htmlEscape(msg) + "</span>\n";
bad += esc(name) + ": <span class=" + type + ">" + esc(msg) + "</span><br>";
output.innerHTML = bad;
} else if (type == "done") {
output.innerHTML = bad + (failed ? "<span class=fail>" + failed + " failure" + (failed > 1 ? "s" : "") + "</span>"
@@ -0,0 +1,120 @@
/*
Simple linter, based on UglifyJS's [1] parse-js module
All of the existing linters either cramp my style or have huge
dependencies (Closure). So here's a very simple, non-invasive one
that only spots
- missing semicolons and trailing commas
- variables or properties that are reserved words
- assigning to a variable you didn't declare
[1]: https://github.com/mishoo/UglifyJS/
*/

var fs = require("fs"), parse_js = require("./parse-js").parse;

var reserved = {};
"break case catch continue debugger default delete do else false finally for function if in\
instanceof new null return switch throw true try typeof var void while with abstract enum\
int short boolean export interface static byte extends long super char final native\
synchonized class float package throws const goto private transient implements protected\
volatile double import public const".split(" ").forEach(function(word) { reserved[word] = true; });

function checkVariable(scope, name, pos) {
while (scope) {
if (scope.cur.hasOwnProperty(name)) return;
scope = scope.prev;
}
fail("Accidental global: " + name, pos);
}
function checkProperty(name, pos) {
if (reserved.hasOwnProperty(name)) {
fail("Using a keyword or reserved word as a property: " + name, pos);
}
}

function walk(ast, scope) {
var tp = ast[0];
if (typeof tp != "string") tp = tp.name;
function sub(ast) { if (ast) walk(ast, scope); }
function subn(array) { if (array) array.forEach(sub); }
if (tp == "block" || tp == "splice" || tp == "toplevel" || tp == "array") {
subn(ast[1]);
} else if (tp == "var" || tp == "const") {
ast[1].forEach(function(def) { scope.cur[def[0]] = true; if (def[1]) sub(def[1]); });
} else if (tp == "try") {
subn(ast[1]);
if (ast[2]) { scope.cur[ast[2][0]] = true; subn(ast[2][1]); }
subn(ast[3]);
} else if (tp == "throw" || tp == "return" || tp == "dot" || tp == "stat") {
sub(ast[1]);
} else if (tp == "dot") {
sub(ast[1]);
checkProperty(ast[2], ast[0]);
} else if (tp == "new" || tp == "call") {
sub(ast[1]); subn(ast[2]);
} else if (tp == "switch") {
sub(ast[1]);
ast[2].forEach(function(part) { sub(part[0]); subn(part[1]); });
} else if (tp == "conditional" || tp == "if" || tp == "for" || tp == "for-in") {
sub(ast[1]); sub(ast[2]); sub(ast[3]); sub(ast[4]);
} else if (tp == "assign") {
if (ast[2][0].name == "name") checkVariable(scope, ast[2][1], ast[2][0]);
sub(ast[2]); sub(ast[3]);
} else if (tp == "function" || tp == "defun") {
if (tp == "defun") scope.cur[ast[1]] = true;
var nscope = {prev: scope, cur: {}};
ast[2].forEach(function(arg) { nscope.cur[arg] = true; });
ast[3].forEach(function(ast) { walk(ast, nscope); });
} else if (tp == "while" || tp == "do" || tp == "sub" || tp == "with") {
sub(ast[1]); sub(ast[2]);
} else if (tp == "binary" || tp == "unary-prefix" || tp == "unary-postfix" || tp == "label") {
if (/\+\+|--/.test(ast[1]) && ast[2][0].name == "name") checkVariable(scope, ast[2][1], ast[2][0]);
sub(ast[2]); sub(ast[3]);
} else if (tp == "object") {
ast[1].forEach(function(prop) {
if (prop.type != "string") checkProperty(prop[0], ast[0]);
sub(prop[1]); sub(prop[2]);
});
} else if (tp == "seq") {
subn(ast.slice(1));
} else if (tp == "name") {
if (reserved.hasOwnProperty(ast[1]) && !/^(?:null|true|false)$/.test(ast[1]))
fail("Using reserved word as variable name: " + ast[1], ast[0]);
}
}

var failed = false, curFile;
function fail(msg, pos) {
if (typeof pos == "object") pos = pos.start.line + 1;
console.log(curFile + ": " + msg + (typeof pos == "number" ? " (" + pos + ")" : ""));
failed = true;
}

function checkFile(fileName) {
curFile = fileName.match(/[^\/+]*\.js$/)[0];
var file = fs.readFileSync(fileName, "utf8");
var badChar = file.match(/[\x00-\x08\x0b\x0c\x0e-\x19\uFEFF]/);
if (badChar) fail("Undesirable character " + badChar[0].charCodeAt(0) + " at position " + badChar.index);
if (/^#!/.test(file)) file = file.slice(file.indexOf("\n") + 1);
try {
var parsed = parse_js(file, true, true);
} catch(e) {
fail(e.message, e.line);
return;
}
walk(parsed, {prev: null, cur: {}});
}

function checkDir(dir) {
fs.readdirSync(dir).forEach(function(file) {
var fname = dir + "/" + file;
if (/\.js$/.test(file)) checkFile(fname);
else if (fs.lstatSync(fname).isDirectory()) checkDir(fname);
});
}

exports.checkDir = checkDir;
exports.checkFile = checkFile;
exports.success = function() { return !failed; };

Large diffs are not rendered by default.

@@ -0,0 +1,30 @@
var page = require('webpage').create();

page.open("http://localhost:3000/test/index.html", function (status) {
if (status != "success") {
console.log("page couldn't be loaded successfully");
phantom.exit(1);
}
waitFor(function () {
return page.evaluate(function () {
var output = document.getElementById('output');
if (!output) { return false; }
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;
});
console.log(output);
phantom.exit(failed > 0 ? 1 : 0);
});
});

function waitFor (test, cb) {
if (test()) {
cb();
} else {
setTimeout(function () { waitFor(test, cb); }, 250);
}
}
@@ -0,0 +1,32 @@
#!/usr/bin/env node

var lint = require("./lint/lint");

lint.checkDir("mode");
lint.checkDir("lib");

var ok = lint.success();

var files = new (require('node-static').Server)('.');

var server = require('http').createServer(function (req, res) {
req.addListener('end', function () {
files.serve(req, res);
});
}).addListener('error', function (err) {
throw err;
}).listen(3000, function () {
var child_process = require('child_process');
child_process.exec("which phantomjs", function (err) {
if (err) {
console.error("PhantomJS is not installed. Download from http://phantomjs.org");
process.exit(1);
}
var cmd = 'phantomjs test/phantom_driver.js';
child_process.exec(cmd, function (err, stdout) {
server.close();
console.log(stdout);
process.exit(err || !ok ? 1 : 0);
});
});
});
@@ -22,6 +22,8 @@ function byClassName(elt, cls) {
return found;
}

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

test("fromTextArea", function() {
var te = document.getElementById("code");
te.value = "CONTENT";
@@ -100,6 +102,10 @@ testCM("indent", function(cm) {
cm.setOption("indentUnit", 8);
cm.indentLine(1);
eq(cm.getLine(1), "\tblah();");
cm.setOption("indentUnit", 10);
cm.setOption("tabSize", 4);
cm.indentLine(1);
eq(cm.getLine(1), "\t\t blah();");
}, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8});

test("defaults", function() {
@@ -322,7 +328,7 @@ testCM("scrollSnap", function(cm) {
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");
is(info.x == 0 && info.y > info.height - 100, "scrolled clean to bottom");
});

testCM("selectionPos", function(cm) {
@@ -357,7 +363,7 @@ testCM("selectionPos", function(cm) {
}
}
is(sawTop && sawBottom && sawMiddle, "all parts");
});
}, null, ie_lt8);

testCM("restoreHistory", function(cm) {
cm.setValue("abc\ndef");
@@ -464,4 +470,185 @@ testCM("wrappingAndResizing", function(cm) {
var coords = cm.charCoords(pos);
eqPos(pos, cm.coordsChar({x: coords.x + 2, y: coords.y + 2}));
});
}, null, ie_lt8);

testCM("measureEndOfLine", function(cm) {
cm.setSize(null, "auto");
var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild;
var w = 20, lh = inner.offsetHeight;
for (var step = 10;; w += step) {
cm.setSize(w);
if (inner.offsetHeight < 2.5 * lh) {
if (step == 10) { w -= 10; step = 1; }
else { break; }
}
}
cm.setValue(cm.getValue() + "\n\n");
var endPos = cm.charCoords({line: 0, ch: 18}, "local");
is(endPos.y > lh * .8, "not at top");
is(endPos.x > w - 20, "not at right");
endPos = cm.charCoords({line: 0, ch: 18});
eqPos(cm.coordsChar({x: endPos.x, y: endPos.y + 2}), {line: 0, ch: 18});
}, {mode: "text/html", value: "<!-- foo barrr -->", lineWrapping: true}, ie_lt8);

testCM("scrollVerticallyAndHorizontally", function(cm) {
cm.setSize(100, 100);
addDoc(cm, 40, 40);
cm.setCursor(39);
var wrap = cm.getWrapperElement(), bar = byClassName(wrap, "CodeMirror-scrollbar")[0];
is(bar.offsetHeight < wrap.offsetHeight, "vertical scrollbar limited by horizontal one");
var cursorBox = byClassName(wrap, "CodeMirror-cursor")[0].getBoundingClientRect();
var editorBox = wrap.getBoundingClientRect();
is(cursorBox.bottom < editorBox.top + cm.getScrollerElement().clientHeight,
"bottom line visible");
}, {gutter: true});

testCM("moveV stuck", function(cm) {
var lines = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild, h0 = lines.offsetHeight;
var val = "fooooooooooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\n";
cm.setValue(val);
for (var w = 50;; w += 5) {
cm.setSize(w);
if (lines.offsetHeight <= 3 * h0) break;
}
cm.setCursor({line: 0, ch: val.length - 1});
cm.moveV(-1, "line");
eqPos(cm.getCursor(), {line: 0, ch: 26});
}, {lineWrapping: true}, ie_lt8);

testCM("clickTab", function(cm) {
var p0 = cm.charCoords({line: 0, ch: 0}), p1 = cm.charCoords({line: 0, ch: 1});
eqPos(cm.coordsChar({x: p0.x + 5, y: p0.y + 5}), {line: 0, ch: 0});
eqPos(cm.coordsChar({x: p1.x - 5, y: p1.y + 5}), {line: 0, ch: 1});
}, {value: "\t\n\n", lineWrapping: true, tabSize: 8});

testCM("verticalScroll", function(cm) {
cm.setSize(100, 200);
cm.setValue("foo\nbar\nbaz\n");
var sc = cm.getScrollerElement(), baseWidth = sc.scrollWidth;
cm.setLine(0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah");
is(sc.scrollWidth > baseWidth, "scrollbar present");
cm.setLine(0, "foo");
eq(sc.scrollWidth, baseWidth, "scrollbar gone");
cm.setLine(0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah");
cm.setLine(1, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh");
is(sc.scrollWidth > baseWidth, "present again");
var curWidth = sc.scrollWidth;
cm.setLine(0, "foo");
is(sc.scrollWidth < curWidth, "scrollbar smaller");
is(sc.scrollWidth > baseWidth, "but still present");
});

testCM("extraKeys", function(cm) {
var outcome;
function fakeKey(expected, code, props) {
if (typeof code == "string") code = code.charCodeAt(0);
var e = {type: "keydown", keyCode: code, preventDefault: function(){}, stopPropagation: function(){}};
if (props) for (var n in props) e[n] = props[n];
outcome = null;
cm.triggerOnKeyDown(e);
eq(outcome, expected);
}
CodeMirror.commands.testCommand = function() {outcome = "tc";};
CodeMirror.commands.goTestCommand = function() {outcome = "gtc";};
cm.setOption("extraKeys", {"Shift-X": function() {outcome = "sx";},
"X": function() {outcome = "x";},
"Ctrl-Alt-U": function() {outcome = "cau";},
"End": "testCommand",
"Home": "goTestCommand",
"Tab": false});
fakeKey(null, "U");
fakeKey("cau", "U", {ctrlKey: true, altKey: true});
fakeKey(null, "U", {shiftKey: true, ctrlKey: true, altKey: true});
fakeKey("x", "X");
fakeKey("sx", "X", {shiftKey: true});
fakeKey("tc", 35);
fakeKey(null, 35, {shiftKey: true});
fakeKey("gtc", 36);
fakeKey("gtc", 36, {shiftKey: true});
fakeKey(null, 9);
});

testCM("wordMovementCommands", function(cm) {
cm.execCommand("goWordLeft");
eqPos(cm.getCursor(), {line: 0, ch: 0});
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqPos(cm.getCursor(), {line: 0, ch: 7});
cm.execCommand("goWordLeft");
eqPos(cm.getCursor(), {line: 0, ch: 5});
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqPos(cm.getCursor(), {line: 0, ch: 12});
cm.execCommand("goWordLeft");
eqPos(cm.getCursor(), {line: 0, ch: 9});
cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqPos(cm.getCursor(), {line: 1, ch: 1});
cm.execCommand("goWordRight");
eqPos(cm.getCursor(), {line: 1, ch: 9});
cm.execCommand("goWordRight");
eqPos(cm.getCursor(), {line: 1, ch: 13});
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqPos(cm.getCursor(), {line: 2, ch: 0});
}, {value: "this is (the) firstline.\na foo12\u00e9\u00f8\u00d7bar\n"});

testCM("charMovementCommands", function(cm) {
cm.execCommand("goCharLeft"); cm.execCommand("goColumnLeft");
eqPos(cm.getCursor(), {line: 0, ch: 0});
cm.execCommand("goCharRight"); cm.execCommand("goCharRight");
eqPos(cm.getCursor(), {line: 0, ch: 2});
cm.setCursor({line: 1, ch: 0});
cm.execCommand("goColumnLeft");
eqPos(cm.getCursor(), {line: 1, ch: 0});
cm.execCommand("goCharLeft");
eqPos(cm.getCursor(), {line: 0, ch: 5});
cm.execCommand("goColumnRight");
eqPos(cm.getCursor(), {line: 0, ch: 5});
cm.execCommand("goCharRight");
eqPos(cm.getCursor(), {line: 1, ch: 0});
cm.execCommand("goLineEnd");
eqPos(cm.getCursor(), {line: 1, ch: 5});
cm.execCommand("goLineStartSmart");
eqPos(cm.getCursor(), {line: 1, ch: 1});
cm.execCommand("goLineStartSmart");
eqPos(cm.getCursor(), {line: 1, ch: 0});
cm.setCursor({line: 2, ch: 0});
cm.execCommand("goCharRight"); cm.execCommand("goColumnRight");
eqPos(cm.getCursor(), {line: 2, ch: 0});
}, {value: "line1\n ine2\n"});

testCM("verticalMovementCommands", function(cm) {
cm.execCommand("goLineUp");
eqPos(cm.getCursor(), {line: 0, ch: 0});
cm.execCommand("goLineDown");
eqPos(cm.getCursor(), {line: 1, ch: 0});
cm.setCursor({line: 1, ch: 12});
cm.execCommand("goLineDown");
eqPos(cm.getCursor(), {line: 2, ch: 5});
cm.execCommand("goLineDown");
eqPos(cm.getCursor(), {line: 3, ch: 0});
cm.execCommand("goLineUp");
eqPos(cm.getCursor(), {line: 2, ch: 5});
cm.execCommand("goLineUp");
eqPos(cm.getCursor(), {line: 1, ch: 12});
cm.execCommand("goPageDown");
eqPos(cm.getCursor(), {line: 5, ch: 0});
cm.execCommand("goPageDown"); cm.execCommand("goLineDown");
eqPos(cm.getCursor(), {line: 5, ch: 0});
cm.execCommand("goPageUp");
eqPos(cm.getCursor(), {line: 0, ch: 0});
}, {value: "line1\nlong long line2\nline3\n\nline5\n"});

testCM("verticalMovementCommandsWrapping", function(cm) {
cm.setSize(120);
cm.setCursor({line: 0, ch: 5});
cm.execCommand("goLineDown");
eq(cm.getCursor().line, 0);
is(cm.getCursor().ch > 5, "moved beyond wrap");
for (var i = 0; ; ++i) {
is(i < 20, "no endless loop");
cm.execCommand("goLineDown");
var cur = cm.getCursor();
if (cur.line == 1) eq(cur.ch, 5);
if (cur.line == 2) { eq(cur.ch, 1); break; }
}
}, {value: "a very long line that wraps around somehow so that we can test cursor movement\nshortone\nk",
lineWrapping: true});