version - 2/3 - The version of Python to recognize. Default is 2.
singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.
+
hangingIndent - int - If you want to write long arguments to a function starting on a new line, how much that line should be indented. Defaults to one normal indentation unit.
Advanced Configuration Options:
Usefull for superset of python syntax like Enthought enaml, IPython magics and questionmark help
diff --git a/mode/python/python.js b/mode/python/python.js
index 802c2dd4ac..8bea5d19d1 100644
--- a/mode/python/python.js
+++ b/mode/python/python.js
@@ -11,6 +11,7 @@ CodeMirror.defineMode("python", function(conf, parserConf) {
var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
+ var hangingIndent = parserConf.hangingIndent || parserConf.indentUnit;
var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
@@ -211,6 +212,11 @@ CodeMirror.defineMode("python", function(conf, parserConf) {
break;
}
}
+ } else if (stream.match(/\s*($|#)/, false)) {
+ // An open paren/bracket/brace with only space or comments after it
+ // on the line will indent the next line a fixed amount, to make it
+ // easier to put arguments, list items, etc. on their own lines.
+ indentUnit = stream.indentation() + hangingIndent;
} else {
indentUnit = stream.column() + stream.current().length;
}
diff --git a/mode/r/r.js b/mode/r/r.js
index 6410efbb22..690cf40502 100644
--- a/mode/r/r.js
+++ b/mode/r/r.js
@@ -36,7 +36,11 @@ CodeMirror.defineMode("r", function(config) {
var word = stream.current();
if (atoms.propertyIsEnumerable(word)) return "atom";
if (keywords.propertyIsEnumerable(word)) {
- if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block";
+ // Block keywords start new blocks, except 'else if', which only starts
+ // one new block for the 'if', no block for the 'else'.
+ if (blockkeywords.propertyIsEnumerable(word) &&
+ !stream.match(/\s*if(\s+|$)/, false))
+ curPunc = "block";
return "keyword";
}
if (builtins.propertyIsEnumerable(word)) return "builtin";
diff --git a/mode/rpm/spec/spec.js b/mode/rpm/spec/spec.js
index 9f339c21b1..0fab6c4891 100644
--- a/mode/rpm/spec/spec.js
+++ b/mode/rpm/spec/spec.js
@@ -4,7 +4,7 @@ CodeMirror.defineMode("spec", function() {
var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/;
var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/;
- var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preun|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/;
+ var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/;
var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros
var control_flow_simple = /^%(else|endif)/; // rpm control flow macros
var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros
diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js
index 96cdd5f9dd..1cdc9a4e43 100644
--- a/mode/ruby/ruby.js
+++ b/mode/ruby/ruby.js
@@ -12,7 +12,7 @@ CodeMirror.defineMode("ruby", function(config) {
"caller", "lambda", "proc", "public", "protected", "private", "require", "load",
"require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
]);
- var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then",
+ var indentWords = wordObj(["def", "class", "case", "for", "while", "module", "then",
"catch", "loop", "proc", "begin"]);
var dedentWords = wordObj(["end", "until"]);
var matching = {"[": "]", "{": "}", "(": ")"};
@@ -214,6 +214,8 @@ CodeMirror.defineMode("ruby", function(config) {
else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
kwtype = "indent";
+ else if (word == "do" && state.context.indented < state.indented)
+ kwtype = "indent";
}
if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style;
if (curPunc == "|") state.varList = !state.varList;
diff --git a/mode/smalltalk/smalltalk.js b/mode/smalltalk/smalltalk.js
index f2d4cb3185..86f749b310 100644
--- a/mode/smalltalk/smalltalk.js
+++ b/mode/smalltalk/smalltalk.js
@@ -40,8 +40,10 @@ CodeMirror.defineMode('smalltalk', function(config) {
stream.next();
token = nextSymbol(stream, new Context(nextSymbol, context));
} else {
- stream.eatWhile(/[^ .\[\]()]/);
- token.name = 'string-2';
+ if (stream.eatWhile(/[^ .{}\[\]()]/))
+ token.name = 'string-2';
+ else
+ token.name = 'meta';
}
} else if (aChar === '$') {
diff --git a/mode/smartymixed/smartymixed.js b/mode/smartymixed/smartymixed.js
old mode 100755
new mode 100644
index a033ab04fa..48dd1a6752
--- a/mode/smartymixed/smartymixed.js
+++ b/mode/smartymixed/smartymixed.js
@@ -159,8 +159,6 @@ CodeMirror.defineMode("smartymixed", function(config) {
return htmlMixedMode.indent(state.htmlMixedState, textAfter);
},
- electricChars: "/{}:",
-
innerMode: function(state) {
return {
state: state.localState || state.htmlMixedState,
diff --git a/mode/sql/sql.js b/mode/sql/sql.js
index 3be68caa1a..903fa6740a 100644
--- a/mode/sql/sql.js
+++ b/mode/sql/sql.js
@@ -177,9 +177,10 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
indent: function(state, textAfter) {
var cx = state.context;
- if (!cx) return CodeMirror.Pass;
- if (cx.align) return cx.col + (textAfter.charAt(0) == cx.type ? 0 : 1);
- else return cx.indent + config.indentUnit;
+ if (!cx) return 0;
+ var closing = textAfter.charAt(0) == cx.type;
+ if (cx.align) return cx.col + (closing ? 0 : 1);
+ else return cx.indent + (closing ? 0 : config.indentUnit);
},
blockCommentStart: "/*",
@@ -326,7 +327,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
name: "sql",
client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"),
keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"),
- builtin: set("bfile blob character clob dec float int integer mlslabel natural naturaln nchar nclob number numeric nvarchar2 real rowtype signtype smallint string varchar varchar2 abs acos add_months ascii asin atan atan2 average bfilename ceil chartorowid chr concat convert cos cosh count decode deref dual dump dup_val_on_index empty error exp false floor found glb greatest hextoraw initcap instr instrb isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mod months_between new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null nvl others power rawtohex reftohex round rowcount rowidtochar rpad rtrim sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv variance vsize"),
+ builtin: set("abs acos add_months ascii asin atan atan2 average bfile bfilename bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv varchar varchar2 variance varying vsize xml"),
operatorChars: /^[*+\-%<>!=~]/,
dateSQL: set("date time timestamp"),
support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber")
diff --git a/mode/xml/xml.js b/mode/xml/xml.js
index 4f49e07faf..96b51ff970 100644
--- a/mode/xml/xml.js
+++ b/mode/xml/xml.js
@@ -45,7 +45,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
- var tagName, type;
+ var tagName, type, setStyle;
function inText(stream, state) {
function chain(parser) {
@@ -110,6 +110,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
return null;
} else if (ch == "<") {
state.tokenize = inText;
+ state.state = baseState;
+ state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " error" : "error";
} else if (/[\'\"]/.test(ch)) {
@@ -169,139 +171,124 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
};
}
- var curState, curStream, setStyle;
- function pass() {
- for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
+ function Context(state, tagName, startOfLine) {
+ this.prev = state.context;
+ this.tagName = tagName;
+ this.indent = state.indented;
+ this.startOfLine = startOfLine;
+ if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
+ this.noIndent = true;
}
- function cont() {
- pass.apply(null, arguments);
- return true;
+ function popContext(state) {
+ if (state.context) state.context = state.context.prev;
}
-
- function pushContext(tagName, startOfLine) {
- var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
- curState.context = {
- prev: curState.context,
- tagName: tagName,
- indent: curState.indented,
- startOfLine: startOfLine,
- noIndent: noIndent
- };
- }
- function popContext() {
- if (curState.context) curState.context = curState.context.prev;
+ function maybePopContext(state, nextTagName) {
+ var parentTagName;
+ while (true) {
+ if (!state.context) {
+ return;
+ }
+ parentTagName = state.context.tagName.toLowerCase();
+ if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
+ !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
+ return;
+ }
+ popContext(state);
+ }
}
- function element(type) {
+ function baseState(type, stream, state) {
if (type == "openTag") {
- curState.tagName = tagName;
- curState.tagStart = curStream.column();
- return cont(attributes, endtag(curState.startOfLine));
+ state.tagName = tagName;
+ state.tagStart = stream.column();
+ return attrState;
} else if (type == "closeTag") {
var err = false;
- if (curState.context) {
- if (curState.context.tagName != tagName) {
- if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
- popContext();
- }
- err = !curState.context || curState.context.tagName != tagName;
+ if (state.context) {
+ if (state.context.tagName != tagName) {
+ if (Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName.toLowerCase()))
+ popContext(state);
+ err = !state.context || state.context.tagName != tagName;
}
} else {
err = true;
}
if (err) setStyle = "error";
- return cont(endclosetag(err));
+ return err ? closeStateErr : closeState;
+ } else {
+ return baseState;
}
- return cont();
- }
- function endtag(startOfLine) {
- return function(type) {
- var tagName = curState.tagName;
- curState.tagName = curState.tagStart = null;
- if (type == "selfcloseTag" ||
- (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
- maybePopContext(tagName.toLowerCase());
- return cont();
- }
- if (type == "endTag") {
- maybePopContext(tagName.toLowerCase());
- pushContext(tagName, startOfLine);
- return cont();
- }
- return cont();
- };
}
- function endclosetag(err) {
- return function(type) {
- if (err) setStyle = "error";
- if (type == "endTag") { popContext(); return cont(); }
+ function closeState(type, _stream, state) {
+ if (type != "endTag") {
setStyle = "error";
- return cont(arguments.callee);
- };
- }
- function maybePopContext(nextTagName) {
- var parentTagName;
- while (true) {
- if (!curState.context) {
- return;
- }
- parentTagName = curState.context.tagName.toLowerCase();
- if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
- !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
- return;
- }
- popContext();
+ return closeState;
}
+ popContext(state);
+ return baseState;
+ }
+ function closeStateErr(type, stream, state) {
+ setStyle = "error";
+ return closeState(type, stream, state);
}
- function attributes(type) {
- if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
- if (type == "endTag" || type == "selfcloseTag") return pass();
+ function attrState(type, _stream, state) {
+ if (type == "word") {
+ setStyle = "attribute";
+ return attrEqState;
+ } else if (type == "endTag" || type == "selfcloseTag") {
+ var tagName = state.tagName, tagStart = state.tagStart;
+ state.tagName = state.tagStart = null;
+ if (type == "selfcloseTag" ||
+ Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase())) {
+ maybePopContext(state, tagName.toLowerCase());
+ } else {
+ maybePopContext(state, tagName.toLowerCase());
+ state.context = new Context(state, tagName, tagStart == state.indented);
+ }
+ return baseState;
+ }
setStyle = "error";
- return cont(attributes);
+ return attrState;
}
- function attribute(type) {
- if (type == "equals") return cont(attvalue, attributes);
+ function attrEqState(type, stream, state) {
+ if (type == "equals") return attrValueState;
if (!Kludges.allowMissing) setStyle = "error";
- else if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
- return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
+ return attrState(type, stream, state);
}
- function attvalue(type) {
- if (type == "string") return cont(attvaluemaybe);
- if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
+ function attrValueState(type, stream, state) {
+ if (type == "string") return attrContinuedState;
+ if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;}
setStyle = "error";
- return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
+ return attrState(type, stream, state);
}
- function attvaluemaybe(type) {
- if (type == "string") return cont(attvaluemaybe);
- else return pass();
+ function attrContinuedState(type, stream, state) {
+ if (type == "string") return attrContinuedState;
+ return attrState(type, stream, state);
}
return {
startState: function() {
- return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, tagStart: null, context: null};
+ return {tokenize: inText,
+ state: baseState,
+ indented: 0,
+ tagName: null, tagStart: null,
+ context: null};
},
token: function(stream, state) {
- if (!state.tagName && stream.sol()) {
- state.startOfLine = true;
+ if (!state.tagName && stream.sol())
state.indented = stream.indentation();
- }
- if (stream.eatSpace()) return null;
- setStyle = type = tagName = null;
+ if (stream.eatSpace()) return null;
+ tagName = type = null;
var style = state.tokenize(stream, state);
- state.type = type;
if ((style || type) && style != "comment") {
- curState = state; curStream = stream;
- while (true) {
- var comb = state.cc.pop() || element;
- if (comb(type || style)) break;
- }
+ setStyle = null;
+ state.state = state.state(type || style, stream, state);
+ if (setStyle)
+ style = setStyle == "error" ? style + " error" : setStyle;
}
- state.startOfLine = false;
- if (setStyle)
- style = setStyle == "error" ? style + " error" : setStyle;
return style;
},
@@ -311,8 +298,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
if (state.tokenize.isInAttribute) {
return state.stringStartCol + 1;
}
- if ((state.tokenize != inTag && state.tokenize != inText) ||
- context && context.noIndent)
+ if (context && context.noIndent) return CodeMirror.Pass;
+ if (state.tokenize != inTag && state.tokenize != inText)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
// Indent the starts of attribute names.
if (state.tagName) {
diff --git a/package.json b/package.json
index 6c5a122ea2..326b95890b 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,14 @@
{
"name": "codemirror",
- "version":"3.20.0",
+ "version":"3.21.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
"url": "http://codemirror.net/LICENSE"}],
"directories": {"lib": "./lib"},
"scripts": {"test": "node ./test/run.js"},
- "devDependencies": {"node-static": "0.6.0"},
+ "devDependencies": {"node-static": "0.6.0",
+ "phantomjs": "1.9.2-5"},
"bugs": "http://github.com/marijnh/CodeMirror/issues",
"keywords": ["JavaScript", "CodeMirror", "Editor"],
"homepage": "http://codemirror.net",
@@ -15,5 +16,5 @@
"email": "marijnh@gmail.com",
"web": "http://marijnhaverbeke.nl"}],
"repository": {"type": "git",
- "url": "http://marijnhaverbeke.nl/git/codemirror"}
+ "url": "https://github.com/marijnh/CodeMirror.git"}
}
diff --git a/test/comment_test.js b/test/comment_test.js
index 7ab3127b43..d8ff2c866d 100644
--- a/test/comment_test.js
+++ b/test/comment_test.js
@@ -48,4 +48,16 @@ namespace = "comment_";
cm.setCursor(1);
cm.execCommand("toggleComment");
}, "a;\n\nb;", "a;\n// \nb;");
+
+ test("dontMessWithStrings", "javascript", function(cm) {
+ cm.execCommand("toggleComment");
+ }, "console.log(\"/*string*/\");", "// console.log(\"/*string*/\");");
+
+ test("dontMessWithStrings2", "javascript", function(cm) {
+ cm.execCommand("toggleComment");
+ }, "console.log(\"// string\");", "// console.log(\"// string\");");
+
+ test("dontMessWithStrings3", "javascript", function(cm) {
+ cm.execCommand("toggleComment");
+ }, "// console.log(\"// string\");", "console.log(\"// string\");");
})();
diff --git a/test/doc_test.js b/test/doc_test.js
index 3e04e155b3..f0f40e76e0 100644
--- a/test/doc_test.js
+++ b/test/doc_test.js
@@ -57,7 +57,7 @@
run.apply(null, editors);
successful = true;
} finally {
- if ((debug && !successful) || verbose) {
+ if (!successful || verbose) {
place.style.visibility = "visible";
} else {
for (var i = 0; i < editors.length; ++i)
diff --git a/test/driver.js b/test/driver.js
index 5befa77278..13952dcc65 100644
--- a/test/driver.js
+++ b/test/driver.js
@@ -1,4 +1,4 @@
-var tests = [], debug = null, debugUsed = new Array(), allNames = [];
+var tests = [], filters = [], allNames = [];
function Failure(why) {this.message = why;}
Failure.prototype.toString = function() { return this.message; };
@@ -32,7 +32,7 @@ function testCM(name, run, opts, expectedFail) {
run(cm);
successful = true;
} finally {
- if ((debug && !successful) || verbose) {
+ if (!successful || verbose) {
place.style.visibility = "visible";
} else {
place.removeChild(cm.getWrapperElement());
@@ -42,39 +42,23 @@ function testCM(name, run, opts, expectedFail) {
}
function runTests(callback) {
- if (debug) {
- if (indexOf(debug, "verbose") === 0) {
- verbose = true;
- debug.splice(0, 1);
- }
- if (debug.length < 1) {
- debug = null;
- }
- }
var totalTime = 0;
function step(i) {
if (i === tests.length){
running = false;
return callback("done");
- }
+ }
var test = tests[i], expFail = test.expectedFail, startTime = +new Date;
- if (debug !== null) {
- var debugIndex = indexOf(debug, test.name);
- if (debugIndex !== -1) {
- // Remove from array for reporting incorrect tests later
- debug.splice(debugIndex, 1);
- } else {
- var wildcardName = test.name.split("_")[0] + "_*";
- debugIndex = indexOf(debug, wildcardName);
- if (debugIndex !== -1) {
- // Remove from array for reporting incorrect tests later
- debug.splice(debugIndex, 1);
- debugUsed.push(wildcardName);
- } else {
- debugIndex = indexOf(debugUsed, wildcardName);
- if (debugIndex == -1) return step(i + 1);
+ if (filters.length) {
+ for (var j = 0; j < filters.length; j++) {
+ if (test.name.match(filters[j])) {
+ break;
}
}
+ if (j == filters.length) {
+ callback("skipped", test.name, message);
+ return step(i + 1);
+ }
}
var threw = false;
try {
@@ -127,13 +111,21 @@ function is(a, msg) {
}
function countTests() {
- if (!debug) return tests.length;
+ if (!filters.length) return tests.length;
var sum = 0;
for (var i = 0; i < tests.length; ++i) {
var name = tests[i].name;
- if (indexOf(debug, name) != -1 ||
- indexOf(debug, name.split("_")[0] + "_*") != -1)
- ++sum;
+ for (var j = 0; j < filters.length; j++) {
+ if (name.match(filters[j])) {
+ ++sum;
+ break;
+ }
+ }
}
return sum;
}
+
+function parseTestFilter(s) {
+ if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i");
+ else return new RegExp(s, "i");
+}
diff --git a/test/emacs_test.js b/test/emacs_test.js
index ab18241dee..f21605bb37 100644
--- a/test/emacs_test.js
+++ b/test/emacs_test.js
@@ -125,6 +125,9 @@
sim("transposeExpr", "do foo[bar] dah",
Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah"));
+ sim("clearMark", "abcde", Pos(0, 2), "Ctrl-Space", "Ctrl-F", "Ctrl-F",
+ "Ctrl-G", "Ctrl-W", txt("abcde"));
+
testCM("save", function(cm) {
var saved = false;
CodeMirror.commands.save = function(cm) { saved = cm.getValue(); };
diff --git a/test/index.html b/test/index.html
index 8e45b6d075..8177038e30 100644
--- a/test/index.html
+++ b/test/index.html
@@ -72,11 +72,13 @@
+
+
@@ -107,10 +109,11 @@
progressTotal = document.getElementById("progress_total").childNodes[0];
var count = 0,
failed = 0,
+ skipped = 0,
bad = "",
running = false, // Flag that states tests are running
- quit = false, // Flag to quit tests ASAP
- verbose = false; // Adds message for *every* test to output
+ quit = false, // Flag to quit tests ASAP
+ verbose = false; // Adds message for *every* test to output
function runHarness(){
if (running) {
@@ -119,19 +122,25 @@
setTimeout(function(){runHarness();}, 500);
return;
}
+ filters = [];
+ verbose = false;
if (window.location.hash.substr(1)){
- debug = window.location.hash.substr(1).split(",");
- } else {
- debug = null;
+ var strings = window.location.hash.substr(1).split(",");
+ while (strings.length) {
+ var s = strings.shift();
+ if (s === "verbose")
+ verbose = true;
+ else
+ filters.push(parseTestFilter(decodeURIComponent(s)));;
+ }
}
quit = false;
running = true;
setStatus("Loading tests...");
count = 0;
failed = 0;
+ skipped = 0;
bad = "";
- verbose = false;
- debugUsed = Array();
totalTests = countTests();
progressTotal.nodeValue = " of " + totalTests;
progressRan.nodeValue = count;
@@ -164,12 +173,16 @@
}
function displayTest(type, name, customMessage) {
var message = "???";
- if (type != "done") ++count;
+ if (type != "done" && type != "skipped") ++count;
progress.style.width = (count * (progress.parentNode.clientWidth - 2) / totalTests) + "px";
progressRan.nodeValue = count;
if (type == "ok") {
message = "Test '" + name + "' succeeded";
if (!verbose) customMessage = false;
+ } else if (type == "skipped") {
+ message = "Test '" + name + "' skipped";
+ ++skipped;
+ if (!verbose) customMessage = false;
} else if (type == "expected") {
message = "Test '" + name + "' failed as expected";
if (!verbose) customMessage = false;
@@ -187,15 +200,11 @@
} else {
type += " ok";
message = "All passed";
+ if (skipped) {
+ message += " (" + skipped + " skipped)";
+ }
}
- if (debug && debug.length) {
- var bogusTests = totalTests - count;
- message += " — " + bogusTests + " nonexistent test" +
- (bogusTests > 1 ? "s" : "") + " requested by location.hash: " +
- "`" + debug.join("`, `") + "`";
- } else {
- progressTotal.nodeValue = '';
- }
+ progressTotal.nodeValue = '';
customMessage = true; // Hack to avoid adding to output
}
if (verbose && !customMessage) customMessage = message;
diff --git a/test/mode_test.js b/test/mode_test.js
index 4b63cf517c..46174e1f78 100644
--- a/test/mode_test.js
+++ b/test/mode_test.js
@@ -59,13 +59,6 @@
return {tokens: tokens, plain: plain};
}
- test.indentation = function(name, mode, tokens, modeName) {
- var data = parseTokens(tokens);
- return test((modeName || mode.name) + "_indent_" + name, function() {
- return compare(data.plain, data.tokens, mode, true);
- });
- };
-
test.mode = function(name, mode, tokens, modeName) {
var data = parseTokens(tokens);
return test((modeName || mode.name) + "_" + name, function() {
@@ -73,7 +66,11 @@
});
};
- function compare(text, expected, mode, compareIndentation) {
+ function esc(str) {
+ return str.replace('&', '&').replace('<', '<');
+ }
+
+ function compare(text, expected, mode) {
var expectedOutput = [];
for (var i = 0; i < expected.length; i += 2) {
@@ -82,61 +79,49 @@
expectedOutput.push(sty, expected[i + 1]);
}
- var observedOutput = highlight(text, mode, compareIndentation);
-
- var pass, passStyle = "";
- pass = highlightOutputsEqual(expectedOutput, observedOutput);
- passStyle = pass ? 'mt-pass' : 'mt-fail';
+ var observedOutput = highlight(text, mode);
- var s = '';
- if (pass) {
- s += '
';
- s += '
' + text.replace('&', '&').replace('<', '<') + '
';
- s += '
';
- s += prettyPrintOutputTable(observedOutput);
- s += '
';
- s += '
';
- return s;
- } else {
- s += '
';
- s += '
' + text.replace('&', '&').replace('<', '<') + '
';
+ var s = "";
+ var diff = highlightOutputsDifferent(expectedOutput, observedOutput);
+ if (diff != null) {
+ s += '
';
+ s += '
' + esc(text) + '
';
s += '
';
s += 'expected:';
- s += prettyPrintOutputTable(expectedOutput);
+ s += prettyPrintOutputTable(expectedOutput, diff);
s += 'observed:';
- s += prettyPrintOutputTable(observedOutput);
+ s += prettyPrintOutputTable(observedOutput, diff);
s += '
';
s += '
';
- throw s;
}
+ if (observedOutput.indentFailures) {
+ for (var i = 0; i < observedOutput.indentFailures.length; i++)
+ s += "
" + esc(observedOutput.indentFailures[i]) + "
";
+ }
+ if (s) throw new Failure(s);
}
- /**
- * Emulation of CodeMirror's internal highlight routine for testing. Multi-line
- * input is supported.
- *
- * @param string to highlight
- *
- * @param mode the mode that will do the actual highlighting
- *
- * @return array of [style, token] pairs
- */
- function highlight(string, mode, compareIndentation) {
+ function highlight(string, mode) {
var state = mode.startState()
var lines = string.replace(/\r\n/g,'\n').split('\n');
var st = [], pos = 0;
for (var i = 0; i < lines.length; ++i) {
var line = lines[i], newLine = true;
+ if (mode.indent) {
+ var ws = line.match(/^\s*/)[0];
+ var indent = mode.indent(state, line.slice(ws.length));
+ if (indent != CodeMirror.Pass && indent != ws.length)
+ (st.indentFailures || (st.indentFailures = [])).push(
+ "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")");
+ }
var stream = new CodeMirror.StringStream(line);
if (line == "" && mode.blankLine) mode.blankLine(state);
/* Start copied code from CodeMirror.highlight */
while (!stream.eol()) {
- var compare = mode.token(stream, state), substr = stream.current();
- if(compareIndentation) compare = mode.indent(state) || null;
- else if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');
-
- stream.start = stream.pos;
+ var compare = mode.token(stream, state), substr = stream.current();
+ if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');
+ stream.start = stream.pos;
if (pos && st[pos-2] == compare && !newLine) {
st[pos-1] += substr;
} else if (substr) {
@@ -154,39 +139,22 @@
return st;
}
- /**
- * Compare two arrays of output from highlight.
- *
- * @param o1 array of [style, token] pairs
- *
- * @param o2 array of [style, token] pairs
- *
- * @return boolean; true iff outputs equal
- */
- function highlightOutputsEqual(o1, o2) {
- if (o1.length != o2.length) return false;
- for (var i = 0; i < o1.length; ++i)
- if (o1[i] != o2[i]) return false;
- return true;
+ function highlightOutputsDifferent(o1, o2) {
+ var minLen = Math.min(o1.length, o2.length);
+ for (var i = 0; i < minLen; ++i)
+ if (o1[i] != o2[i]) return i >> 1;
+ if (o1.length > minLen || o2.length > minLen) return minLen;
}
- /**
- * Print tokens and corresponding styles in a table. Spaces in the token are
- * replaced with 'interpunct' dots (·).
- *
- * @param output array of [style, token] pairs
- *
- * @return html string
- */
- function prettyPrintOutputTable(output) {
+ function prettyPrintOutputTable(output, diffAt) {
var s = '
';
s += '
';
for (var i = 0; i < output.length; i += 2) {
var style = output[i], val = output[i+1];
s +=
- '