2,581 changes: 1,595 additions & 986 deletions lib/codemirror.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion mode/coffeescript/coffeescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ CodeMirror.defineMode('coffeescript', function(conf) {

function tokenFactory(delimiter, outclass) {
var singleline = delimiter.length == 1;
return function tokenString(stream, state) {
return function(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"\/\\]/);
if (stream.eat('\\')) {
Expand Down
4 changes: 2 additions & 2 deletions mode/haxe/haxe.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
poplex.lex = true;

function expect(wanted) {
return function expecting(type) {
return function(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
Expand Down Expand Up @@ -330,7 +330,7 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
return function(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
Expand Down
66 changes: 43 additions & 23 deletions mode/htmlmixed/htmlmixed.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
CodeMirror.defineMode("htmlmixed", function(config) {
CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
var jsMode = CodeMirror.getMode(config, "javascript");
var cssMode = CodeMirror.getMode(config, "css");

var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i,
mode: CodeMirror.getMode(config, "javascript")});
if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) {
var conf = scriptTypesConf[i];
scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)});
}
scriptTypes.push({matches: /./,
mode: CodeMirror.getMode(config, "text/plain")});

function html(stream, state) {
var tagName = state.htmlState.tagName;
var style = htmlMode.token(stream, state.htmlState);
if (/(?:^|\s)tag(?:\s|$)/.test(style) && stream.current() == ">" && state.htmlState.context) {
if (/^script$/i.test(state.htmlState.context.tagName)) {
state.token = javascript;
state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
}
else if (/^style$/i.test(state.htmlState.context.tagName)) {
state.token = css;
state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") {
// Script block: mode to change to depends on type attribute
var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i);
scriptType = scriptType ? scriptType[1] : "";
if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1);
for (var i = 0; i < scriptTypes.length; ++i) {
var tp = scriptTypes[i];
if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) {
if (tp.mode) {
state.token = script;
state.localMode = tp.mode;
state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, ""));
}
break;
}
}
} else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") {
state.token = css;
state.localMode = cssMode;
state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
}
return style;
}
Expand All @@ -27,19 +48,19 @@ CodeMirror.defineMode("htmlmixed", function(config) {
}
return style;
}
function javascript(stream, state) {
function script(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
state.localState = null;
state.localState = state.localMode = null;
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*script\s*>/,
jsMode.token(stream, state.localState));
state.localMode.token(stream, state.localState));
}
function css(stream, state) {
if (stream.match(/^<\/\s*style\s*>/i, false)) {
state.token = html;
state.localState = null;
state.localState = state.localMode = null;
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*style\s*>/,
Expand All @@ -49,13 +70,13 @@ CodeMirror.defineMode("htmlmixed", function(config) {
return {
startState: function() {
var state = htmlMode.startState();
return {token: html, localState: null, mode: "html", htmlState: state};
return {token: html, localMode: null, localState: null, htmlState: state};
},

copyState: function(state) {
if (state.localState)
var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
return {token: state.token, localState: local, mode: state.mode,
var local = CodeMirror.copyState(state.localMode, state.localState);
return {token: state.token, localMode: state.localMode, localState: local,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},

Expand All @@ -64,19 +85,18 @@ CodeMirror.defineMode("htmlmixed", function(config) {
},

indent: function(state, textAfter) {
if (state.token == html || /^\s*<\//.test(textAfter))
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter);
else if (state.token == javascript)
return jsMode.indent(state.localState, textAfter);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter);
else
return cssMode.indent(state.localState, textAfter);
return CodeMirror.Pass;
},

electricChars: "/{}:",

innerMode: function(state) {
var mode = state.token == html ? htmlMode : state.token == javascript ? jsMode : cssMode;
return {state: state.localState || state.htmlState, mode: mode};
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
};
}, "xml", "javascript", "css");
Expand Down
23 changes: 22 additions & 1 deletion mode/htmlmixed/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<script src="../xml/xml.js"></script>
<script src="../javascript/javascript.js"></script>
<script src="../css/css.js"></script>
<script src="../vbscript/vbscript.js"></script>
<script src="htmlmixed.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
Expand Down Expand Up @@ -39,11 +40,31 @@ <h1>Mixed HTML Example</h1>
</html>
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "text/html", tabMode: "indent"});
// Define an extended mixed-mode that understands vbscript and
// leaves mustache/handlebars embedded templates in html mode
var mixedMode = {
name: "htmlmixed",
scriptTypes: [{matches: /\/x-handlebars-template|\/x-mustache/i,
mode: null},
{matches: /(text|application)\/(x-)?vb(a|script)/i,
mode: "vbscript"}]
};
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: mixedMode, tabMode: "indent"});
</script>

<p>The HTML mixed mode depends on the XML, JavaScript, and CSS modes.</p>

<p>It takes an optional mode configuration
option, <code>scriptTypes</code>, which can be used to add custom
behavior for specific <code>&lt;script type="..."></code> tags. If
given, it should hold an array of <code>{matches, mode}</code>
objects, where <code>matches</code> is a string or regexp that
matches the script type, and <code>mode</code> is
either <code>null</code>, for script types that should stay in
HTML mode, or a <a href="../../doc/manual.html#option_mode">mode
spec</a> corresponding to the mode that should be used for the
script.</p>

<p><strong>MIME types defined:</strong> <code>text/html</code>
(redefined, only takes effect if you load this parser after the
XML parser).</p>
Expand Down
16 changes: 10 additions & 6 deletions mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
Expand Down Expand Up @@ -242,7 +242,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
poplex.lex = true;

function expect(wanted) {
return function expecting(type) {
return function(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
Expand Down Expand Up @@ -283,8 +283,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}

function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator" && value == "?") return cont(expression, expect(":"), expression);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(maybeoperator);
if (value == "?") return cont(expression, expect(":"), expression);
return cont(expression);
}
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
Expand All @@ -299,6 +302,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
else if (type == "number" || type == "string") cx.marked = type + " property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
Expand All @@ -307,7 +311,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
return function(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
Expand Down
3 changes: 2 additions & 1 deletion mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ CodeMirror.modeInfo = [
{name: 'PHP', mime: 'text/x-php', mode: 'php'},
{name: 'PHP(HTML)', mime: 'application/x-httpd-php', mode: 'php'},
{name: 'Pig', mime: 'text/x-pig', mode: 'pig'},
{name: 'Plain Text', mime: 'text/plain', mode: 'null'},
{name: 'Properties files', mime: 'text/x-properties', mode: 'clike'},
{name: 'Python', mime: 'text/x-python', mode: 'python'},
{name: 'R', mime: 'text/x-rsrc', mode: 'r'},
Expand All @@ -57,7 +58,7 @@ CodeMirror.modeInfo = [
{name: 'PL/SQL', mime: 'text/x-plsql', mode: 'sql'},
{name: 'sTeX', mime: 'text/x-stex', mode: 'stex'},
{name: 'LaTeX', mime: 'text/x-latex', mode: 'stex'},
{name: 'TiddlyWiki ', mime: 'text/x-tiddlywiki', mode: 'tiddlwiki'},
{name: 'TiddlyWiki ', mime: 'text/x-tiddlywiki', mode: 'tiddlywiki'},
{name: 'Tiki wiki', mime: 'text/tiki', mode: 'tiki'},
{name: 'VB.NET', mime: 'text/x-vb', mode: 'vb'},
{name: 'VBScript', mime: 'text/vbscript', mode: 'vbscript'},
Expand Down
2 changes: 1 addition & 1 deletion mode/php/php.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@

innerMode: function(state) { return {state: state.curState, mode: state.curMode}; }
};
}, "htmlmixed");
}, "htmlmixed", "clike");

CodeMirror.defineMIME("application/x-httpd-php", "php");
CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
Expand Down
11 changes: 11 additions & 0 deletions mode/python/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ <h2>Configuration Options:</h2>
<li>version - 2/3 - The version of Python to recognize. Default is 2.</li>
<li>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.</li>
</ul>
<h2>Advanced Configuration Options:</h2>
<p>Usefull for superset of python syntax like Enthought enaml, IPython magics and questionmark help</p>
<ul>
<li>singleOperators - RegEx - Regular Expression for single operator matching, default : <pre>^[\\+\\-\\*/%&amp;|\\^~&lt;&gt;!]</pre></li>
<li>singleDelimiters - RegEx - Regular Expression for single delimiter matching, default : <pre>^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]</pre></li>
<li>doubleOperators - RegEx - Regular Expression for double operators matching, default : <pre>^((==)|(!=)|(&lt;=)|(&gt;=)|(&lt;&gt;)|(&lt;&lt;)|(&gt;&gt;)|(//)|(\\*\\*))</pre></li>
<li>doubleDelimiters - RegEx - Regular Expressoin for double delimiters matching, default : <pre>^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&amp;=)|(\\|=)|(\\^=))</pre></li>
<li>tripleDelimiters - RegEx - Regular Expression for triple delimiters matching, default : <pre>^((//=)|(&gt;&gt;=)|(&lt;&lt;=)|(\\*\\*=))</pre></li>
<li>identifiers - RegEx - Regular Expression for identifier, default : <pre>^[_A-Za-z][_A-Za-z0-9]*</pre></li>
</ul>


<p><strong>MIME types defined:</strong> <code>text/x-python</code>.</p>
</body>
Expand Down
14 changes: 7 additions & 7 deletions mode/python/python.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
CodeMirror.defineMode("python", function(conf, parserConf) {
var ERRORCLASS = 'error';

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

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 singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
var singleDelimiters = parserConf.singleDelimiters || new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
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 wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
Expand Down
131 changes: 131 additions & 0 deletions mode/q/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Q mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/edit/matchbrackets.js"></script>
<script src="q.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: Q mode</h1>

<div><textarea id="code" name="code">
/ utilities to quickly load a csv file - for more exhaustive analysis of the csv contents see csvguess.q
/ 2009.09.20 - updated to match latest csvguess.q

/ .csv.colhdrs[file] - return a list of colhdrs from file
/ info:.csv.info[file] - return a table of information about the file
/ columns are:
/ c - column name; ci - column index; t - load type; mw - max width;
/ dchar - distinct characters in values; rule - rule that caught the type
/ maybe - needs checking, _could_ be say a date, but perhaps just a float?
/ .csv.info0[file;onlycols] - like .csv.info except that it only analyses <onlycols>
/ example:
/ info:.csv.info0[file;(.csv.colhdrs file)like"*price"]
/ info:.csv.infolike[file;"*price"]
/ show delete from info where t=" "
/ .csv.data[file;info] - use the info from .csv.info to read the data
/ .csv.data10[file;info] - like .csv.data but only returns the first 10 rows
/ bulkload[file;info] - bulk loads file into table DATA (which must be already defined :: DATA:() )
/ .csv.read[file]/read10[file] - for when you don't care about checking/tweaking the <info> before reading

\d .csv
DELIM:","
ZAPHDRS:0b / lowercase and remove _ from colhdrs (junk characters are always removed)
WIDTHHDR:25000 / number of characters read to get the header
READLINES:222 / number of lines read and used to guess the types
SYMMAXWIDTH:11 / character columns narrower than this are stored as symbols
SYMMAXGR:10 / max symbol granularity% before we give up and keep as a * string
FORCECHARWIDTH:30 / every field (of any type) with values this wide or more is forced to character "*"
DISCARDEMPTY:0b / completely ignore empty columns if true else set them to "C"
CHUNKSIZE:50000000 / used in fs2 (modified .Q.fs)

k)nameltrim:{$[~@x;.z.s'x;~(*x)in aA:.Q.a,.Q.A;(+/&\~x in aA)_x;x]}
k)fs2:{[f;s]((-7!s)>){[f;s;x]i:1+last@&0xa=r:1:(s;x;CHUNKSIZE);f@`\:i#r;x+i}[f;s]/0j}
cleanhdrs:{{$[ZAPHDRS;lower x except"_";x]}x where x in DELIM,.Q.an}
cancast:{nw:x$"";if[not x in"BXCS";nw:(min 0#;max 0#;::)@\:nw];$[not any nw in x$(11&count y)#y;$[11<count y;not any nw in x$y;1b];0b]}

read:{[file]data[file;info[file]]}
read10:{[file]data10[file;info[file]]}

colhdrs:{[file]
`$nameltrim DELIM vs cleanhdrs first read0(file;0;1+first where 0xa=read1(file;0;WIDTHHDR))}
data:{[file;info]
(exec c from info where not t=" ")xcol(exec t from info;enlist DELIM)0:file}
data10:{[file;info]
data[;info](file;0;1+last 11#where 0xa=read1(file;0;15*WIDTHHDR))}
info0:{[file;onlycols]
colhdrs:`$nameltrim DELIM vs cleanhdrs first head:read0(file;0;1+last where 0xa=read1(file;0;WIDTHHDR));
loadfmts:(count colhdrs)#"S";if[count onlycols;loadfmts[where not colhdrs in onlycols]:"C"];
breaks:where 0xa=read1(file;0;floor(10+READLINES)*WIDTHHDR%count head);
nas:count as:colhdrs xcol(loadfmts;enlist DELIM)0:(file;0;1+last((1+READLINES)&count breaks)#breaks);
info:([]c:key flip as;v:value flip as);as:();
reserved:key`.q;reserved,:.Q.res;reserved,:`i;
info:update res:c in reserved from info;
info:update ci:i,t:"?",ipa:0b,mdot:0,mw:0,rule:0,gr:0,ndv:0,maybe:0b,empty:0b,j10:0b,j12:0b from info;
info:update ci:`s#ci from info;
if[count onlycols;info:update t:" ",rule:10 from info where not c in onlycols];
info:update sdv:{string(distinct x)except`}peach v from info;
info:update ndv:count each sdv from info;
info:update gr:floor 0.5+100*ndv%nas,mw:{max count each x}peach sdv from info where 0<ndv;
info:update t:"*",rule:20 from info where mw>.csv.FORCECHARWIDTH; / long values
info:update t:"C "[.csv.DISCARDEMPTY],rule:30,empty:1b from info where t="?",mw=0; / empty columns
info:update dchar:{asc distinct raze x}peach sdv from info where t="?";
info:update mdot:{max sum each"."=x}peach sdv from info where t="?",{"."in x}each dchar;
info:update t:"n",rule:40 from info where t="?",{any x in"0123456789"}each dchar; / vaguely numeric..
info:update t:"I",rule:50,ipa:1b from info where t="n",mw within 7 15,mdot=3,{all x in".0123456789"}each dchar,.csv.cancast["I"]peach sdv; / ip-address
info:update t:"J",rule:60 from info where t="n",mdot=0,{all x in"+-0123456789"}each dchar,.csv.cancast["J"]peach sdv;
info:update t:"I",rule:70 from info where t="J",mw<12,.csv.cancast["I"]peach sdv;
info:update t:"H",rule:80 from info where t="I",mw<7,.csv.cancast["H"]peach sdv;
info:update t:"F",rule:90 from info where t="n",mdot<2,mw>1,.csv.cancast["F"]peach sdv;
info:update t:"E",rule:100,maybe:1b from info where t="F",mw<9;
info:update t:"M",rule:110,maybe:1b from info where t in"nIHEF",mdot<2,mw within 4 7,.csv.cancast["M"]peach sdv;
info:update t:"D",rule:120,maybe:1b from info where t in"nI",mdot in 0 2,mw within 6 11,.csv.cancast["D"]peach sdv;
info:update t:"V",rule:130,maybe:1b from info where t="I",mw in 5 6,7<count each dchar,{all x like"*[0-9][0-5][0-9][0-5][0-9]"}peach sdv,.csv.cancast["V"]peach sdv; / 235959 12345
info:update t:"U",rule:140,maybe:1b from info where t="H",mw in 3 4,7<count each dchar,{all x like"*[0-9][0-5][0-9]"}peach sdv,.csv.cancast["U"]peach sdv; /2359
info:update t:"U",rule:150,maybe:0b from info where t="n",mw in 4 5,mdot=0,{all x like"*[0-9]:[0-5][0-9]"}peach sdv,.csv.cancast["U"]peach sdv;
info:update t:"T",rule:160,maybe:0b from info where t="n",mw within 7 12,mdot<2,{all x like"*[0-9]:[0-5][0-9]:[0-5][0-9]*"}peach sdv,.csv.cancast["T"]peach sdv;
info:update t:"V",rule:170,maybe:0b from info where t="T",mw in 7 8,mdot=0,.csv.cancast["V"]peach sdv;
info:update t:"T",rule:180,maybe:1b from info where t in"EF",mw within 7 10,mdot=1,{all x like"*[0-9][0-5][0-9][0-5][0-9].*"}peach sdv,.csv.cancast["T"]peach sdv;
info:update t:"Z",rule:190,maybe:0b from info where t="n",mw within 11 24,mdot<4,.csv.cancast["Z"]peach sdv;
info:update t:"P",rule:200,maybe:1b from info where t="n",mw within 12 29,mdot<4,{all x like"[12]*"}peach sdv,.csv.cancast["P"]peach sdv;
info:update t:"N",rule:210,maybe:1b from info where t="n",mw within 3 28,mdot=1,.csv.cancast["N"]peach sdv;
info:update t:"?",rule:220,maybe:0b from info where t="n"; / reset remaining maybe numeric
info:update t:"C",rule:230,maybe:0b from info where t="?",mw=1; / char
info:update t:"B",rule:240,maybe:0b from info where t in"HC",mw=1,mdot=0,{$[all x in"01tTfFyYnN";(any"0fFnN"in x)and any"1tTyY"in x;0b]}each dchar; / boolean
info:update t:"B",rule:250,maybe:1b from info where t in"HC",mw=1,mdot=0,{all x in"01tTfFyYnN"}each dchar; / boolean
info:update t:"X",rule:260,maybe:0b from info where t="?",mw=2,{$[all x in"0123456789abcdefABCDEF";(any .Q.n in x)and any"abcdefABCDEF"in x;0b]}each dchar; /hex
info:update t:"S",rule:270,maybe:1b from info where t="?",mw<.csv.SYMMAXWIDTH,mw>1,gr<.csv.SYMMAXGR; / symbols (max width permitting)
info:update t:"*",rule:280,maybe:0b from info where t="?"; / the rest as strings
/ flag those S/* columns which could be encoded to integers (.Q.j10/x10/j12/x12) to avoid symbols
info:update j12:1b from info where t in"S*",mw<13,{all x in .Q.nA}each dchar;
info:update j10:1b from info where t in"S*",mw<11,{all x in .Q.b6}each dchar;
select c,ci,t,maybe,empty,res,j10,j12,ipa,mw,mdot,rule,gr,ndv,dchar from info}
info:info0[;()] / by default don't restrict columns
infolike:{[file;pattern] info0[file;{x where x like y}[lower colhdrs[file];pattern]]} / .csv.infolike[file;"*time"]

\d .
/ DATA:()
bulkload:{[file;info]
if[not`DATA in system"v";'`DATA.not.defined];
if[count DATA;'`DATA.not.empty];
loadhdrs:exec c from info where not t=" ";loadfmts:exec t from info;
.csv.fs2[{[file;loadhdrs;loadfmts] `DATA insert $[count DATA;flip loadhdrs!(loadfmts;.csv.DELIM)0:file;loadhdrs xcol(loadfmts;enlist .csv.DELIM)0:file]}[file;loadhdrs;loadfmts]];
count DATA}
@[.:;"\\l csvutil.custom.q";::]; / save your custom settings in csvutil.custom.q to override those set at the beginning of the file
</textarea></div>

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

<p><strong>MIME type defined:</strong> <code>text/x-q</code>.</p>
</body>
</html>
124 changes: 124 additions & 0 deletions mode/q/q.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
CodeMirror.defineMode("q",function(config){
var indentUnit=config.indentUnit,
curPunc,
keywords=buildRE(["abs","acos","aj","aj0","all","and","any","asc","asin","asof","atan","attr","avg","avgs","bin","by","ceiling","cols","cor","cos","count","cov","cross","csv","cut","delete","deltas","desc","dev","differ","distinct","div","do","each","ej","enlist","eval","except","exec","exit","exp","fby","fills","first","fkeys","flip","floor","from","get","getenv","group","gtime","hclose","hcount","hdel","hopen","hsym","iasc","idesc","if","ij","in","insert","inter","inv","key","keys","last","like","list","lj","load","log","lower","lsq","ltime","ltrim","mavg","max","maxs","mcount","md5","mdev","med","meta","min","mins","mmax","mmin","mmu","mod","msum","neg","next","not","null","or","over","parse","peach","pj","plist","prd","prds","prev","prior","rand","rank","ratios","raze","read0","read1","reciprocal","reverse","rload","rotate","rsave","rtrim","save","scan","select","set","setenv","show","signum","sin","sqrt","ss","ssr","string","sublist","sum","sums","sv","system","tables","tan","til","trim","txf","type","uj","ungroup","union","update","upper","upsert","value","var","view","views","vs","wavg","where","where","while","within","wj","wj1","wsum","xasc","xbar","xcol","xcols","xdesc","xexp","xgroup","xkey","xlog","xprev","xrank"]),
E=/[|/&^!+:\\\-*%$=~#;@><,?_\'\"\[\(\]\)\s{}]/;
function buildRE(w){return new RegExp("^("+w.join("|")+")$");}
function tokenBase(stream,state){
var sol=stream.sol(),c=stream.next();
curPunc=null;
if(sol)
if(c=="/")
return(state.tokenize=tokenLineComment)(stream,state);
else if(c=="\\"){
if(stream.eol()||/\s/.test(stream.peek()))
return stream.skipToEnd(),/^\\\s*$/.test(stream.current())?(state.tokenize=tokenCommentToEOF)(stream, state):state.tokenize=tokenBase,"comment";
else
return state.tokenize=tokenBase,"builtin";
}
if(/\s/.test(c))
return stream.peek()=="/"?(stream.skipToEnd(),"comment"):"whitespace";
if(c=='"')
return(state.tokenize=tokenString)(stream,state);
if(c=='`')
return stream.eatWhile(/[A-Z|a-z|\d|_|:|\/|\.]/),"symbol";
if(("."==c&&/\d/.test(stream.peek()))||/\d/.test(c)){
var t=null;
stream.backUp(1);
if(stream.match(/^\d{4}\.\d{2}(m|\.\d{2}([D|T](\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)?)?)/)
|| stream.match(/^\d+D(\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)/)
|| stream.match(/^\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?/)
|| stream.match(/^\d+[ptuv]{1}/))
t="temporal";
else if(stream.match(/^0[NwW]{1}/)
|| stream.match(/^0x[\d|a-f|A-F]*/)
|| stream.match(/^[0|1]+[b]{1}/)
|| stream.match(/^\d+[chijn]{1}/)
|| stream.match(/-?\d*(\.\d*)?(e[+\-]?\d+)?(e|f)?/))
t="number";
return(t&&(!(c=stream.peek())||E.test(c)))?t:(stream.next(),"error");
}
if(/[A-Z|a-z]|\./.test(c))
return stream.eatWhile(/[A-Z|a-z|\.|_|\d]/),keywords.test(stream.current())?"keyword":"variable";
if(/[|/&^!+:\\\-*%$=~#;@><\.,?_\']/.test(c))
return null;
if(/[{}\(\[\]\)]/.test(c))
return null;
return"error";
}
function tokenLineComment(stream,state){
return stream.skipToEnd(),/\/\s*$/.test(stream.current())?(state.tokenize=tokenBlockComment)(stream,state):(state.tokenize=tokenBase),"comment";
}
function tokenBlockComment(stream,state){
var f=stream.sol()&&stream.peek()=="\\";
stream.skipToEnd();
if(f&&/^\\\s*$/.test(stream.current()))
state.tokenize=tokenBase;
return"comment";
}
function tokenCommentToEOF(stream){return stream.skipToEnd(),"comment";}
function tokenString(stream,state){
var escaped=false,next,end=false;
while((next=stream.next())){
if(next=="\""&&!escaped){end=true;break;}
escaped=!escaped&&next=="\\";
}
if(end)state.tokenize=tokenBase;
return"string";
}
function pushContext(state,type,col){state.context={prev:state.context,indent:state.indent,col:col,type:type};}
function popContext(state){state.indent=state.context.indent;state.context=state.context.prev;}
return{
startState:function(){
return{tokenize:tokenBase,
context:null,
indent:0,
col:0};
},
token:function(stream,state){
if(stream.sol()){
if(state.context&&state.context.align==null)
state.context.align=false;
state.indent=stream.indentation();
}
//if (stream.eatSpace()) return null;
var style=state.tokenize(stream,state);
if(style!="comment"&&state.context&&state.context.align==null&&state.context.type!="pattern"){
state.context.align=true;
}
if(curPunc=="(")pushContext(state,")",stream.column());
else if(curPunc=="[")pushContext(state,"]",stream.column());
else if(curPunc=="{")pushContext(state,"}",stream.column());
else if(/[\]\}\)]/.test(curPunc)){
while(state.context&&state.context.type=="pattern")popContext(state);
if(state.context&&curPunc==state.context.type)popContext(state);
}
else if(curPunc=="."&&state.context&&state.context.type=="pattern")popContext(state);
else if(/atom|string|variable/.test(style)&&state.context){
if(/[\}\]]/.test(state.context.type))
pushContext(state,"pattern",stream.column());
else if(state.context.type=="pattern"&&!state.context.align){
state.context.align=true;
state.context.col=stream.column();
}
}
return style;
},
indent:function(state,textAfter){
var firstChar=textAfter&&textAfter.charAt(0);
var context=state.context;
if(/[\]\}]/.test(firstChar))
while (context&&context.type=="pattern")context=context.prev;
var closing=context&&firstChar==context.type;
if(!context)
return 0;
else if(context.type=="pattern")
return context.col;
else if(context.align)
return context.col+(closing?0:1);
else
return context.indent+(closing?0:indentUnit);
}
};
});
CodeMirror.defineMIME("text/x-q","q");
2 changes: 1 addition & 1 deletion mode/sass/sass.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ CodeMirror.defineMode("sass", function(config) {
tokenizer: tokenBase,
scopes: [{offset: 0, type: 'sass'}],
definedVars: [],
definedMixins: [],
definedMixins: []
};
},
token: function(stream, state) {
Expand Down
39 changes: 39 additions & 0 deletions mode/turtle/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Turtle mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="turtle.js"></script>
<style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Turtle mode</h1>
<form><textarea id="code" name="code">
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

<http://purl.org/net/bsletten>
a foaf:Person;
foaf:interest <http://www.w3.org/2000/01/sw/>;
foaf:based_near [
geo:lat "34.0736111" ;
geo:lon "-118.3994444"
]

</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: "text/turtle",
tabMode: "indent",
matchBrackets: true
});
</script>

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

</body>
</html>
145 changes: 145 additions & 0 deletions mode/turtle/turtle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
CodeMirror.defineMode("turtle", function(config) {
var indentUnit = config.indentUnit;
var curPunc;

function wordRegexp(words) {
return new RegExp("^(?:" + words.join("|") + ")$", "i");
}
var ops = wordRegexp([]);
var keywords = wordRegexp(["@prefix", "@base", "a"]);
var operatorChars = /[*+\-<>=&|]/;

function tokenBase(stream, state) {
var ch = stream.next();
curPunc = null;
if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) {
stream.match(/^[^\s\u00a0>]*>?/);
return "atom";
}
else if (ch == "\"" || ch == "'") {
state.tokenize = tokenLiteral(ch);
return state.tokenize(stream, state);
}
else if (/[{}\(\),\.;\[\]]/.test(ch)) {
curPunc = ch;
return null;
}
else if (ch == "#") {
stream.skipToEnd();
return "comment";
}
else if (operatorChars.test(ch)) {
stream.eatWhile(operatorChars);
return null;
}
else if (ch == ":") {
return "operator";
} else {
stream.eatWhile(/[_\w\d]/);
if(stream.peek() == ":") {
return "variable-3";
} else {
var word = stream.current();

if(keywords.test(word)) {
return "meta";
}

if(ch >= "A" && ch <= "Z") {
return "comment";
} else {
return "keyword";
}
}
var word = stream.current();
if (ops.test(word))
return null;
else if (keywords.test(word))
return "meta";
else
return "variable";
}
}

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

function pushContext(state, type, col) {
state.context = {prev: state.context, indent: state.indent, col: col, type: type};
}
function popContext(state) {
state.indent = state.context.indent;
state.context = state.context.prev;
}

return {
startState: function() {
return {tokenize: tokenBase,
context: null,
indent: 0,
col: 0};
},

token: function(stream, state) {
if (stream.sol()) {
if (state.context && state.context.align == null) state.context.align = false;
state.indent = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);

if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") {
state.context.align = true;
}

if (curPunc == "(") pushContext(state, ")", stream.column());
else if (curPunc == "[") pushContext(state, "]", stream.column());
else if (curPunc == "{") pushContext(state, "}", stream.column());
else if (/[\]\}\)]/.test(curPunc)) {
while (state.context && state.context.type == "pattern") popContext(state);
if (state.context && curPunc == state.context.type) popContext(state);
}
else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state);
else if (/atom|string|variable/.test(style) && state.context) {
if (/[\}\]]/.test(state.context.type))
pushContext(state, "pattern", stream.column());
else if (state.context.type == "pattern" && !state.context.align) {
state.context.align = true;
state.context.col = stream.column();
}
}

return style;
},

indent: function(state, textAfter) {
var firstChar = textAfter && textAfter.charAt(0);
var context = state.context;
if (/[\]\}]/.test(firstChar))
while (context && context.type == "pattern") context = context.prev;

var closing = context && firstChar == context.type;
if (!context)
return 0;
else if (context.type == "pattern")
return context.col;
else if (context.align)
return context.col + (closing ? 0 : 1);
else
return context.indent + (closing ? 0 : indentUnit);
}
};
});

CodeMirror.defineMIME("text/turtle", "turtle");
2 changes: 1 addition & 1 deletion mode/vb/vb.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ CodeMirror.defineMode("vb", function(conf, parserConf) {
var singleline = delimiter.length == 1;
var OUTCLASS = 'string';

return function tokenString(stream, state) {
return function(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"]/);
if (stream.match(delimiter)) {
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":"3.01.00",
"version":"3.10.00",
"main": "codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
Expand Down
329 changes: 329 additions & 0 deletions test/doc_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
(function() {
// A minilanguage for instantiating linked CodeMirror instances and Docs
function instantiateSpec(spec, place, opts) {
var names = {}, pos = 0, l = spec.length, editors = [];
while (spec) {
var m = spec.match(/^(\w+)(\*?)(?:='([^\']*)'|<(~?)(\w+)(?:\/(\d+)-(\d+))?)\s*/);
var name = m[1], isDoc = m[2], cur;
if (m[3]) {
cur = isDoc ? CodeMirror.Doc(m[3]) : CodeMirror(place, clone(opts, {value: m[3]}));
} else {
var other = m[5];
if (!names.hasOwnProperty(other)) {
names[other] = editors.length;
editors.push(CodeMirror(place, opts));
}
var doc = editors[names[other]].linkedDoc({
sharedHist: !m[4],
from: m[6] ? Number(m[6]) : null,
to: m[7] ? Number(m[7]) : null
});
cur = isDoc ? doc : CodeMirror(place, clone(opts, {value: doc}));
}
names[name] = editors.length;
editors.push(cur);
spec = spec.slice(m[0].length);
}
return editors;
}

function clone(obj, props) {
if (!obj) return;
clone.prototype = obj;
var inst = new clone();
if (props) for (var n in props) if (props.hasOwnProperty(n))
inst[n] = props[n];
return inst;
}

function eqAll(val) {
var end = arguments.length, msg = null;
if (typeof arguments[end-1] == "string")
msg = arguments[--end];
if (i == end) throw new Error("No editors provided to eqAll");
for (var i = 1; i < end; ++i)
eq(arguments[i].getValue(), val, msg)
}

function testDoc(name, spec, run, opts, expectFail) {
if (!opts) opts = {};

return test("doc_" + name, function() {
var place = document.getElementById("testground");
var editors = instantiateSpec(spec, place, opts);
var successful = false;

try {
run.apply(null, editors);
successful = true;
} finally {
if ((debug && !successful) || verbose) {
place.style.visibility = "visible";
} else {
for (var i = 0; i < editors.length; ++i)
if (editors[i] instanceof CodeMirror)
place.removeChild(editors[i].getWrapperElement());
}
}
}, expectFail);
}

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

function testBasic(a, b) {
eqAll("x", a, b);
a.setValue("hey");
eqAll("hey", a, b);
b.setValue("wow");
eqAll("wow", a, b);
a.replaceRange("u\nv\nw", Pos(0, 3));
b.replaceRange("i", Pos(0, 4));
b.replaceRange("j", Pos(2, 1));
eqAll("wowui\nv\nwj", a, b);
}

testDoc("basic", "A='x' B<A", testBasic);
testDoc("basicSeparate", "A='x' B<~A", testBasic);

testDoc("sharedHist", "A='ab\ncd\nef' B<A", function(a, b) {
a.replaceRange("x", Pos(0));
b.replaceRange("y", Pos(1));
a.replaceRange("z", Pos(2));
eqAll("abx\ncdy\nefz", a, b);
a.undo();
a.undo();
eqAll("abx\ncd\nef", a, b);
a.redo();
eqAll("abx\ncdy\nef", a, b);
b.redo();
eqAll("abx\ncdy\nefz", a, b);
a.undo(); b.undo(); a.undo(); a.undo();
eqAll("ab\ncd\nef", a, b);
}, null, ie_lt8);

testDoc("undoIntact", "A='ab\ncd\nef' B<~A", function(a, b) {
a.replaceRange("x", Pos(0));
b.replaceRange("y", Pos(1));
a.replaceRange("z", Pos(2));
a.replaceRange("q", Pos(0));
eqAll("abxq\ncdy\nefz", a, b);
a.undo();
a.undo();
eqAll("abx\ncdy\nef", a, b);
b.undo();
eqAll("abx\ncd\nef", a, b);
a.redo();
eqAll("abx\ncd\nefz", a, b);
a.redo();
eqAll("abxq\ncd\nefz", a, b);
a.undo(); a.undo(); a.undo(); a.undo();
eqAll("ab\ncd\nef", a, b);
b.redo();
eqAll("ab\ncdy\nef", a, b);
});

testDoc("undoConflict", "A='ab\ncd\nef' B<~A", function(a, b) {
a.replaceRange("x", Pos(0));
a.replaceRange("z", Pos(2));
// This should clear the first undo event in a, but not the second
b.replaceRange("y", Pos(0));
a.undo(); a.undo();
eqAll("abxy\ncd\nef", a, b);
a.replaceRange("u", Pos(2));
a.replaceRange("v", Pos(0));
// This should clear both events in a
b.replaceRange("w", Pos(0));
a.undo(); a.undo();
eqAll("abxyvw\ncd\nefu", a, b);
});

testDoc("doubleRebase", "A='ab\ncd\nef\ng' B<~A C<B", function(a, b, c) {
c.replaceRange("u", Pos(3));
a.replaceRange("", Pos(0, 0), Pos(1, 0));
c.undo();
eqAll("cd\nef\ng", a, b, c);
});

testDoc("undoUpdate", "A='ab\ncd\nef' B<~A", function(a, b) {
a.replaceRange("x", Pos(2));
b.replaceRange("u\nv\nw\n", Pos(0, 0));
a.undo();
eqAll("u\nv\nw\nab\ncd\nef", a, b);
a.redo();
eqAll("u\nv\nw\nab\ncd\nefx", a, b);
a.undo();
eqAll("u\nv\nw\nab\ncd\nef", a, b);
b.undo();
a.redo();
eqAll("ab\ncd\nefx", a, b);
a.undo();
eqAll("ab\ncd\nef", a, b);
});

testDoc("undoKeepRanges", "A='abcdefg' B<A", function(a, b) {
var m = a.markText(Pos(0, 1), Pos(0, 3), {className: "foo"});
b.replaceRange("x", Pos(0, 0));
eqPos(m.find().from, Pos(0, 2));
b.replaceRange("yzzy", Pos(0, 1), Pos(0));
eq(m.find(), null);
b.undo();
eqPos(m.find().from, Pos(0, 2));
b.undo();
eqPos(m.find().from, Pos(0, 1));
});

testDoc("longChain", "A='uv' B<A C<B D<C", function(a, b, c, d) {
a.replaceSelection("X");
eqAll("Xuv", a, b, c, d);
d.replaceRange("Y", Pos(0));
eqAll("XuvY", a, b, c, d);
});

testDoc("broadCast", "B<A C<A D<A E<A", function(a, b, c, d, e) {
b.setValue("uu");
eqAll("uu", a, b, c, d, e);
a.replaceRange("v", Pos(0, 1));
eqAll("uvu", a, b, c, d, e);
});

// A and B share a history, C and D share a separate one
testDoc("islands", "A='x\ny\nz' B<A C<~A D<C", function(a, b, c, d) {
a.replaceRange("u", Pos(0));
d.replaceRange("v", Pos(2));
b.undo();
eqAll("x\ny\nzv", a, b, c, d);
c.undo();
eqAll("x\ny\nz", a, b, c, d);
a.redo();
eqAll("xu\ny\nz", a, b, c, d);
d.redo();
eqAll("xu\ny\nzv", a, b, c, d);
});

testDoc("unlink", "B<A C<A D<B", function(a, b, c, d) {
a.setValue("hi");
b.unlinkDoc(a);
d.setValue("aye");
eqAll("hi", a, c);
eqAll("aye", b, d);
a.setValue("oo");
eqAll("oo", a, c);
eqAll("aye", b, d);
});

testDoc("bareDoc", "A*='foo' B*<A C<B", function(a, b, c) {
is(a instanceof CodeMirror.Doc);
is(b instanceof CodeMirror.Doc);
is(c instanceof CodeMirror);
eqAll("foo", a, b, c);
a.replaceRange("hey", Pos(0, 0), Pos(0));
c.replaceRange("!", Pos(0));
eqAll("hey!", a, b, c);
b.unlinkDoc(a);
b.setValue("x");
eqAll("x", b, c);
eqAll("hey!", a);
});

testDoc("swapDoc", "A='a' B*='b' C<A", function(a, b, c) {
var d = a.swapDoc(b);
d.setValue("x");
eqAll("x", c, d);
eqAll("b", a, b);
});

testDoc("docKeepsScroll", "A='x' B*='y'", function(a, b) {
addDoc(a, 200, 200);
a.scrollIntoView(Pos(199, 200));
var c = a.swapDoc(b);
a.swapDoc(c);
var pos = a.getScrollInfo();
is(pos.left > 0, "not at left");
is(pos.top > 0, "not at top");
});

testDoc("copyDoc", "A='u'", function(a) {
var copy = a.getDoc().copy(true);
a.setValue("foo");
copy.setValue("bar");
var old = a.swapDoc(copy);
eq(a.getValue(), "bar");
a.undo();
eq(a.getValue(), "u");
a.swapDoc(old);
eq(a.getValue(), "foo");
eq(old.historySize().undo, 1);
eq(old.copy(false).historySize().undo, 0);
});

testDoc("docKeepsMode", "A='1+1'", function(a) {
var other = CodeMirror.Doc("hi", "text/x-markdown");
a.setOption("mode", "text/javascript");
var old = a.swapDoc(other);
eq(a.getOption("mode"), "text/x-markdown");
eq(a.getMode().name, "markdown");
a.swapDoc(old);
eq(a.getOption("mode"), "text/javascript");
eq(a.getMode().name, "javascript");
});

testDoc("subview", "A='1\n2\n3\n4\n5' B<~A/1-3", function(a, b) {
eq(b.getValue(), "2\n3");
eq(b.firstLine(), 1);
b.setCursor(Pos(4));
eqPos(b.getCursor(), Pos(2, 1));
a.replaceRange("-1\n0\n", Pos(0, 0));
eq(b.firstLine(), 3);
eqPos(b.getCursor(), Pos(4, 1));
a.undo();
eqPos(b.getCursor(), Pos(2, 1));
b.replaceRange("oyoy\n", Pos(2, 0));
eq(a.getValue(), "1\n2\noyoy\n3\n4\n5");
b.undo();
eq(a.getValue(), "1\n2\n3\n4\n5");
});

testDoc("subviewEditOnBoundary", "A='11\n22\n33\n44\n55' B<~A/1-4", function(a, b) {
a.replaceRange("x\nyy\nz", Pos(0, 1), Pos(2, 1));
eq(b.firstLine(), 2);
eq(b.lineCount(), 2);
eq(b.getValue(), "z3\n44");
a.replaceRange("q\nrr\ns", Pos(3, 1), Pos(4, 1));
eq(b.firstLine(), 2);
eq(b.getValue(), "z3\n4q");
eq(a.getValue(), "1x\nyy\nz3\n4q\nrr\ns5");
a.execCommand("selectAll");
a.replaceSelection("!");
eqAll("!", a, b);
});


testDoc("sharedMarker", "A='ab\ncd\nef\ngh' B<A C<~A/1-2", function(a, b, c) {
var mark = b.markText(Pos(0, 1), Pos(3, 1),
{className: "cm-searching", shared: true});
var found = a.findMarksAt(Pos(0, 2));
eq(found.length, 1);
eq(found[0], mark);
eq(c.findMarksAt(Pos(1, 1)).length, 1);
eqPos(mark.find().from, Pos(0, 1));
eqPos(mark.find().to, Pos(3, 1));
b.replaceRange("x\ny\n", Pos(0, 0));
eqPos(mark.find().from, Pos(2, 1));
eqPos(mark.find().to, Pos(5, 1));
var cleared = 0;
CodeMirror.on(mark, "clear", function() {++cleared;});
b.operation(function(){mark.clear();});
eq(a.findMarksAt(Pos(3, 1)).length, 0);
eq(b.findMarksAt(Pos(3, 1)).length, 0);
eq(c.findMarksAt(Pos(3, 1)).length, 0);
eq(mark.find(), null);
eq(cleared, 1);
});

testDoc("undoInSubview", "A='line 0\nline 1\nline 2\nline 3\nline 4' B<A/1-4", function(a, b) {
b.replaceRange("x", Pos(2, 0));
a.undo();
eq(a.getValue(), "line 0\nline 1\nline 2\nline 3\nline 4");
eq(b.getValue(), "line 1\nline 2\nline 3");
});
})();
6 changes: 3 additions & 3 deletions test/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ function eq(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) throw new Failure(label("comparing null to " + str(b)));
if (b == null) throw new Failure(label("comparing " + str(a) + " to null"));
if (a == null) throw new Failure(label("comparing null to " + str(b), msg));
if (b == null) throw new Failure(label("comparing " + str(a) + " to null", msg));
if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg));
}
function is(a, msg) {
Expand All @@ -135,4 +135,4 @@ function countTests() {
++sum;
}
return sum;
}
}
9 changes: 3 additions & 6 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ <h1>CodeMirror: Test Suite</h1>

<script src="driver.js"></script>
<script src="test.js"></script>
<script src="doc_test.js"></script>
<script src="mode_test.js"></script>
<script src="../mode/css/css.js"></script>
<script src="../mode/css/test.js"></script>
Expand All @@ -65,12 +66,8 @@ <h1>CodeMirror: Test Suite</h1>
<script src="../mode/xquery/test.js"></script>
<script src="vim_test.js"></script>
<script>
window.onload = function() {
runHarness();
};
CodeMirror.on(window, 'hashchange', function(){
runHarness();
});
window.onload = runHarness;
CodeMirror.on(window, 'hashchange', runHarness);

function esc(str) {
return str.replace(/[<&]/, function(ch) { return ch == "<" ? "&lt;" : "&amp;"; });
Expand Down
4 changes: 4 additions & 0 deletions test/lint/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function checkFile(fileName) {
locations: true,
ecmaVersion: 3,
strictSemicolons: true,
allowTrailingCommas: false,
forbidReserved: true,
sourceFile: fileName
});
Expand Down Expand Up @@ -71,6 +72,9 @@ function checkFile(fileName) {
cur.vars[node.name].used = true;
return;
}
},
FunctionExpression: function(node) {
if (node.id) fail("Named function expression", node.loc);
}
}, scopePasser);

Expand Down
641 changes: 391 additions & 250 deletions test/test.js

Large diffs are not rendered by default.

59 changes: 55 additions & 4 deletions test/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ function copyCursor(cur) {
function testVim(name, run, opts, expectedFail) {
var vimOpts = {
lineNumbers: true,
mode: 'text/x-csrc',
keyMap: 'vim',
showCursorWhenSelecting: true,
value: code
Expand Down Expand Up @@ -266,6 +265,41 @@ testVim('Changing lines after Eol operation', function(cm, vim, helpers) {
// same place we were at on line 1
helpers.assertCursorAt({ line: 5, ch: lines[2].length - 2 });
});
//making sure gj and gk recover from clipping
testVim('gj_gk_clipping', function(cm,vim,helpers){
cm.setCursor(0, 1);
helpers.doKeys('g','j','g','j');
helpers.assertCursorAt(2, 1);
helpers.doKeys('g','k','g','k');
helpers.assertCursorAt(0, 1);
},{value: 'line 1\n\nline 2'});
//testing a mix of j/k and gj/gk
testVim('j_k_and_gj_gk', function(cm,vim,helpers){
cm.setSize(120);
cm.setCursor(0, 0);
//go to the last character on the first line
helpers.doKeys('$');
//move up/down on the column within the wrapped line
//side-effect: cursor is not locked to eol anymore
helpers.doKeys('g','k');
var cur=cm.getCursor();
eq(cur.line,0);
is((cur.ch<176),'gk didn\'t move cursor back (1)');
helpers.doKeys('g','j');
helpers.assertCursorAt(0, 176);
//should move to character 177 on line 2 (j/k preserve character index within line)
helpers.doKeys('j');
//due to different line wrapping, the cursor can be on a different screen-x now
//gj and gk preserve screen-x on movement, much like moveV
helpers.doKeys('3','g','k');
cur=cm.getCursor();
eq(cur.line,1);
is((cur.ch<176),'gk didn\'t move cursor back (2)');
helpers.doKeys('g','j','2','g','j');
//should return to the same character-index
helpers.doKeys('k');
helpers.assertCursorAt(0, 176);
},{ lineWrapping:true, value: 'This line is intentially long to test movement of gj and gk over wrapped lines. I will start on the end of this line, then make a step up and back to set the origin for j and k.\nThis line is supposed to be even longer than the previous. I will jump here and make another wiggle with gj and gk, before I jump back to the line above. Both wiggles should not change my cursor\'s target character but both j/k and gj/gk change each other\'s reference position.'});
testVim('}', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('}');
Expand Down Expand Up @@ -827,11 +861,14 @@ testVim('? and n/N', function(cm, vim, helpers) {
helpers.doKeys('2', '?');
helpers.assertCursorAt(0, 11);
}, { value: 'match nope match \n nope Match' });
testVim(',/ clearSearchHighlight', function(cm, vim, helpers) {
//:noh should clear highlighting of search-results but allow to resume search through n
testVim('noh_clearSearchHighlight', function(cm, vim, helpers) {
cm.openDialog = helpers.fakeOpenDialog('match');
helpers.doKeys('?');
helpers.doKeys(',', '/', 'n');
helpers.assertCursorAt(0, 11);
helpers.doEx('noh');
eq(vim.searchState_.getOverlay(),null,'match-highlighting wasn\'t cleared');
helpers.doKeys('n');
helpers.assertCursorAt(0, 11,'can\'t resume search after clearing highlighting');
}, { value: 'match nope match \n nope Match' });
testVim('*', function(cm, vim, helpers) {
cm.setCursor(0, 9);
Expand Down Expand Up @@ -987,6 +1024,20 @@ testVim('ex_map_key2ex', function(cm, vim, helpers) {
eq(written, true);
eq(actualCm, cm);
});
// Testing registration of functions as ex-commands and mapping to <Key>-keys
testVim('ex_api_test', function(cm, vim, helpers) {
var res=false;
var val='from';
CodeMirror.Vim.defineEx('extest','ext',function(cm,params){
if(params.args)val=params.args[0];
else res=true;
});
helpers.doEx(':ext to');
eq(val,'to','Defining ex-command failed');
CodeMirror.Vim.map('<C-CR><Space>',':ext');
helpers.doKeys('Ctrl-Enter','Space');
is(res,'Mapping to key failed');
});
// For now, this test needs to be last because it messes up : for future tests.
testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
helpers.doEx('map : x');
Expand Down