14 changes: 14 additions & 0 deletions doc/releases.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ <h2>Release notes and version history</h2>

<h2>Version 5.x</h2>

<p class="rel">20-08-2015: <a href="http://codemirror.net/codemirror-5.7.zip">Version 5.7</a>:</p>

<ul class="rel-note">
<li>New modes: <a href="../mode/vue/index.html">Vue</a>, <a href="../mode/oz/index.html">Oz</a>, <a href="../mode/mscgen/index.html">MscGen</a> (and dialects), <a href="../mode/css/gss.html">Closure Stylesheets</a></li>
<li>Implement <a href="http://commonmark.org">CommonMark</a>-style flexible list indent and cross-line code spans in <a href="../mode/markdown/index.html">Markdown</a> mode</li>
<li>Add a replace-all button to the <a href="../doc/manual.html#addon_search">search addon</a>, and make the persistent search dialog transparent when it obscures the match</li>
<li>Handle <code>acync</code>/<code>await</code> and ocal and binary numbers in <a href="../mode/javascript/index.html">JavaScript mode</a></li>
<li>Fix various issues with the <a href="../mode/haxe/index.html">Haxe mode</a></li>
<li>Make the <a href="../doc/manual.html#addon_closebrackets">closebrackets addon</a> select only the wrapped text when wrapping selection in brackets</li>
<li>Tokenize properties as properties in the <a href="../mode/coffeescript/index.html">CoffeeScript mode</a></li>
<li>The <a href="../doc/manual.html#addon_placeholder">placeholder addon</a> now accepts a DOM node as well as a string placeholder</li>
<li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.6.0...5.7.0">list of patches</a></li>
</ul>

<p class="rel">20-08-2015: <a href="http://codemirror.net/codemirror-5.6.zip">Version 5.6</a>:</p>

<ul class="rel-note">
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ <h2>This is CodeMirror</h2>
</div>
</div>
<div class=actionsleft>
Get the current version: <a href="http://codemirror.net/codemirror.zip">5.6</a>.<br>
Get the current version: <a href="http://codemirror.net/codemirror.zip">5.7</a>.<br>
You can see the <a href="https://github.com/codemirror/codemirror" title="Github repository">code</a> or<br>
read the <a href="doc/releases.html">release notes</a>.<br>
There is a <a href="doc/compress.html">minification helper</a>.
Expand Down
14 changes: 11 additions & 3 deletions keymap/sublime.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,19 @@
var cursor = cm.getCursor();
var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);
var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize"));
var indentUnit = cm.getOption("indentUnit");

if (toStartOfLine && !/\S/.test(toStartOfLine) && column % cm.getOption("indentUnit") == 0)
return cm.indentSelection("subtract");
else
if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) {
var prevIndent = new Pos(cursor.line,
CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit));

// If no smart delete is happening (due to tab sizing) just do a regular delete
if (prevIndent.ch == cursor.ch) return CodeMirror.Pass;

return cm.replaceRange("", prevIndent, cursor, "+delete");
} else {
return CodeMirror.Pass;
}
};

cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
Expand Down
47 changes: 30 additions & 17 deletions lib/codemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@

on(te, "compositionstart", function() {
var start = cm.getCursor("from");
if (input.composing) input.composing.range.clear()
input.composing = {
start: start,
range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
Expand Down Expand Up @@ -1533,6 +1534,10 @@
}
},

readOnlyChanged: function(val) {
if (!val) this.reset();
},

setUneditable: nothing,

needsContentAttribute: false
Expand All @@ -1551,7 +1556,6 @@
init: function(display) {
var input = this, cm = input.cm;
var div = input.div = display.lineDiv;
div.contentEditable = "true";
disableBrowserMagic(div);

on(div, "paste", function(e) { handlePaste(e, cm); })
Expand Down Expand Up @@ -1592,7 +1596,7 @@

on(div, "input", function() {
if (input.composing) return;
if (!input.pollContent())
if (isReadOnly(cm) || !input.pollContent())
runInOp(input.cm, function() {regChange(cm);});
});

Expand Down Expand Up @@ -1817,17 +1821,24 @@
this.div.focus();
},
applyComposition: function(composing) {
if (composing.data && composing.data != composing.startData)
if (isReadOnly(this.cm))
operation(this.cm, regChange)(this.cm)
else if (composing.data && composing.data != composing.startData)
operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel);
},

setUneditable: function(node) {
node.setAttribute("contenteditable", "false");
node.contentEditable = "false"
},

onKeyPress: function(e) {
e.preventDefault();
operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
if (!isReadOnly(this.cm))
operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
},

readOnlyChanged: function(val) {
this.div.contentEditable = String(val != "nocursor")
},

onContextMenu: nothing,
Expand Down Expand Up @@ -5388,8 +5399,8 @@
cm.display.disabled = true;
} else {
cm.display.disabled = false;
if (!val) cm.display.input.reset();
}
cm.display.input.readOnlyChanged(val)
});
option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
option("dragDrop", true, dragDropChanged);
Expand Down Expand Up @@ -6952,7 +6963,7 @@
txt.setAttribute("cm-text", "\t");
builder.col += tabWidth;
} else if (m[0] == "\r" || m[0] == "\n") {
var txt = content.appendChild(elt("span", m[0] == "\r" ? "" : "", "cm-invalidchar"));
var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar"));
txt.setAttribute("cm-text", m[0]);
builder.col += 1;
} else {
Expand Down Expand Up @@ -8211,7 +8222,7 @@

// The inverse of countColumn -- find the offset that corresponds to
// a particular column.
function findColumn(string, goal, tabSize) {
var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) {
for (var pos = 0, col = 0;;) {
var nextTab = string.indexOf("\t", pos);
if (nextTab == -1) nextTab = string.length;
Expand Down Expand Up @@ -8504,14 +8515,16 @@

// KEY NAMES

var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
CodeMirror.keyNames = keyNames;
var keyNames = CodeMirror.keyNames = {
3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete",
173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"
};
(function() {
// Number keys
for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
Expand Down Expand Up @@ -8816,7 +8829,7 @@

// THE END

CodeMirror.version = "5.6.0";
CodeMirror.version = "5.7.0";

return CodeMirror;
});
15 changes: 15 additions & 0 deletions mode/clike/clike.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return false;
}

function cpp14Literal(stream) {
stream.eatWhile(/[\w\.']/);
return "number";
}

function cpp11StringHook(stream, state) {
stream.backUp(1);
// Raw strings.
Expand Down Expand Up @@ -373,6 +378,16 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"U": cpp11StringHook,
"L": cpp11StringHook,
"R": cpp11StringHook,
"0": cpp14Literal,
"1": cpp14Literal,
"2": cpp14Literal,
"3": cpp14Literal,
"4": cpp14Literal,
"5": cpp14Literal,
"6": cpp14Literal,
"7": cpp14Literal,
"8": cpp14Literal,
"9": cpp14Literal,
token: function(stream, state, style) {
if (style == "variable" && stream.peek() == "(" &&
(state.prevToken == ";" || state.prevToken == null ||
Expand Down
9 changes: 9 additions & 0 deletions mode/clike/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,13 @@
" [keyword for] (;;)",
" [variable x][operator ++];",
"[keyword return];");

var mode_cpp = CodeMirror.getMode({indentUnit: 2}, "text/x-c++src");
function MTCPP(name) { test.mode(name, mode_cpp, Array.prototype.slice.call(arguments, 1)); }

MTCPP("cpp14_literal",
"[number 10'000];",
"[number 0b10'000];",
"[number 0x10'000];",
"[string '100000'];");
})();
31 changes: 14 additions & 17 deletions mode/coffeescript/coffeescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/;
var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/;

var wordOperators = wordRegexp(["and", "or", "not",
"is", "isnt", "in",
Expand Down Expand Up @@ -145,6 +145,8 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
}
}



// Handle operators and delimiters
if (stream.match(operators) || stream.match(wordOperators)) {
return "operator";
Expand All @@ -157,6 +159,10 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return "atom";
}

if (stream.match(atProp) || state.prop && stream.match(identifiers)) {
return "property";
}

if (stream.match(keywords)) {
return "keyword";
}
Expand All @@ -165,10 +171,6 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return "variable";
}

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

// Handle non-detected items
stream.next();
return ERRORCLASS;
Expand Down Expand Up @@ -266,7 +268,7 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
var current = stream.current();

// Handle "." connected identifiers
if (current === ".") {
if (false && current === ".") {
style = state.tokenize(stream, state);
current = stream.current();
if (/^\.[\w$]+$/.test(current)) {
Expand All @@ -280,9 +282,7 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
if (current === "return") {
state.dedent = true;
}
if (((current === "->" || current === "=>") &&
!state.lambda &&
!stream.peek())
if (((current === "->" || current === "=>") && stream.eol())
|| style === "indent") {
indent(stream, state);
}
Expand Down Expand Up @@ -324,8 +324,7 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return {
tokenize: tokenBase,
scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
lastToken: null,
lambda: false,
prop: false,
dedent: 0
};
},
Expand All @@ -335,12 +334,9 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
if (fillAlign && stream.sol()) fillAlign.align = false;

var style = tokenLexer(stream, state);
if (fillAlign && style && style != "comment") fillAlign.align = true;

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

if (stream.eol() && stream.lambda) {
state.lambda = false;
if (style && style != "comment") {
if (fillAlign) fillAlign.align = true;
state.prop = style == "punctuation" && stream.current() == "."
}

return style;
Expand All @@ -365,5 +361,6 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
});

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

});
81 changes: 66 additions & 15 deletions mode/css/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
counterDescriptors = parserConfig.counterDescriptors || {},
colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {},
allowNested = parserConfig.allowNested;
allowNested = parserConfig.allowNested,
supportsAtComponent = parserConfig.supportsAtComponent === true;

var type, override;
function ret(style, tp) { type = tp; return style; }
Expand Down Expand Up @@ -122,13 +123,14 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
this.prev = prev;
}

function pushContext(state, stream, type) {
state.context = new Context(type, stream.indentation() + indentUnit, state.context);
function pushContext(state, stream, type, indent) {
state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
return type;
}

function popContext(state) {
state.context = state.context.prev;
if (state.context.prev)
state.context = state.context.prev;
return state.context.type;
}

Expand Down Expand Up @@ -160,9 +162,13 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) {
return popContext(state);
} else if (/@(media|supports|(-moz-)?document)/.test(type)) {
} else if (supportsAtComponent && /@component/.test(type)) {
return pushContext(state, stream, "atComponentBlock");
} else if (/^@(-moz-)?document$/.test(type)) {
return pushContext(state, stream, "documentTypes");
} else if (/^@(media|supports|(-moz-)?document|import)$/.test(type)) {
return pushContext(state, stream, "atBlock");
} else if (/@(font-face|counter-style)/.test(type)) {
} else if (/^@(font-face|counter-style)/.test(type)) {
state.stateArg = type;
return "restricted_atBlock_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
Expand Down Expand Up @@ -255,17 +261,24 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return pass(type, stream, state);
};

states.documentTypes = function(type, stream, state) {
if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
override = "tag";
return state.context.type;
} else {
return states.atBlock(type, stream, state);
}
};

states.atBlock = function(type, stream, state) {
if (type == "(") return pushContext(state, stream, "atBlock_parens");
if (type == "}") return popAndPass(type, stream, state);
if (type == "}" || type == ";") return popAndPass(type, stream, state);
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");

if (type == "word") {
var word = stream.current().toLowerCase();
if (word == "only" || word == "not" || word == "and" || word == "or")
override = "keyword";
else if (documentTypes.hasOwnProperty(word))
override = "tag";
else if (mediaTypes.hasOwnProperty(word))
override = "attribute";
else if (mediaFeatures.hasOwnProperty(word))
Expand All @@ -286,6 +299,16 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return state.context.type;
};

states.atComponentBlock = function(type, stream, state) {
if (type == "}")
return popAndPass(type, stream, state);
if (type == "{")
return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
if (type == "word")
override = "error";
return state.context.type;
};

states.atBlock_parens = function(type, stream, state) {
if (type == ")") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
Expand Down Expand Up @@ -364,12 +387,18 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
var cx = state.context, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
if (cx.prev &&
(ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "restricted_atBlock") ||
ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
ch == "{" && (cx.type == "at" || cx.type == "atBlock"))) {
indent = cx.indent - indentUnit;
cx = cx.prev;
if (cx.prev) {
if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
// Resume indentation from parent context.
cx = cx.prev;
indent = cx.indent;
} else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
// Dedent relative to current context.
indent = Math.max(0, cx.indent - indentUnit);
cx = cx.prev;
}
}
return indent;
},
Expand Down Expand Up @@ -769,4 +798,26 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
helperType: "less"
});

CodeMirror.defineMIME("text/x-gss", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
supportsAtComponent: true,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css",
helperType: "gss"
});

});
103 changes: 103 additions & 0 deletions mode/css/gss.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<!doctype html>

<title>CodeMirror: Closure Stylesheets (GSS) mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">

<link rel="stylesheet" href="../../lib/codemirror.css">
<link rel="stylesheet" href="../../addon/hint/show-hint.css">
<script src="../../lib/codemirror.js"></script>
<script src="css.js"></script>
<script src="../../addon/hint/show-hint.js"></script>
<script src="../../addon/hint/css-hint.js"></script>
<style>.CodeMirror {background: #f8f8f8;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>

<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">Closure Stylesheets (GSS)</a>
</ul>
</div>

<article>
<h2>Closure Stylesheets (GSS) mode</h2>
<form><textarea id="code" name="code">
/* Some example Closure Stylesheets */

@provide 'some.styles';

@require 'other.styles';

@component {

@def FONT_FAMILY "Times New Roman", Georgia, Serif;
@def FONT_SIZE_NORMAL 15px;
@def FONT_NORMAL normal FONT_SIZE_NORMAL FONT_FAMILY;

@def BG_COLOR rgb(235, 239, 249);

@def DIALOG_BORDER_COLOR rgb(107, 144, 218);
@def DIALOG_BG_COLOR BG_COLOR;

@def LEFT_HAND_NAV_WIDTH 180px;
@def LEFT_HAND_NAV_PADDING 3px;

@defmixin size(WIDTH, HEIGHT) {
width: WIDTH;
height: HEIGHT;
}

body {
background-color: BG_COLOR;
margin: 0;
padding: 3em 6em;
font: FONT_NORMAL;
color: #000;
}

#navigation a {
font-weight: bold;
text-decoration: none !important;
}

.dialog {
background-color: DIALOG_BG_COLOR;
border: 1px solid DIALOG_BORDER_COLOR;
}

.content {
position: absolute;
margin-left: add(LEFT_HAND_NAV_PADDING, /* padding left */
LEFT_HAND_NAV_WIDTH,
LEFT_HAND_NAV_PADDING); /* padding right */

}

.logo {
@mixin size(150px, 55px);
background-image: url('http://www.google.com/images/logo_sm.gif');
}

}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
extraKeys: {"Ctrl-Space": "autocomplete"},
lineNumbers: true,
matchBrackets: "text/x-less",
mode: "text/x-gss"
});
</script>

<p>A mode for <a href="https://github.com/google/closure-stylesheets">Closure Stylesheets</a> (GSS).</p>
<p><strong>MIME type defined:</strong> <code>text/x-gss</code>.</p>

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

</article>
17 changes: 17 additions & 0 deletions mode/css/gss_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function() {
"use strict";

var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-gss");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "gss"); }

MT("atComponent",
"[def @component] {",
"[tag foo] {",
" [property color]: [keyword black];",
"}",
"}");

})();
6 changes: 3 additions & 3 deletions mode/css/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }

// Error, because "foobarhello" is neither a known type or property, but
// property was expected (after "and"), and it should be in parenthese.
// property was expected (after "and"), and it should be in parentheses.
MT("atMediaUnknownType",
"[def @media] [attribute screen] [keyword and] [error foobarhello] { }");

Expand Down Expand Up @@ -120,7 +120,7 @@
"}");

MT("empty_url",
"[def @import] [tag url]() [tag screen];");
"[def @import] [atom url]() [attribute screen];");

MT("parens",
"[qualifier .foo] {",
Expand Down Expand Up @@ -156,7 +156,7 @@
" [tag foo] {",
" [property font-family]: [variable Verdana], [atom sans-serif];",
" }",
" }");
"}");

MT("document_url",
"[def @document] [tag url]([string http://blah]) { [qualifier .class] { } }");
Expand Down
2 changes: 1 addition & 1 deletion mode/elm/elm.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,4 @@
});

CodeMirror.defineMIME("text/x-elm", "elm");
})();
});
40 changes: 23 additions & 17 deletions mode/gfm/gfm.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
})(function(CodeMirror) {
"use strict";

var urlRE = /^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»]))/i

CodeMirror.defineMode("gfm", function(config, modeConfig) {
var codeDepth = 0;
function blankLine(state) {
Expand All @@ -37,7 +39,7 @@ CodeMirror.defineMode("gfm", function(config, modeConfig) {

// Hack to prevent formatting override inside code blocks (block and inline)
if (state.codeBlock) {
if (stream.match(/^```/)) {
if (stream.match(/^```+/)) {
state.codeBlock = false;
return null;
}
Expand All @@ -47,7 +49,7 @@ CodeMirror.defineMode("gfm", function(config, modeConfig) {
if (stream.sol()) {
state.code = false;
}
if (stream.sol() && stream.match(/^```/)) {
if (stream.sol() && stream.match(/^```+/)) {
stream.skipToEnd();
state.codeBlock = true;
return null;
Expand Down Expand Up @@ -78,25 +80,29 @@ CodeMirror.defineMode("gfm", function(config, modeConfig) {
}
if (stream.sol() || state.ateSpace) {
state.ateSpace = false;
if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
// User/Project@SHA
// User@SHA
// SHA
state.combineTokens = true;
return "link";
} else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
// User/Project#Num
// User#Num
// #Num
state.combineTokens = true;
return "link";
if (modeConfig.gitHubSpice !== false) {
if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
// User/Project@SHA
// User@SHA
// SHA
state.combineTokens = true;
return "link";
} else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
// User/Project#Num
// User#Num
// #Num
state.combineTokens = true;
return "link";
}
}
}
if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»]))/i) &&
stream.string.slice(stream.start - 2, stream.start) != "](") {
if (stream.match(urlRE) &&
stream.string.slice(stream.start - 2, stream.start) != "](" &&
(stream.start == 0 || /\W/.test(stream.string.charAt(stream.start - 1)))) {
// URLs
// Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
// And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
// And then limited url schemes to the CommonMark list, so foo:bar isn't matched as a URL
state.combineTokens = true;
return "link";
}
Expand All @@ -109,7 +115,7 @@ CodeMirror.defineMode("gfm", function(config, modeConfig) {
var markdownConfig = {
underscoresBreakWords: false,
taskLists: true,
fencedCodeBlocks: true,
fencedCodeBlocks: '```',
strikethrough: true
};
for (var attr in modeConfig) {
Expand Down
21 changes: 19 additions & 2 deletions mode/gfm/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
"[comment ```]",
"bar");

MT("fencedCodeBlocksNoTildes",
"~~~",
"foo",
"~~~");

MT("taskListAsterisk",
"[variable-2 * []] foo]", // Invalid; must have space or x between []
"[variable-2 * [ ]]bar]", // Invalid; must have space after ]
Expand Down Expand Up @@ -133,6 +138,15 @@
MT("vanillaLink",
"foo [link http://www.example.com/] bar");

MT("vanillaLinkNoScheme",
"foo [link www.example.com] bar");

MT("vanillaLinkHttps",
"foo [link https://www.example.com/] bar");

MT("vanillaLinkDataSchema",
"foo [link data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==] bar");

MT("vanillaLinkPunctuation",
"foo [link http://www.example.com/]. bar");

Expand All @@ -142,6 +156,9 @@
MT("vanillaLinkEmphasis",
"foo [em *][em&link http://www.example.com/index.html][em *] bar");

MT("notALink",
"foo asfd:asdf bar");

MT("notALink",
"[comment ```css]",
"[tag foo] {[property color]:[keyword black];}",
Expand All @@ -152,8 +169,8 @@

MT("notALink",
"[comment `foo]",
"[link http://www.example.com/]",
"[comment `foo]",
"[comment&link http://www.example.com/]",
"[comment `] foo",
"",
"[link http://www.example.com/]");

Expand Down
148 changes: 68 additions & 80 deletions mode/haxe/haxe.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,21 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {

// Tokenizer

var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"};
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"};
var type = kw("typedef");
return {
"if": A, "while": A, "else": B, "do": B, "try": B,
"return": C, "break": C, "continue": C, "new": C, "throw": C,
"var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"),
var keywords = {
"if": A, "while": A, "else": B, "do": B, "try": B,
"return": C, "break": C, "continue": C, "new": C, "throw": C,
"var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"),
"public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"),
"function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "never": kw("property_access"), "trace":kw("trace"),
"function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "never": kw("property_access"), "trace":kw("trace"),
"class": type, "abstract":type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type,
"true": atom, "false": atom, "null": atom
};
}();
"true": atom, "false": atom, "null": atom
};

var isOperatorChar = /[+\-*&%=<>!?|]/;

Expand All @@ -41,14 +39,13 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
return f(stream, state);
}

function nextUntilUnescaped(stream, end) {
function toUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
return true;
escaped = !escaped && next == "\\";
}
return escaped;
}

// Used as scratch variables to communicate multiple values without
Expand All @@ -61,70 +58,58 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {

function haxeTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
if (ch == '"' || ch == "'") {
return chain(stream, state, haxeTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
} else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
stream.match(/^\d*(?:\.\d*(?!\.))?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) {
nextUntilUnescaped(stream, "/");
} else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) {
toUnescaped(stream, "/");
stream.eatWhile(/[gimsu]/);
return ret("regexp", "string-2");
}
else if (ch == "/") {
} else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, haxeTokenComment);
}
else if (stream.eat("/")) {
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else {
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
} else if (ch == "#") {
stream.skipToEnd();
return ret("conditional", "meta");
}
else if (ch == "@") {
} else if (ch == "@") {
stream.eat(/:/);
stream.eatWhile(/[\w_]/);
return ret ("metadata", "meta");
}
else if (isOperatorChar.test(ch)) {
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
var word;
if(/[A-Z]/.test(ch))
{
stream.eatWhile(/[\w_<>]/);
word = stream.current();
return ret("type", "variable-3", word);
}
else
{
} else {
var word;
if(/[A-Z]/.test(ch)) {
stream.eatWhile(/[\w_<>]/);
word = stream.current();
return ret("type", "variable-3", word);
} else {
stream.eatWhile(/[\w_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
}
}

function haxeTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
if (toUnescaped(stream, quote))
state.tokenize = haxeTokenBase;
return ret("string", "string");
};
Expand Down Expand Up @@ -176,27 +161,25 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
if (type == "variable" && imported(state, content)) return "variable-3";
if (type == "variable" && imported(state, content)) return "variable-3";
return style;
}
}
}

function imported(state, typename)
{
if (/[a-z]/.test(typename.charAt(0)))
return false;
var len = state.importedtypes.length;
for (var i = 0; i<len; i++)
if(state.importedtypes[i]==typename) return true;
function imported(state, typename) {
if (/[a-z]/.test(typename.charAt(0)))
return false;
var len = state.importedtypes.length;
for (var i = 0; i<len; i++)
if(state.importedtypes[i]==typename) return true;
}


function registerimport(importname) {
var state = cx.state;
for (var t = state.importedtypes; t; t = t.next)
if(t.name == importname) return;
state.importedtypes = { name: importname, next: state.importedtypes };
var state = cx.state;
for (var t = state.importedtypes; t; t = t.next)
if(t.name == importname) return;
state.importedtypes = { name: importname, next: state.importedtypes };
}
// Combinator utils

Expand Down Expand Up @@ -229,6 +212,7 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
popcontext.lex = true;
function pushlex(type, info) {
var result = function() {
var state = cx.state;
Expand All @@ -252,7 +236,7 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(f);
};
}
return f;
}

Expand All @@ -266,25 +250,26 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
if (type == "attribute") return cont(maybeattribute);
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
statement, poplex, popcontext);
if (type == "import") return cont(importdef, expect(";"));
if (type == "typedef") return cont(typedef);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "type" ) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "[") return cont(pushlex("]"), commasep(maybeexpression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
Expand Down Expand Up @@ -318,14 +303,14 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
}

function importdef (type, value) {
if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
else if(type == "variable" || type == "property" || type == "." || value == "*") return cont(importdef);
if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
else if(type == "variable" || type == "property" || type == "." || value == "*") return cont(importdef);
}

function typedef (type, value)
{
if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
else if (type == "type" && /[A-Z]/.test(value.charAt(0))) { return cont(); }
if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
else if (type == "type" && /[A-Z]/.test(value.charAt(0))) { return cont(); }
}

function maybelabel(type) {
Expand Down Expand Up @@ -363,16 +348,19 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
if (type == ",") return cont(vardef1);
}
function forspec1(type, value) {
if (type == "variable") {
register(value);
}
return cont(pushlex(")"), pushcontext, forin, expression, poplex, statement, popcontext);
if (type == "variable") {
register(value);
return cont(forin, expression)
} else {
return pass()
}
}
function forin(_type, value) {
if (value == "in") return cont();
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
//function names starting with upper-case letters are recognised as types, so cludging them together here.
if (type == "variable" || type == "type") {register(value); return cont(functiondef);}
if (value == "new") return cont(functiondef);
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext);
}
Expand All @@ -395,15 +383,15 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {

return {
startState: function(basecolumn) {
var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"];
var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"];
return {
tokenize: haxeTokenBase,
reAllowed: true,
kwAllowed: true,
cc: [],
lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
importedtypes: defaulttypes,
importedtypes: defaulttypes,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
Expand Down
221 changes: 125 additions & 96 deletions mode/htmlmixed/htmlmixed.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,113 +9,142 @@
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {name: "xml",
htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag});
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;
if (tagName) tagName = tagName.toLowerCase();
var style = htmlMode.token(stream, 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;
}
"use strict";

var defaultTags = {
script: [
["lang", /(javascript|babel)/i, "javascript"],
["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i, "javascript"],
["type", /./, "text/plain"],
[null, null, "javascript"]
],
style: [
["lang", /^css$/i, "css"],
["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
["type", /./, "text/plain"],
[null, null, "css"]
]
};

function maybeBackup(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat);
if (close > -1) stream.backUp(cur.length - close);
else if (cur.match(/<\/?$/)) {
var cur = stream.current(), close = cur.search(pat);
if (close > -1) {
stream.backUp(cur.length - close);
} else if (cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur);
}
return style;
}
function script(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;
}
return maybeBackup(stream, /<\/\s*script\s*>/,
state.localMode.token(stream, state.localState));

var attrRegexpCache = {};
function getAttrRegexp(attr) {
var regexp = attrRegexpCache[attr];
if (regexp) return regexp;
return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
}

function getAttrValue(stream, attr) {
var pos = stream.pos, match;
while (pos >= 0 && stream.string.charAt(pos) !== "<") pos--;
if (pos < 0) return pos;
if (match = stream.string.slice(pos, stream.pos).match(getAttrRegexp(attr)))
return match[2];
return "";
}

function getTagRegexp(tagName, anchored) {
return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
}
function css(stream, state) {
if (stream.match(/^<\/\s*style\s*>/i, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;

function addTags(from, to) {
for (var tag in from) {
var dest = to[tag] || (to[tag] = []);
var source = from[tag];
for (var i = source.length - 1; i >= 0; i--)
dest.unshift(source[i])
}
return maybeBackup(stream, /<\/\s*style\s*>/,
cssMode.token(stream, state.localState));
}

return {
startState: function() {
var state = htmlMode.startState();
return {token: html, localMode: null, localState: null, htmlState: state};
},

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

token: function(stream, state) {
return state.token(stream, state);
},

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

innerMode: function(state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
function findMatchingMode(tagInfo, stream) {
for (var i = 0; i < tagInfo.length; i++) {
var spec = tagInfo[i];
if (!spec[0] || spec[1].test(getAttrValue(stream, spec[0]))) return spec[2];
}
};
}, "xml", "javascript", "css");
}

CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {
name: "xml",
htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
});

var tags = {};
var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
addTags(defaultTags, tags);
if (configTags) addTags(configTags, tags);
if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])

function html(stream, state) {
var tagName = state.htmlState.tagName;
var tagInfo = tagName && tags[tagName.toLowerCase()];

var style = htmlMode.token(stream, state.htmlState), modeSpec;

if (tagInfo && /\btag\b/.test(style) && stream.current() === ">" &&
(modeSpec = findMatchingMode(tagInfo, stream))) {
var mode = CodeMirror.getMode(config, modeSpec);
var endTagA = getTagRegexp(tagName, true), endTag = getTagRegexp(tagName, false);
state.token = function (stream, state) {
if (stream.match(endTagA, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;
}
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
};
state.localMode = mode;
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, ""));
}
return style;
};

return {
startState: function () {
var state = htmlMode.startState();
return {token: html, localMode: null, localState: null, htmlState: state};
},

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

token: function (stream, state) {
return state.token(stream, state);
},

CodeMirror.defineMIME("text/html", "htmlmixed");
indent: function (state, textAfter) {
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter);
else
return CodeMirror.Pass;
},

innerMode: function (state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
};
}, "xml", "javascript", "css");

CodeMirror.defineMIME("text/html", "htmlmixed");
});
3 changes: 3 additions & 0 deletions mode/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ <h2>Language modes</h2>
<li><a href="brainfuck/index.html">Brainfuck</a></li>
<li><a href="clike/index.html">C, C++, C#</a></li>
<li><a href="clojure/index.html">Clojure</a></li>
<li><a href="css/gss.html">Closure Stylesheets (GSS)</a></li>
<li><a href="cmake/index.html">CMake</a></li>
<li><a href="cobol/index.html">COBOL</a></li>
<li><a href="coffeescript/index.html">CoffeeScript</a></li>
Expand Down Expand Up @@ -90,6 +91,7 @@ <h2>Language modes</h2>
<li><a href="clike/index.html">Objective C</a></li>
<li><a href="mllike/index.html">OCaml</a></li>
<li><a href="octave/index.html">Octave</a> (MATLAB)</li>
<li><a href="oz/index.html">Oz</a></li>
<li><a href="pascal/index.html">Pascal</a></li>
<li><a href="pegjs/index.html">PEG.js</a></li>
<li><a href="perl/index.html">Perl</a></li>
Expand Down Expand Up @@ -139,6 +141,7 @@ <h2>Language modes</h2>
<li><a href="velocity/index.html">Velocity</a></li>
<li><a href="verilog/index.html">Verilog/SystemVerilog</a></li>
<li><a href="vhdl/index.html">VHDL</a></li>
<li><a href="vue/index.html">Vue.js app</a></li>
<li><a href="xml/index.html">XML/HTML</a></li>
<li><a href="xquery/index.html">XQuery</a></li>
<li><a href="yaml/index.html">YAML</a></li>
Expand Down
19 changes: 14 additions & 5 deletions mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"async": kw("async"), "function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C
"await": C, "yield": C, "export": kw("export"), "import": kw("import"), "extends": C
};

// Extend the 'normal' keywords with the TypeScript language extensions
Expand Down Expand Up @@ -105,6 +105,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/o/i)) {
stream.eatWhile(/[0-7]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/b/i)) {
stream.eatWhile(/[01]/i);
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
Expand Down Expand Up @@ -348,8 +354,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("form"), afterExport, poplex);
if (type == "import") return cont(pushlex("form"), afterImport, poplex);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
Expand All @@ -367,6 +373,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {

var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "async") return cont(expression);
if (type == "function") return cont(functiondef, maybeop);
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
Expand Down Expand Up @@ -432,7 +439,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
if (type == "async") {
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
return cont(afterprop);
Expand Down
4 changes: 3 additions & 1 deletion mode/markdown/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,10 @@ <h2>Markdown mode</h2>
});
</script>

<p>Optionally depends on the XML mode for properly highlighted inline XML blocks.</p>
<p>You might want to use the <a href="../gfm/index.html">Github-Flavored Markdown mode</a> instead, which adds support for fenced code blocks and a few other things.</p>

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

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

<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#markdown_*">normal</a>, <a href="../../test/index.html#verbose,markdown_*">verbose</a>.</p>
Expand Down
69 changes: 40 additions & 29 deletions mode/markdown/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (modeCfg.underscoresBreakWords === undefined)
modeCfg.underscoresBreakWords = true;

// Turn on fenced code blocks? ("```" to start/end)
if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;
// Use `fencedCodeBlocks` to configure fenced code blocks. false to
// disable, string to specify a precise regexp that the fence should
// match, and true to allow three or more backticks or tildes (as
// per CommonMark).

// Turn on task lists? ("- [ ] " and "- [x] ")
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
Expand Down Expand Up @@ -72,9 +74,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
, ulRE = /^[*\-+]\s+/
, olRE = /^[0-9]+([.)])\s+/
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
, atxHeaderRE = /^(#+)(?: |$)/
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
, setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/
, textRE = /^[^#!\[\]*_\\<>` "'(~]+/;
, textRE = /^[^#!\[\]*_\\<>` "'(~]+/
, fencedCodeRE = new RegExp("^(" + (modeCfg.fencedCodeBlocks === true ? "~~~+|```+" : modeCfg.fencedCodeBlocks) +
")[ \\t]*([\\w+#]*)");

function switchInline(stream, state, f) {
state.f = state.inline = f;
Expand All @@ -86,6 +90,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return f(stream, state);
}

function lineIsEmpty(line) {
return !line || !/\S/.test(line.string)
}

// Blocks

Expand All @@ -110,7 +117,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
// Mark this line as blank
state.thisLineHasContent = false;
state.prevLine = state.thisLine
state.thisLine = null
return null;
}

Expand Down Expand Up @@ -141,7 +149,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var match = null;
if (state.indentationDiff >= 4) {
stream.skipToEnd();
if (prevLineIsIndentedCode || !state.prevLineHasContent) {
if (prevLineIsIndentedCode || lineIsEmpty(state.prevLine)) {
state.indentation -= 4;
state.indentedCode = true;
return code;
Expand All @@ -155,7 +163,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline;
return getType(state);
} else if (state.prevLineHasContent && !state.quote && !prevLineIsList && !prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) {
} else if (!lineIsEmpty(state.prevLine) && !state.quote && !prevLineIsList &&
!prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) {
state.header = match[0].charAt(0) == '=' ? 1 : 2;
if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline;
Expand All @@ -170,15 +179,15 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} else if (stream.match(hrRE, true)) {
state.hr = true;
return hr;
} else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
} else if ((lineIsEmpty(state.prevLine) || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
var listType = null;
if (stream.match(ulRE, true)) {
listType = 'ul';
} else {
stream.match(olRE, true);
listType = 'ol';
}
state.indentation += 4;
state.indentation = stream.column() + stream.current().length;
state.list = true;
state.listDepth++;
if (modeCfg.taskLists && stream.match(taskListRE, false)) {
Expand All @@ -187,9 +196,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
state.f = state.inline;
if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
return getType(state);
} else if (modeCfg.fencedCodeBlocks && stream.match(/^```[ \t]*([\w+#]*)/, true)) {
} else if (modeCfg.fencedCodeBlocks && (match = stream.match(fencedCodeRE, true))) {
state.fencedChars = match[1]
// try switching mode
state.localMode = getMode(RegExp.$1);
state.localMode = getMode(match[2]);
if (state.localMode) state.localState = state.localMode.startState();
state.f = state.block = local;
if (modeCfg.highlightFormatting) state.formatting = "code-block";
Expand All @@ -213,7 +223,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
}

function local(stream, state) {
if (stream.sol() && stream.match("```", false)) {
if (stream.sol() && state.fencedChars && stream.match(state.fencedChars, false)) {
state.localMode = state.localState = null;
state.f = state.block = leavingLocal;
return null;
Expand All @@ -226,9 +236,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
}

function leavingLocal(stream, state) {
stream.match("```");
stream.match(state.fencedChars);
state.block = blockNormal;
state.f = inlineNormal;
state.fencedChars = null;
if (modeCfg.highlightFormatting) state.formatting = "code-block";
state.code = true;
var returnType = getType(state);
Expand Down Expand Up @@ -656,8 +667,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return {
f: blockNormal,

prevLineHasContent: false,
thisLineHasContent: false,
prevLine: null,
thisLine: null,

block: blockNormal,
htmlState: null,
Expand All @@ -680,16 +691,17 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
quote: 0,
trailingSpace: 0,
trailingSpaceNewLine: false,
strikethrough: false
strikethrough: false,
fencedChars: null
};
},

copyState: function(s) {
return {
f: s.f,

prevLineHasContent: s.prevLineHasContent,
thisLineHasContent: s.thisLineHasContent,
prevLine: s.prevLine,
thisLine: s.this,

block: s.block,
htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
Expand All @@ -702,6 +714,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
text: s.text,
formatting: false,
linkTitle: s.linkTitle,
code: s.code,
em: s.em,
strong: s.strong,
strikethrough: s.strikethrough,
Expand All @@ -714,7 +727,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
indentedCode: s.indentedCode,
trailingSpace: s.trailingSpace,
trailingSpaceNewLine: s.trailingSpaceNewLine,
md_inside: s.md_inside
md_inside: s.md_inside,
fencedChars: s.fencedChars
};
},

Expand All @@ -723,28 +737,25 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
// Reset state.formatting
state.formatting = false;

if (stream.sol()) {
var forceBlankLine = !!state.header || state.hr;
if (stream != state.thisLine) {
var forceBlankLine = state.header || state.hr;

// Reset state.header and state.hr
state.header = 0;
state.hr = false;

if (stream.match(/^\s*$/, true) || forceBlankLine) {
state.prevLineHasContent = false;
blankLine(state);
return forceBlankLine ? this.token(stream, state) : null;
} else {
state.prevLineHasContent = state.thisLineHasContent;
state.thisLineHasContent = true;
if (!forceBlankLine) return null
state.prevLine = null
}

state.prevLine = state.thisLine
state.thisLine = stream

// Reset state.taskList
state.taskList = false;

// Reset state.code
state.code = false;

// Reset state.trailingSpace
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
Expand Down
68 changes: 62 additions & 6 deletions mode/markdown/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
var modeHighlightFormatting = CodeMirror.getMode({tabSize: 4}, {name: "markdown", highlightFormatting: true});
function FT(name) { test.mode(name, modeHighlightFormatting, Array.prototype.slice.call(arguments, 1)); }
var modeAtxNoSpace = CodeMirror.getMode({tabSize: 4}, {name: "markdown", allowAtxHeaderWithoutSpace: true});
function AtxNoSpaceTest(name) { test.mode(name, modeAtxNoSpace, Array.prototype.slice.call(arguments, 1)); }
var modeFenced = CodeMirror.getMode({tabSize: 4}, {name: "markdown", fencedCodeBlocks: true});
function FencedTest(name) { test.mode(name, modeFenced, Array.prototype.slice.call(arguments, 1)); }

FT("formatting_emAsterisk",
"[em&formatting&formatting-em *][em foo][em&formatting&formatting-em *]");
Expand Down Expand Up @@ -110,7 +114,7 @@
// Block code using single backtick (shouldn't work)
MT("blockCodeSingleBacktick",
"[comment `]",
"foo",
"[comment foo]",
"[comment `]");

// Unclosed backticks
Expand Down Expand Up @@ -173,6 +177,16 @@
MT("noAtxH1WithoutSpace",
"#5 bolt");

// CommonMark requires a space after # but most parsers don't
AtxNoSpaceTest("atxNoSpaceAllowed_H1NoSpace",
"[header&header-1 #foo]");

AtxNoSpaceTest("atxNoSpaceAllowed_H4NoSpace",
"[header&header-4 ####foo]");

AtxNoSpaceTest("atxNoSpaceAllowed_H1Space",
"[header&header-1 # foo]");

// Inline styles should be parsed inside headers
MT("atxH1inline",
"[header&header-1 # foo ][header&header-1&em *bar*]");
Expand Down Expand Up @@ -498,14 +512,14 @@
"",
" [variable-3 * bar]",
"",
" [variable-2 hello]"
" [variable-3 hello]"
);
MT("listNested",
"[variable-2 * foo]",
"",
" [variable-3 * bar]",
"",
" [variable-3 * foo]"
" [keyword * foo]"
);

// Code followed by text
Expand Down Expand Up @@ -766,10 +780,52 @@
MT("taskList",
"[variable-2 * [ ]] bar]");

MT("fencedCodeBlocks",
"[comment ```]",
MT("noFencedCodeBlocks",
"~~~",
"foo",
"[comment ```]");
"~~~");

FencedTest("fencedCodeBlocks",
"[comment ```]",
"[comment foo]",
"[comment ```]",
"bar");

FencedTest("fencedCodeBlocksMultipleChars",
"[comment `````]",
"[comment foo]",
"[comment ```]",
"[comment foo]",
"[comment `````]",
"bar");

FencedTest("fencedCodeBlocksTildes",
"[comment ~~~]",
"[comment foo]",
"[comment ~~~]",
"bar");

FencedTest("fencedCodeBlocksTildesMultipleChars",
"[comment ~~~~~]",
"[comment ~~~]",
"[comment foo]",
"[comment ~~~~~]",
"bar");

FencedTest("fencedCodeBlocksMultipleChars",
"[comment `````]",
"[comment foo]",
"[comment ```]",
"[comment foo]",
"[comment `````]",
"bar");

FencedTest("fencedCodeBlocksMixed",
"[comment ~~~]",
"[comment ```]",
"[comment foo]",
"[comment ~~~]",
"bar");

// Tests that require XML mode

Expand Down
7 changes: 6 additions & 1 deletion mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
{name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy"]},
{name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"], alias: ["csharp"]},
{name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj"]},
{name: "Closure Stylesheets (GSS)", mime: "text/x-gss", mode: "css", ext: ["gss"]},
{name: "CMake", mime: "text/x-cmake", mode: "cmake", ext: ["cmake", "cmake.in"], file: /^CMakeLists.txt$/},
{name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript", ext: ["coffee"], alias: ["coffee", "coffee-script"]},
{name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]},
Expand Down Expand Up @@ -86,6 +87,7 @@
{name: "Objective C", mime: "text/x-objectivec", mode: "clike", ext: ["m", "mm"]},
{name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]},
{name: "Octave", mime: "text/x-octave", mode: "octave", ext: ["m"]},
{name: "Oz", mime: "text/x-oz", mode: "oz", ext: ["oz"]},
{name: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]},
{name: "PEG.js", mime: "null", mode: "pegjs", ext: ["jsonld"]},
{name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]},
Expand Down Expand Up @@ -143,7 +145,10 @@
{name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd"], alias: ["rss", "wsdl", "xsd"]},
{name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]},
{name: "YAML", mime: "text/x-yaml", mode: "yaml", ext: ["yaml", "yml"], alias: ["yml"]},
{name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]}
{name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]},
{name: "mscgen", mime: "text/x-mscgen", mode: "mscgen", ext: ["mscgen", "mscin", "msc"]},
{name: "xu", mime: "text/x-xu", mode: "mscgen", ext: ["xu"]},
{name: "msgenny", mime: "text/x-msgenny", mode: "mscgen", ext: ["msgenny"]}
];
// Ensure all modes have a mime property for backwards compatibility
for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
Expand Down
65 changes: 65 additions & 0 deletions mode/mscgen/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!doctype html>

<title>CodeMirror: Oz mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">

<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="mscgen.js"></script>
<style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">MscGen</a>
</ul>
</div>

<article>
<h2>MscGen mode</h2>

<div><textarea id="code">
# Sample mscgen program
# See http://www.mcternan.me.uk/mscgen or
# https://sverweij.github.io/mscgen_js for more samples

msc {
# options
hscale="1.2";

# entities/ lifelines
a [label="Entity A"],
b [label="Entity B", linecolor="red", arclinecolor="red", textbgcolor="pink"],
c [label="Entity C"];

# arcs/ messages
a => c [label="doSomething(args)"];
b => c [label="doSomething(args)"];
c >> * [label="everyone asked me", arcskip="1"];
c =>> c [label="doing something"];
c -x * [label="report back", arcskip="1"];
|||;
--- [label="shows's over, however ..."];
b => a [label="did you see c doing something?"];
a -> b [label="nope"];
b :> a [label="shall we ask again?"];
a => b [label="naah"];
...;
}
</textarea></div>

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

<p><strong>MIME types defined:</strong> <code>text/x-mscgen</code></p>
</article>
44 changes: 44 additions & 0 deletions mode/mscgen/index_msgenny.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: msgenny mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="mscgen.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
</head>
<body>
<h1>CodeMirror: msgenny mode</h1>

<div><textarea id="code">
# Sample msgenny program
# https://sverweij.github.io/mscgen_js for more samples

a -> b : a -> b (signal);
a => b : a => b (method);
b >> a : b >> a (return value);
a =>> b : a =>> b (callback);
a -x b : a -x b (lost);
a :> b : a :> b (emphasis);
a .. b : a .. b (dotted);
a -- b : "a -- b straight line";
a note a : a note a\n(note),
b box b : b box b\n(action);
a rbox a : a rbox a\n(reference),
b abox b : b abox b\n(state/ condition);
||| : ||| (empty row);
... : ... (omitted row);
--- : --- (comment);
</textarea></div>

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

<p><strong>MIME types defined:</strong> <code>text/x-msgenny</code></p>
</body>
</html>
70 changes: 70 additions & 0 deletions mode/mscgen/index_xu.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: xu mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="mscgen.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
</head>
<body>
<h1>CodeMirror: xù mode</h1>

<div><textarea id="code">
# test50 - expansions to mscgen to support inline expressions
# for now in a separate language: xù
msc {

hscale="0.8",
width="700";

a,
b [label="change store"],
c,
d [label="necro queue"],
e [label="natalis queue"],
f;

a =>> b [label="get change list()"];
a alt f [label="changes found"] { /* alt is a xu specific keyword*/
b >> a [label="list of changes"];
a =>> c [label="cull old stuff (list of changes)"];
b loop e [label="for each change"] { // loop is xu specific as well...
/*
* Here interesting stuff happens.
* TBD
*/
c =>> b [label="get change()"];
b >> c [label="change"];
c alt e [label="change too old"] {
c =>> d [label="queue(change)"];
--- [label="change newer than latest run"];
c =>> e [label="queue(change)"];
--- [label="all other cases"];
||| [label="leave well alone"];
};
};


c >> a [label="done
processing"];

/* shucks! nothing found ...*/
--- [label="nothing found"];
b >> a [label="nothing"];
a note a [label="silent exit"];
};
}
</textarea></div>

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

<p><strong>MIME types defined:</strong> <code>text/x-xu</code></p>
</body>
</html>
169 changes: 169 additions & 0 deletions mode/mscgen/mscgen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

// mode(s) for the sequence chart dsl's mscgen, xù and msgenny
// For more information on mscgen, see the site of the original author:
// http://www.mcternan.me.uk/mscgen
//
// This mode for mscgen and the two derivative languages were
// originally made for use in the mscgen_js interpreter
// (https://sverweij.github.io/mscgen_js)

(function(mod) {
if ( typeof exports == "object" && typeof module == "object")// CommonJS
mod(require("../../lib/codemirror"));
else if ( typeof define == "function" && define.amd)// AMD
define(["../../lib/codemirror"], mod);
else// Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

var languages = {
mscgen: {
"keywords" : ["msc"],
"options" : ["hscale", "width", "arcgradient", "wordwraparcs"],
"attributes" : ["label", "idurl", "id", "url", "linecolor", "linecolour", "textcolor", "textcolour", "textbgcolor", "textbgcolour", "arclinecolor", "arclinecolour", "arctextcolor", "arctextcolour", "arctextbgcolor", "arctextbgcolour", "arcskip"],
"brackets" : ["\\{", "\\}"], // [ and ] are brackets too, but these get handled in with lists
"arcsWords" : ["note", "abox", "rbox", "box"],
"arcsOthers" : ["\\|\\|\\|", "\\.\\.\\.", "---", "--", "<->", "==", "<<=>>", "<=>", "\\.\\.", "<<>>", "::", "<:>", "->", "=>>", "=>", ">>", ":>", "<-", "<<=", "<=", "<<", "<:", "x-", "-x"],
"singlecomment" : ["//", "#"],
"operators" : ["="]
},
xu: {
"keywords" : ["msc"],
"options" : ["hscale", "width", "arcgradient", "wordwraparcs", "watermark"],
"attributes" : ["label", "idurl", "id", "url", "linecolor", "linecolour", "textcolor", "textcolour", "textbgcolor", "textbgcolour", "arclinecolor", "arclinecolour", "arctextcolor", "arctextcolour", "arctextbgcolor", "arctextbgcolour", "arcskip"],
"brackets" : ["\\{", "\\}"], // [ and ] are brackets too, but these get handled in with lists
"arcsWords" : ["note", "abox", "rbox", "box", "alt", "else", "opt", "break", "par", "seq", "strict", "neg", "critical", "ignore", "consider", "assert", "loop", "ref", "exc"],
"arcsOthers" : ["\\|\\|\\|", "\\.\\.\\.", "---", "--", "<->", "==", "<<=>>", "<=>", "\\.\\.", "<<>>", "::", "<:>", "->", "=>>", "=>", ">>", ":>", "<-", "<<=", "<=", "<<", "<:", "x-", "-x"],
"singlecomment" : ["//", "#"],
"operators" : ["="]
},
msgenny: {
"keywords" : null,
"options" : ["hscale", "width", "arcgradient", "wordwraparcs", "watermark"],
"attributes" : null,
"brackets" : ["\\{", "\\}"],
"arcsWords" : ["note", "abox", "rbox", "box", "alt", "else", "opt", "break", "par", "seq", "strict", "neg", "critical", "ignore", "consider", "assert", "loop", "ref", "exc"],
"arcsOthers" : ["\\|\\|\\|", "\\.\\.\\.", "---", "--", "<->", "==", "<<=>>", "<=>", "\\.\\.", "<<>>", "::", "<:>", "->", "=>>", "=>", ">>", ":>", "<-", "<<=", "<=", "<<", "<:", "x-", "-x"],
"singlecomment" : ["//", "#"],
"operators" : ["="]
}
}

CodeMirror.defineMode("mscgen", function(_, modeConfig) {
var language = languages[modeConfig && modeConfig.language || "mscgen"]
return {
startState: startStateFn,
copyState: copyStateFn,
token: produceTokenFunction(language),
lineComment : "#",
blockCommentStart : "/*",
blockCommentEnd : "*/"
};
});

CodeMirror.defineMIME("text/x-mscgen", "mscgen");
CodeMirror.defineMIME("text/x-xu", {name: "mscgen", language: "xu"});
CodeMirror.defineMIME("text/x-msgenny", {name: "mscgen", language: "msgenny"});

function wordRegexpBoundary(pWords) {
return new RegExp("\\b((" + pWords.join(")|(") + "))\\b", "i");
}

function wordRegexp(pWords) {
return new RegExp("((" + pWords.join(")|(") + "))", "i");
}

function startStateFn() {
return {
inComment : false,
inString : false,
inAttributeList : false,
inScript : false
};
}

function copyStateFn(pState) {
return {
inComment : pState.inComment,
inString : pState.inString,
inAttributeList : pState.inAttributeList,
inScript : pState.inScript
};
}

function produceTokenFunction(pConfig) {

return function(pStream, pState) {
if (pStream.match(wordRegexp(pConfig.brackets), true, true)) {
return "bracket";
}
/* comments */
if (!pState.inComment) {
if (pStream.match(/\/\*[^\*\/]*/, true, true)) {
pState.inComment = true;
return "comment";
}
if (pStream.match(wordRegexp(pConfig.singlecomment), true, true)) {
pStream.skipToEnd();
return "comment";
}
}
if (pState.inComment) {
if (pStream.match(/[^\*\/]*\*\//, true, true))
pState.inComment = false;
else
pStream.skipToEnd();
return "comment";
}
/* strings */
if (!pState.inString && pStream.match(/\"(\\\"|[^\"])*/, true, true)) {
pState.inString = true;
return "string";
}
if (pState.inString) {
if (pStream.match(/[^\"]*\"/, true, true))
pState.inString = false;
else
pStream.skipToEnd();
return "string";
}
/* keywords & operators */
if (!!pConfig.keywords && pStream.match(wordRegexpBoundary(pConfig.keywords), true, true))
return "keyword";

if (pStream.match(wordRegexpBoundary(pConfig.options), true, true))
return "keyword";

if (pStream.match(wordRegexpBoundary(pConfig.arcsWords), true, true))
return "keyword";

if (pStream.match(wordRegexp(pConfig.arcsOthers), true, true))
return "keyword";

if (!!pConfig.operators && pStream.match(wordRegexp(pConfig.operators), true, true))
return "operator";

/* attribute lists */
if (!pConfig.inAttributeList && !!pConfig.attributes && pStream.match(/\[/, true, true)) {
pConfig.inAttributeList = true;
return "bracket";
}
if (pConfig.inAttributeList) {
if (pConfig.attributes !== null && pStream.match(wordRegexpBoundary(pConfig.attributes), true, true)) {
return "attribute";
}
if (pStream.match(/]/, true, true)) {
pConfig.inAttributeList = false;
return "bracket";
}
}

pStream.next();
return "base";
};
}

});
75 changes: 75 additions & 0 deletions mode/mscgen/mscgen_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function() {
var mode = CodeMirror.getMode({indentUnit: 2}, "mscgen");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }

MT("empty chart",
"[keyword msc][bracket {]",
"[base ]",
"[bracket }]"
);

MT("comments",
"[comment // a single line comment]",
"[comment # another single line comment /* and */ ignored here]",
"[comment /* A multi-line comment even though it contains]",
"[comment msc keywords and \"quoted text\"*/]");

MT("strings",
"[string \"// a string\"]",
"[string \"a string running over]",
"[string two lines\"]",
"[string \"with \\\"escaped quote\"]"
);

MT("xù/ msgenny keywords classify as 'base'",
"[base watermark]",
"[base alt loop opt ref else break par seq assert]"
);

MT("mscgen options classify as keyword",
"[keyword hscale]", "[keyword width]", "[keyword arcgradient]", "[keyword wordwraparcs]"
);

MT("mscgen arcs classify as keyword",
"[keyword note]","[keyword abox]","[keyword rbox]","[keyword box]",
"[keyword |||...---]", "[keyword ..--==::]",
"[keyword ->]", "[keyword <-]", "[keyword <->]",
"[keyword =>]", "[keyword <=]", "[keyword <=>]",
"[keyword =>>]", "[keyword <<=]", "[keyword <<=>>]",
"[keyword >>]", "[keyword <<]", "[keyword <<>>]",
"[keyword -x]", "[keyword x-]", "[keyword -X]", "[keyword X-]",
"[keyword :>]", "[keyword <:]", "[keyword <:>]"
);

MT("within an attribute list, attributes classify as attribute",
"[bracket [[][attribute label]",
"[attribute id]","[attribute url]","[attribute idurl]",
"[attribute linecolor]","[attribute linecolour]","[attribute textcolor]","[attribute textcolour]","[attribute textbgcolor]","[attribute textbgcolour]",
"[attribute arclinecolor]","[attribute arclinecolour]","[attribute arctextcolor]","[attribute arctextcolour]","[attribute arctextbgcolor]","[attribute arctextbgcolour]",
"[attribute arcskip][bracket ]]]"
);

MT("outside an attribute list, attributes classify as base",
"[base label]",
"[base id]","[base url]","[base idurl]",
"[base linecolor]","[base linecolour]","[base textcolor]","[base textcolour]","[base textbgcolor]","[base textbgcolour]",
"[base arclinecolor]","[base arclinecolour]","[base arctextcolor]","[base arctextcolour]","[base arctextbgcolor]","[base arctextbgcolour]",
"[base arcskip]"
);

MT("a typical program",
"[comment # typical mscgen program]",
"[keyword msc][base ][bracket {]",
"[keyword wordwraparcs][operator =][string \"true\"][base , ][keyword hscale][operator =][string \"0.8\"][keyword arcgradient][operator =][base 30;]",
"[base a][bracket [[][attribute label][operator =][string \"Entity A\"][bracket ]]][base ,]",
"[base b][bracket [[][attribute label][operator =][string \"Entity B\"][bracket ]]][base ,]",
"[base c][bracket [[][attribute label][operator =][string \"Entity C\"][bracket ]]][base ;]",
"[base a ][keyword =>>][base b][bracket [[][attribute label][operator =][string \"Hello entity B\"][bracket ]]][base ;]",
"[base a ][keyword <<][base b][bracket [[][attribute label][operator =][string \"Here's an answer dude!\"][bracket ]]][base ;]",
"[base c ][keyword :>][base *][bracket [[][attribute label][operator =][string \"What about me?\"][base , ][attribute textcolor][operator =][base red][bracket ]]][base ;]",
"[bracket }]"
);
})();
71 changes: 71 additions & 0 deletions mode/mscgen/msgenny_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function() {
var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-msgenny");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "msgenny"); }

MT("comments",
"[comment // a single line comment]",
"[comment # another single line comment /* and */ ignored here]",
"[comment /* A multi-line comment even though it contains]",
"[comment msc keywords and \"quoted text\"*/]");

MT("strings",
"[string \"// a string\"]",
"[string \"a string running over]",
"[string two lines\"]",
"[string \"with \\\"escaped quote\"]"
);

MT("xù/ msgenny keywords classify as 'keyword'",
"[keyword watermark]",
"[keyword alt]","[keyword loop]","[keyword opt]","[keyword ref]","[keyword else]","[keyword break]","[keyword par]","[keyword seq]","[keyword assert]"
);

MT("mscgen options classify as keyword",
"[keyword hscale]", "[keyword width]", "[keyword arcgradient]", "[keyword wordwraparcs]"
);

MT("mscgen arcs classify as keyword",
"[keyword note]","[keyword abox]","[keyword rbox]","[keyword box]",
"[keyword |||...---]", "[keyword ..--==::]",
"[keyword ->]", "[keyword <-]", "[keyword <->]",
"[keyword =>]", "[keyword <=]", "[keyword <=>]",
"[keyword =>>]", "[keyword <<=]", "[keyword <<=>>]",
"[keyword >>]", "[keyword <<]", "[keyword <<>>]",
"[keyword -x]", "[keyword x-]", "[keyword -X]", "[keyword X-]",
"[keyword :>]", "[keyword <:]", "[keyword <:>]"
);

MT("within an attribute list, mscgen/ xù attributes classify as base",
"[base [[label]",
"[base idurl id url]",
"[base linecolor linecolour textcolor textcolour textbgcolor textbgcolour]",
"[base arclinecolor arclinecolour arctextcolor arctextcolour arctextbgcolor arctextbgcolour]",
"[base arcskip]]]"
);

MT("outside an attribute list, mscgen/ xù attributes classify as base",
"[base label]",
"[base idurl id url]",
"[base linecolor linecolour textcolor textcolour textbgcolor textbgcolour]",
"[base arclinecolor arclinecolour arctextcolor arctextcolour arctextbgcolor arctextbgcolour]",
"[base arcskip]"
);

MT("a typical program",
"[comment # typical msgenny program]",
"[keyword wordwraparcs][operator =][string \"true\"][base , ][keyword hscale][operator =][string \"0.8\"][base , ][keyword arcgradient][operator =][base 30;]",
"[base a : ][string \"Entity A\"][base ,]",
"[base b : Entity B,]",
"[base c : Entity C;]",
"[base a ][keyword =>>][base b: ][string \"Hello entity B\"][base ;]",
"[base a ][keyword alt][base c][bracket {]",
"[base a ][keyword <<][base b: ][string \"Here's an answer dude!\"][base ;]",
"[keyword ---][base : ][string \"sorry, won't march - comm glitch\"]",
"[base a ][keyword x-][base b: ][string \"Here's an answer dude! (won't arrive...)\"][base ;]",
"[bracket }]",
"[base c ][keyword :>][base *: What about me?;]"
);
})();
75 changes: 75 additions & 0 deletions mode/mscgen/xu_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function() {
var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-xu");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "xu"); }

MT("empty chart",
"[keyword msc][bracket {]",
"[base ]",
"[bracket }]"
);

MT("comments",
"[comment // a single line comment]",
"[comment # another single line comment /* and */ ignored here]",
"[comment /* A multi-line comment even though it contains]",
"[comment msc keywords and \"quoted text\"*/]");

MT("strings",
"[string \"// a string\"]",
"[string \"a string running over]",
"[string two lines\"]",
"[string \"with \\\"escaped quote\"]"
);

MT("xù/ msgenny keywords classify as 'keyword'",
"[keyword watermark]",
"[keyword alt]","[keyword loop]","[keyword opt]","[keyword ref]","[keyword else]","[keyword break]","[keyword par]","[keyword seq]","[keyword assert]"
);

MT("mscgen options classify as keyword",
"[keyword hscale]", "[keyword width]", "[keyword arcgradient]", "[keyword wordwraparcs]"
);

MT("mscgen arcs classify as keyword",
"[keyword note]","[keyword abox]","[keyword rbox]","[keyword box]",
"[keyword |||...---]", "[keyword ..--==::]",
"[keyword ->]", "[keyword <-]", "[keyword <->]",
"[keyword =>]", "[keyword <=]", "[keyword <=>]",
"[keyword =>>]", "[keyword <<=]", "[keyword <<=>>]",
"[keyword >>]", "[keyword <<]", "[keyword <<>>]",
"[keyword -x]", "[keyword x-]", "[keyword -X]", "[keyword X-]",
"[keyword :>]", "[keyword <:]", "[keyword <:>]"
);

MT("within an attribute list, attributes classify as attribute",
"[bracket [[][attribute label]",
"[attribute id]","[attribute url]","[attribute idurl]",
"[attribute linecolor]","[attribute linecolour]","[attribute textcolor]","[attribute textcolour]","[attribute textbgcolor]","[attribute textbgcolour]",
"[attribute arclinecolor]","[attribute arclinecolour]","[attribute arctextcolor]","[attribute arctextcolour]","[attribute arctextbgcolor]","[attribute arctextbgcolour]",
"[attribute arcskip][bracket ]]]"
);

MT("outside an attribute list, attributes classify as base",
"[base label]",
"[base id]","[base url]","[base idurl]",
"[base linecolor]","[base linecolour]","[base textcolor]","[base textcolour]","[base textbgcolor]","[base textbgcolour]",
"[base arclinecolor]","[base arclinecolour]","[base arctextcolor]","[base arctextcolour]","[base arctextbgcolor]","[base arctextbgcolour]",
"[base arcskip]"
);

MT("a typical program",
"[comment # typical mscgen program]",
"[keyword msc][base ][bracket {]",
"[keyword wordwraparcs][operator =][string \"true\"][keyword hscale][operator =][string \"0.8\"][keyword arcgradient][operator =][base 30;]",
"[base a][bracket [[][attribute label][operator =][string \"Entity A\"][bracket ]]][base ,]",
"[base b][bracket [[][attribute label][operator =][string \"Entity B\"][bracket ]]][base ,]",
"[base c][bracket [[][attribute label][operator =][string \"Entity C\"][bracket ]]][base ;]",
"[base a ][keyword =>>][base b][bracket [[][attribute label][operator =][string \"Hello entity B\"][bracket ]]][base ;]",
"[base a ][keyword <<][base b][bracket [[][attribute label][operator =][string \"Here's an answer dude!\"][bracket ]]][base ;]",
"[base c ][keyword :>][base *][bracket [[][attribute label][operator =][string \"What about me?\"][base , ][attribute textcolor][operator =][base red][bracket ]]][base ;]",
"[bracket }]"
);
})();
59 changes: 59 additions & 0 deletions mode/oz/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!doctype html>

<title>CodeMirror: Oz mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">

<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="oz.js"></script>
<script type="text/javascript" src="../../addon/runmode/runmode.js"></script>
<style>
.CodeMirror {border: 1px solid #aaa;}
</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">Oz</a>
</ul>
</div>

<article>
<h2>Oz mode</h2>
<textarea id="code" name="code">
declare
fun {Ints N Max}
if N == Max then nil
else
{Delay 1000}
N|{Ints N+1 Max}
end
end

fun {Sum S Stream}
case Stream of nil then S
[] H|T then S|{Sum H+S T} end
end

local X Y in
thread X = {Ints 0 1000} end
thread Y = {Sum 0 X} end
{Browse Y}
end
</textarea>
<p>MIME type defined: <code>text/x-oz</code>.</p>

<script type="text/javascript">
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
mode: "text/x-oz",
readOnly: false
});
</script>
</article>
252 changes: 252 additions & 0 deletions mode/oz/oz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("oz", function (conf) {

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

var singleOperators = /[\^@!\|<>#~\.\*\-\+\\/,=]/;
var doubleOperators = /(<-)|(:=)|(=<)|(>=)|(<=)|(<:)|(>:)|(=:)|(\\=)|(\\=:)|(!!)|(==)|(::)/;
var tripleOperators = /(:::)|(\.\.\.)|(=<:)|(>=:)/;

var middle = ["in", "then", "else", "of", "elseof", "elsecase", "elseif", "catch",
"finally", "with", "require", "prepare", "import", "export", "define", "do"];
var end = ["end"];

var atoms = wordRegexp(["true", "false", "nil", "unit"]);
var commonKeywords = wordRegexp(["andthen", "at", "attr", "declare", "feat", "from", "lex",
"mod", "mode", "orelse", "parser", "prod", "prop", "scanner", "self", "syn", "token"]);
var openingKeywords = wordRegexp(["local", "proc", "fun", "case", "class", "if", "cond", "or", "dis",
"choice", "not", "thread", "try", "raise", "lock", "for", "suchthat", "meth", "functor"]);
var middleKeywords = wordRegexp(middle);
var endKeywords = wordRegexp(end);

// Tokenizers
function tokenBase(stream, state) {
if (stream.eatSpace()) {
return null;
}

// Brackets
if(stream.match(/[{}]/)) {
return "bracket";
}

// Special [] keyword
if (stream.match(/(\[])/)) {
return "keyword"
}

// Operators
if (stream.match(tripleOperators) || stream.match(doubleOperators)) {
return "operator";
}

// Atoms
if(stream.match(atoms)) {
return 'atom';
}

// Opening keywords
var matched = stream.match(openingKeywords);
if (matched) {
if (!state.doInCurrentLine)
state.currentIndent++;
else
state.doInCurrentLine = false;

// Special matching for signatures
if(matched[0] == "proc" || matched[0] == "fun")
state.tokenize = tokenFunProc;
else if(matched[0] == "class")
state.tokenize = tokenClass;
else if(matched[0] == "meth")
state.tokenize = tokenMeth;

return 'keyword';
}

// Middle and other keywords
if (stream.match(middleKeywords) || stream.match(commonKeywords)) {
return "keyword"
}

// End keywords
if (stream.match(endKeywords)) {
state.currentIndent--;
return 'keyword';
}

// Eat the next char for next comparisons
var ch = stream.next();

// Strings
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}

// Numbers
if (/[~\d]/.test(ch)) {
if (ch == "~") {
if(! /^[0-9]/.test(stream.peek()))
return null;
else if (( stream.next() == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) || stream.match(/^[0-9]*(\.[0-9]+)?([eE][~+]?[0-9]+)?/))
return "number";
}

if ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) || stream.match(/^[0-9]*(\.[0-9]+)?([eE][~+]?[0-9]+)?/))
return "number";

return null;
}

// Comments
if (ch == "%") {
stream.skipToEnd();
return 'comment';
}
else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
}
}

// Single operators
if(singleOperators.test(ch)) {
return "operator";
}

// If nothing match, we skip the entire alphanumerical block
stream.eatWhile(/\w/);

return "variable";
}

function tokenClass(stream, state) {
if (stream.eatSpace()) {
return null;
}
stream.match(/([A-Z][A-Za-z0-9_]*)|(`.+`)/);
state.tokenize = tokenBase;
return "variable-3"
}

function tokenMeth(stream, state) {
if (stream.eatSpace()) {
return null;
}
stream.match(/([a-zA-Z][A-Za-z0-9_]*)|(`.+`)/);
state.tokenize = tokenBase;
return "def"
}

function tokenFunProc(stream, state) {
if (stream.eatSpace()) {
return null;
}

if(!state.hasPassedFirstStage && stream.eat("{")) {
state.hasPassedFirstStage = true;
return "bracket";
}
else if(state.hasPassedFirstStage) {
stream.match(/([A-Z][A-Za-z0-9_]*)|(`.+`)|\$/);
state.hasPassedFirstStage = false;
state.tokenize = tokenBase;
return "def"
}
else {
state.tokenize = tokenBase;
return null;
}
}

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

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

function buildElectricInputRegEx() {
// Reindentation should occur on [] or on a match of any of
// the block closing keywords, at the end of a line.
var allClosings = middle.concat(end);
return new RegExp("[\\[\\]]|(" + allClosings.join("|") + ")$");
}

return {

startState: function () {
return {
tokenize: tokenBase,
currentIndent: 0,
doInCurrentLine: false,
hasPassedFirstStage: false
};
},

token: function (stream, state) {
if (stream.sol())
state.doInCurrentLine = 0;

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

indent: function (state, textAfter) {
var trueText = textAfter.replace(/^\s+|\s+$/g, '');

if (trueText.match(endKeywords) || trueText.match(middleKeywords) || trueText.match(/(\[])/))
return conf.indentUnit * (state.currentIndent - 1);

if (state.currentIndent < 0)
return 0;

return state.currentIndent * conf.indentUnit;
},
fold: "indent",
electricInput: buildElectricInputRegEx(),
lineComment: "%",
blockCommentStart: "/*",
blockCommentEnd: "*/"
};
});

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

});
Loading