10 changes: 7 additions & 3 deletions keymap/sublime.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
map["Shift-" + ctrl + "K"] = "deleteLine";

function insertLine(cm, above) {
if (cm.isReadOnly()) return CodeMirror.Pass
cm.operation(function() {
var len = cm.listSelections().length, newSelection = [], last = -1;
for (var i = 0; i < len; i++) {
Expand All @@ -123,9 +124,9 @@
});
}

cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { insertLine(cm, false); };
cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { return insertLine(cm, false); };

cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { insertLine(cm, true); };
cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { return insertLine(cm, true); };

function wordAt(cm, pos) {
var start = pos.ch, end = start, line = cm.getLine(pos.line);
Expand Down Expand Up @@ -192,6 +193,7 @@
var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-";

cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) {
if (cm.isReadOnly()) return CodeMirror.Pass
var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], from = range.from().line - 1, to = range.to().line;
Expand All @@ -218,6 +220,7 @@
};

cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) {
if (cm.isReadOnly()) return CodeMirror.Pass
var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
for (var i = ranges.length - 1; i >= 0; i--) {
var range = ranges[i], from = range.to().line + 1, to = range.from().line;
Expand All @@ -240,7 +243,7 @@
});
};

map[ctrl + "/"] = function(cm) {
cmds[map[ctrl + "/"] = "toggleCommentIndented"] = function(cm) {
cm.toggleComment({ indent: true });
}

Expand Down Expand Up @@ -289,6 +292,7 @@
map[ctrl + "T"] = "transposeChars";

function sortLines(cm, caseSensitive) {
if (cm.isReadOnly()) return CodeMirror.Pass
var ranges = cm.listSelections(), toSort = [], selected;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
Expand Down
31 changes: 16 additions & 15 deletions keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,7 @@
// Keypress character binding of format "'a'"
return key.charAt(1);
}
var pieces = key.split('-');
if (/-$/.test(key)) {
// If the - key was typed, split will result in 2 extra empty strings
// in the array. Replace them with 1 '-'.
pieces.splice(-2, 2, '-');
}
var pieces = key.split(/-(?!$)/);
var lastPiece = pieces[pieces.length - 1];
if (pieces.length == 1 && pieces[0].length == 1) {
// No-modifier bindings use literal character bindings above. Skip.
Expand Down Expand Up @@ -1959,13 +1954,21 @@
text = text.slice(0, - match[0].length);
}
}
var wasLastLine = head.line - 1 == cm.lastLine();
cm.replaceRange('', anchor, head);
if (args.linewise && !wasLastLine) {
var prevLineEnd = new Pos(anchor.line - 1, Number.MAX_VALUE);
var wasLastLine = cm.firstLine() == cm.lastLine();
if (head.line > cm.lastLine() && args.linewise && !wasLastLine) {
cm.replaceRange('', prevLineEnd, head);
} else {
cm.replaceRange('', anchor, head);
}
if (args.linewise) {
// Push the next line back down, if there is a next line.
CodeMirror.commands.newlineAndIndent(cm);
// null ch so setCursor moves to end of line.
anchor.ch = null;
if (!wasLastLine) {
cm.setCursor(prevLineEnd);
CodeMirror.commands.newlineAndIndent(cm);
}
// make sure cursor ends up at the end of the line.
anchor.ch = Number.MAX_VALUE;
}
finalHead = anchor;
} else {
Expand Down Expand Up @@ -2144,9 +2147,7 @@
switch (actionArgs.position) {
case 'center': y = y - (height / 2) + lineHeight;
break;
case 'bottom': y = y - height + lineHeight*1.4;
break;
case 'top': y = y + lineHeight*0.4;
case 'bottom': y = y - height + lineHeight;
break;
}
cm.scrollTo(null, y);
Expand Down
2 changes: 1 addition & 1 deletion lib/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
}

/* The fake, visible scrollbars. Used to force redraw during scrolling
before actuall scrolling happens, thus preventing shaking and
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
Expand Down
167 changes: 91 additions & 76 deletions lib/codemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
else if (typeof define == "function" && define.amd) // AMD
return define([], mod);
else // Plain browser env
this.CodeMirror = mod();
(this || window).CodeMirror = mod();
})(function() {
"use strict";

Expand Down Expand Up @@ -823,7 +823,7 @@
// given line.
function updateWidgetHeight(line) {
if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
line.widgets[i].height = line.widgets[i].node.offsetHeight;
line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight;
}

// Do a bulk-read of the DOM positions and sizes needed to draw the
Expand Down Expand Up @@ -1094,10 +1094,6 @@
if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
}

function isReadOnly(cm) {
return cm.options.readOnly || cm.doc.cantEdit;
}

// This will be set to an array of strings when copying, so that,
// when pasting, we know what kind of selections the copied text
// was made out of.
Expand Down Expand Up @@ -1152,7 +1148,7 @@
var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
if (pasted) {
e.preventDefault();
if (!isReadOnly(cm) && !cm.options.disableInput)
if (!cm.isReadOnly() && !cm.options.disableInput)
runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); });
return true;
}
Expand Down Expand Up @@ -1255,7 +1251,7 @@
});

on(te, "paste", function(e) {
if (handlePaste(e, cm)) return true;
if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return

cm.state.pasteIncoming = true;
input.fastPoll();
Expand Down Expand Up @@ -1289,7 +1285,7 @@
on(te, "copy", prepareCopyCut);

on(display.scroller, "paste", function(e) {
if (eventInWidget(display, e)) return;
if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return;
cm.state.pasteIncoming = true;
input.focus();
});
Expand Down Expand Up @@ -1423,7 +1419,7 @@
// in which case reading its value would be expensive.
if (this.contextMenuPending || !cm.state.focused ||
(hasSelection(input) && !prevInput && !this.composing) ||
isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
return false;

var text = input.value;
Expand Down Expand Up @@ -1574,7 +1570,9 @@
var div = input.div = display.lineDiv;
disableBrowserMagic(div);

on(div, "paste", function(e) { handlePaste(e, cm); })
on(div, "paste", function(e) {
if (!signalDOMEvent(cm, e)) handlePaste(e, cm);
})

on(div, "compositionstart", function(e) {
var data = e.data;
Expand Down Expand Up @@ -1612,7 +1610,7 @@

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

Expand Down Expand Up @@ -1692,8 +1690,13 @@
try { var rng = range(start.node, start.offset, end.offset, end.node); }
catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
if (rng) {
sel.removeAllRanges();
sel.addRange(rng);
if (!gecko && this.cm.state.focused) {
sel.collapse(start.node, start.offset);
if (!rng.collapsed) sel.addRange(rng);
} else {
sel.removeAllRanges();
sel.addRange(rng);
}
if (old && sel.anchorNode == null) sel.addRange(old);
else if (gecko) this.startGracePeriod();
}
Expand Down Expand Up @@ -1837,7 +1840,7 @@
this.div.focus();
},
applyComposition: function(composing) {
if (isReadOnly(this.cm))
if (this.cm.isReadOnly())
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);
Expand All @@ -1849,7 +1852,7 @@

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

Expand Down Expand Up @@ -2154,15 +2157,16 @@

// Give beforeSelectionChange handlers a change to influence a
// selection update.
function filterSelectionChange(doc, sel) {
function filterSelectionChange(doc, sel, options) {
var obj = {
ranges: sel.ranges,
update: function(ranges) {
this.ranges = [];
for (var i = 0; i < ranges.length; i++)
this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
clipPos(doc, ranges[i].head));
}
},
origin: options && options.origin
};
signal(doc, "beforeSelectionChange", doc, obj);
if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
Expand All @@ -2188,7 +2192,7 @@

function setSelectionNoUndo(doc, sel, options) {
if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
sel = filterSelectionChange(doc, sel);
sel = filterSelectionChange(doc, sel, options);

var bias = options && options.bias ||
(cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
Expand Down Expand Up @@ -2222,8 +2226,9 @@
var out;
for (var i = 0; i < sel.ranges.length; i++) {
var range = sel.ranges[i];
var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
var newHead = skipAtomic(doc, range.head, bias, mayClear);
var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
if (out || newAnchor != range.anchor || newHead != range.head) {
if (!out) out = sel.ranges.slice(0, i);
out[i] = new Range(newAnchor, newHead);
Expand All @@ -2232,54 +2237,59 @@
return out ? normalizeSelection(out, sel.primIndex) : sel;
}

// Ensure a given position is not inside an atomic range.
function skipAtomic(doc, pos, bias, mayClear) {
var flipped = false, curPos = pos;
var dir = bias || 1;
doc.cantEdit = false;
search: for (;;) {
var line = getLine(doc, curPos.line);
if (line.markedSpans) {
for (var i = 0; i < line.markedSpans.length; ++i) {
var sp = line.markedSpans[i], m = sp.marker;
if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
(sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
if (mayClear) {
signal(m, "beforeCursorEnter");
if (m.explicitlyCleared) {
if (!line.markedSpans) break;
else {--i; continue;}
}
}
if (!m.atomic) continue;
var newPos = m.find(dir < 0 ? -1 : 1);
if (cmp(newPos, curPos) == 0) {
newPos.ch += dir;
if (newPos.ch < 0) {
if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
else newPos = null;
} else if (newPos.ch > line.text.length) {
if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
else newPos = null;
}
if (!newPos) {
if (flipped) {
// Driven in a corner -- no valid cursor position found at all
// -- try again *with* clearing, if we didn't already
if (!mayClear) return skipAtomic(doc, pos, bias, true);
// Otherwise, turn off editing until further notice, and return the start of the doc
doc.cantEdit = true;
return Pos(doc.first, 0);
}
flipped = true; newPos = pos; dir = -dir;
}
}
curPos = newPos;
continue search;
function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
var line = getLine(doc, pos.line);
if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
var sp = line.markedSpans[i], m = sp.marker;
if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
(sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
if (mayClear) {
signal(m, "beforeCursorEnter");
if (m.explicitlyCleared) {
if (!line.markedSpans) break;
else {--i; continue;}
}
}
if (!m.atomic) continue;

if (oldPos) {
var near = m.find(dir < 0 ? 1 : -1), diff;
if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) near = movePos(doc, near, -dir, line);
if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
return skipAtomicInner(doc, near, pos, dir, mayClear);
}

var far = m.find(dir < 0 ? -1 : 1);
if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) far = movePos(doc, far, dir, line);
return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null;
}
return curPos;
}
return pos;
}

// Ensure a given position is not inside an atomic range.
function skipAtomic(doc, pos, oldPos, bias, mayClear) {
var dir = bias || 1;
var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
(!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
(!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
if (!found) {
doc.cantEdit = true;
return Pos(doc.first, 0);
}
return found;
}

function movePos(doc, pos, dir, line) {
if (dir < 0 && pos.ch == 0) {
if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1));
else return null;
} else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0);
else return null;
} else {
return new Pos(pos.line, pos.ch + dir);
}
}

Expand Down Expand Up @@ -3608,7 +3618,7 @@
}

var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
type == "single" && (contained = sel.contains(start)) > -1 &&
(cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) &&
(cmp(contained.to(), start) > 0 || start.xRel < 0))
Expand Down Expand Up @@ -3832,7 +3842,7 @@
e_preventDefault(e);
if (ie) lastDrop = +new Date;
var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
if (!pos || isReadOnly(cm)) return;
if (!pos || cm.isReadOnly()) return;
// Might be a file drop, in which case we simply extract the text
// and insert it.
if (files && files.length && window.FileReader && window.File) {
Expand Down Expand Up @@ -4071,7 +4081,7 @@
cm.display.input.ensurePolled();
var prevShift = cm.display.shift, done = false;
try {
if (isReadOnly(cm)) cm.state.suppressEdits = true;
if (cm.isReadOnly()) cm.state.suppressEdits = true;
if (dropShift) cm.display.shift = false;
done = bound(cm) != Pass;
} finally {
Expand Down Expand Up @@ -4844,7 +4854,7 @@
if (dir > 0 && !moveOnce(!first)) break;
}
}
var result = skipAtomic(doc, Pos(line, ch), origDir, true);
var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true);
if (!possible) result.hitSide = true;
return result;
}
Expand Down Expand Up @@ -5232,6 +5242,7 @@
signal(this, "overwriteToggle", this, this.state.overwrite);
},
hasFocus: function() { return this.display.input.getField() == activeElt(); },
isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); },

scrollTo: methodOp(function(x, y) {
if (x != null || y != null) resolveScrollToPos(this);
Expand Down Expand Up @@ -6668,7 +6679,7 @@
parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;";
removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
}
return widget.height = widget.node.offsetHeight;
return widget.height = widget.node.parentNode.offsetHeight;
}

function addLineWidget(doc, handle, node, options) {
Expand Down Expand Up @@ -7078,7 +7089,7 @@
if (nextChange == pos) { // Update current marker set
spanStyle = spanEndStyle = spanStartStyle = title = css = "";
collapsed = null; nextChange = Infinity;
var foundBookmarks = [];
var foundBookmarks = [], endStyles
for (var j = 0; j < spans.length; ++j) {
var sp = spans[j], m = sp.marker;
if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
Expand All @@ -7091,14 +7102,17 @@
if (m.className) spanStyle += " " + m.className;
if (m.css) css = (css ? css + ";" : "") + m.css;
if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to)
if (m.title && !title) title = m.title;
if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
collapsed = sp;
} else if (sp.from > pos && nextChange > sp.from) {
nextChange = sp.from;
}
}
if (endStyles) for (var j = 0; j < endStyles.length; j += 2)
if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j]

if (collapsed && (collapsed.from || 0) == pos) {
buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
collapsed.marker, collapsed.from == null);
Expand Down Expand Up @@ -7446,10 +7460,11 @@
extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
}),
extendSelections: docMethodOp(function(heads, options) {
extendSelections(this, clipPosArray(this, heads, options));
extendSelections(this, clipPosArray(this, heads), options);
}),
extendSelectionsBy: docMethodOp(function(f, options) {
extendSelections(this, map(this.sel.ranges, f), options);
var heads = map(this.sel.ranges, f);
extendSelections(this, clipPosArray(this, heads), options);
}),
setSelections: docMethodOp(function(ranges, primary, options) {
if (!ranges.length) return;
Expand Down Expand Up @@ -8866,7 +8881,7 @@

// THE END

CodeMirror.version = "5.9.0";
CodeMirror.version = "5.10.0";

return CodeMirror;
});
25 changes: 12 additions & 13 deletions mode/clike/clike.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,21 +262,20 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
var cTypes = "int long char short double float unsigned signed void size_t ptrdiff_t";

function cppHook(stream, state) {
if (!state.startOfLine) return false;
for (;;) {
if (stream.skipTo("\\")) {
stream.next();
if (stream.eol()) {
state.tokenize = cppHook;
break;
}
} else {
stream.skipToEnd();
state.tokenize = null;
break;
if (!state.startOfLine) return false
for (var ch, next = null; ch = stream.peek();) {
if (!ch) {
break
} else if (ch == "\\" && stream.match(/^.$/)) {
next = cppHook
break
} else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) {
break
}
stream.next()
}
return "meta";
state.tokenize = next
return "meta"
}

function pointerHook(_stream, state) {
Expand Down
9 changes: 9 additions & 0 deletions mode/clike/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@
" [variable x][operator ++];",
"[keyword return];");

MT("preprocessor",
"[meta #define FOO 3]",
"[variable-3 int] [variable foo];",
"[meta #define BAR\\]",
"[meta 4]",
"[variable-3 unsigned] [variable-3 int] [variable bar] [operator =] [number 8];",
"[meta #include <baz> ][comment // comment]")


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)); }

Expand Down
391 changes: 391 additions & 0 deletions mode/crystal/crystal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,391 @@
// 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("crystal", function(config) {
function wordRegExp(words, end) {
return new RegExp((end ? "" : "^") + "(?:" + words.join("|") + ")" + (end ? "$" : "\\b"));
}

function chain(tokenize, stream, state) {
state.tokenize.push(tokenize);
return tokenize(stream, state);
}

var operators = /^(?:[-+/%|&^]|\*\*?|[<>]{2})/;
var conditionalOperators = /^(?:[=!]~|===|<=>|[<>=!]=?|[|&]{2}|~)/;
var indexingOperators = /^(?:\[\][?=]?)/;
var anotherOperators = /^(?:\.(?:\.{2})?|->|[?:])/;
var idents = /^[a-z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/;
var types = /^[A-Z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/;
var keywords = wordRegExp([
"abstract", "alias", "as", "asm", "begin", "break", "case", "class", "def", "do",
"else", "elsif", "end", "ensure", "enum", "extend", "for", "fun", "if", "ifdef",
"include", "instance_sizeof", "lib", "macro", "module", "next", "of", "out", "pointerof",
"private", "protected", "rescue", "return", "require", "sizeof", "struct",
"super", "then", "type", "typeof", "union", "unless", "until", "when", "while", "with",
"yield", "__DIR__", "__FILE__", "__LINE__"
]);
var atomWords = wordRegExp(["true", "false", "nil", "self"]);
var indentKeywordsArray = [
"def", "fun", "macro",
"class", "module", "struct", "lib", "enum", "union",
"if", "unless", "case", "while", "until", "begin", "then",
"do",
"for", "ifdef"
];
var indentKeywords = wordRegExp(indentKeywordsArray);
var dedentKeywordsArray = [
"end",
"else", "elsif",
"rescue", "ensure"
];
var dedentKeywords = wordRegExp(dedentKeywordsArray);
var dedentPunctualsArray = ["\\)", "\\}", "\\]"];
var dedentPunctuals = new RegExp("^(?:" + dedentPunctualsArray.join("|") + ")$");
var nextTokenizer = {
"def": tokenFollowIdent, "fun": tokenFollowIdent, "macro": tokenMacroDef,
"class": tokenFollowType, "module": tokenFollowType, "struct": tokenFollowType,
"lib": tokenFollowType, "enum": tokenFollowType, "union": tokenFollowType
};
var matching = {"[": "]", "{": "}", "(": ")", "<": ">"};

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

// Macros
if (state.lastToken != "\\" && stream.match("{%", false)) {
return chain(tokenMacro("%", "%"), stream, state);
}

if (state.lastToken != "\\" && stream.match("{{", false)) {
return chain(tokenMacro("{", "}"), stream, state);
}

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

// Variables and keywords
var matched;
if (stream.match(idents)) {
stream.eat(/[?!]/);

matched = stream.current();
if (stream.eat(":")) {
return "atom";
} else if (state.lastToken == ".") {
return "property";
} else if (keywords.test(matched)) {
if (state.lastToken != "abstract" && indentKeywords.test(matched)) {
if (!(matched == "fun" && state.blocks.indexOf("lib") >= 0)) {
state.blocks.push(matched);
state.currentIndent += 1;
}
} else if (dedentKeywords.test(matched)) {
state.blocks.pop();
state.currentIndent -= 1;
}

if (nextTokenizer.hasOwnProperty(matched)) {
state.tokenize.push(nextTokenizer[matched]);
}

return "keyword";
} else if (atomWords.test(matched)) {
return "atom";
}

return "variable";
}

// Class variables and instance variables
// or attributes
if (stream.eat("@")) {
if (stream.peek() == "[") {
return chain(tokenNest("[", "]", "meta"), stream, state);
}

stream.eat("@");
stream.match(idents) || stream.match(types);
return "variable-2";
}

// Global variables
if (stream.eat("$")) {
stream.eat(/[0-9]+|\?/) || stream.match(idents) || stream.match(types);
return "variable-3";
}

// Constants and types
if (stream.match(types)) {
return "tag";
}

// Symbols or ':' operator
if (stream.eat(":")) {
if (stream.eat("\"")) {
return chain(tokenQuote("\"", "atom", false), stream, state);
} else if (stream.match(idents) || stream.match(types) ||
stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators)) {
return "atom";
}
stream.eat(":");
return "operator";
}

// Strings
if (stream.eat("\"")) {
return chain(tokenQuote("\"", "string", true), stream, state);
}

// Strings or regexps or macro variables or '%' operator
if (stream.peek() == "%") {
var style = "string";
var embed = true;
var delim;

if (stream.match("%r")) {
// Regexps
style = "string-2";
delim = stream.next();
} else if (stream.match("%w")) {
embed = false;
delim = stream.next();
} else {
if(delim = stream.match(/^%([^\w\s=])/)) {
delim = delim[1];
} else if (stream.match(/^%[a-zA-Z0-9_\u009F-\uFFFF]*/)) {
// Macro variables
return "meta";
} else {
// '%' operator
return "operator";
}
}

if (matching.hasOwnProperty(delim)) {
delim = matching[delim];
}
return chain(tokenQuote(delim, style, embed), stream, state);
}

// Characters
if (stream.eat("'")) {
stream.match(/^(?:[^']|\\(?:[befnrtv0'"]|[0-7]{3}|u(?:[0-9a-fA-F]{4}|\{[0-9a-fA-F]{1,6}\})))/);
stream.eat("'");
return "atom";
}

// Numbers
if (stream.eat("0")) {
if (stream.eat("x")) {
stream.match(/^[0-9a-fA-F]+/);
} else if (stream.eat("o")) {
stream.match(/^[0-7]+/);
} else if (stream.eat("b")) {
stream.match(/^[01]+/);
}
return "number";
}

if (stream.eat(/\d/)) {
stream.match(/^\d*(?:\.\d+)?(?:[eE][+-]?\d+)?/);
return "number";
}

// Operators
if (stream.match(operators)) {
stream.eat("="); // Operators can follow assigin symbol.
return "operator";
}

if (stream.match(conditionalOperators) || stream.match(anotherOperators)) {
return "operator";
}

// Parens and braces
if (matched = stream.match(/[({[]/, false)) {
matched = matched[0];
return chain(tokenNest(matched, matching[matched], null), stream, state);
}

// Escapes
if (stream.eat("\\")) {
stream.next();
return "meta";
}

stream.next();
return null;
}

function tokenNest(begin, end, style, started) {
return function (stream, state) {
if (!started && stream.match(begin)) {
state.tokenize[state.tokenize.length - 1] = tokenNest(begin, end, style, true);
state.currentIndent += 1;
return style;
}

var nextStyle = tokenBase(stream, state);
if (stream.current() === end) {
state.tokenize.pop();
state.currentIndent -= 1;
nextStyle = style;
}

return nextStyle;
};
}

function tokenMacro(begin, end, started) {
return function (stream, state) {
if (!started && stream.match("{" + begin)) {
state.currentIndent += 1;
state.tokenize[state.tokenize.length - 1] = tokenMacro(begin, end, true);
return "meta";
}

if (stream.match(end + "}")) {
state.currentIndent -= 1;
state.tokenize.pop();
return "meta";
}

return tokenBase(stream, state);
};
}

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

var matched;
if (matched = stream.match(idents)) {
if (matched == "def") {
return "keyword";
}
stream.eat(/[?!]/);
}

state.tokenize.pop();
return "def";
}

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

if (stream.match(idents)) {
stream.eat(/[!?]/);
} else {
stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators);
}
state.tokenize.pop();
return "def";
}

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

stream.match(types);
state.tokenize.pop();
return "def";
}

function tokenQuote(end, style, embed) {
return function (stream, state) {
var escaped = false;

while (stream.peek()) {
if (!escaped) {
if (stream.match("{%", false)) {
state.tokenize.push(tokenMacro("%", "%"));
return style;
}

if (stream.match("{{", false)) {
state.tokenize.push(tokenMacro("{", "}"));
return style;
}

if (embed && stream.match("#{", false)) {
state.tokenize.push(tokenNest("#{", "}", "meta"));
return style;
}

var ch = stream.next();

if (ch == end) {
state.tokenize.pop();
return style;
}

escaped = ch == "\\";
} else {
stream.next();
escaped = false;
}
}

return style;
};
}

return {
startState: function () {
return {
tokenize: [tokenBase],
currentIndent: 0,
lastToken: null,
blocks: []
};
},

token: function (stream, state) {
var style = state.tokenize[state.tokenize.length - 1](stream, state);
var token = stream.current();

if (style && style != "comment") {
state.lastToken = token;
}

return style;
},

indent: function (state, textAfter) {
textAfter = textAfter.replace(/^\s*(?:\{%)?\s*|\s*(?:%\})?\s*$/g, "");

if (dedentKeywords.test(textAfter) || dedentPunctuals.test(textAfter)) {
return config.indentUnit * (state.currentIndent - 1);
}

return config.indentUnit * state.currentIndent;
},

fold: "indent",
electricInput: wordRegExp(dedentPunctualsArray.concat(dedentKeywordsArray), true),
lineComment: '#'
};
});

CodeMirror.defineMIME("text/x-crystal", "crystal");
});
119 changes: 119 additions & 0 deletions mode/crystal/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<!doctype html>

<title>CodeMirror: Crystal 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="../../addon/edit/matchbrackets.js"></script>
<script src="crystal.js"></script>
<style>
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
.cm-s-default span.cm-arrow { color: red; }
</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="#">Crystal</a>
</ul>
</div>

<article>
<h2>Crystal mode</h2>
<form><textarea id="code" name="code">
# Features of Crystal
# - Ruby-inspired syntax.
# - Statically type-checked but without having to specify the type of variables or method arguments.
# - Be able to call C code by writing bindings to it in Crystal.
# - Have compile-time evaluation and generation of code, to avoid boilerplate code.
# - Compile to efficient native code.

# A very basic HTTP server
require "http/server"

server = HTTP::Server.new(8080) do |request|
HTTP::Response.ok "text/plain", "Hello world, got #{request.path}!"
end

puts "Listening on http://0.0.0.0:8080"
server.listen

module Foo
def initialize(@foo); end

abstract def abstract_method : String

@[AlwaysInline]
def with_foofoo
with Foo.new(self) yield
end

struct Foo
def initialize(@foo); end

def hello_world
@foo.abstract_method
end
end
end

class Bar
include Foo

@@foobar = 12345

def initialize(@bar)
super(@bar.not_nil! + 100)
end

macro alias_method(name, method)
def {{ name }}(*args)
{{ method }}(*args)
end
end

def a_method
"Hello, World"
end

alias_method abstract_method, a_method

macro def show_instance_vars : Nil
{% for var in @type.instance_vars %}
puts "@{{ var }} = #{ @{{ var }} }"
{% end %}
nil
end
end

class Baz &lt; Bar; end

lib LibC
fun c_puts = "puts"(str : Char*) : Int
end

$baz = Baz.new(100)
$baz.show_instance_vars
$baz.with_foofoo do
LibC.c_puts hello_world
end
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: "text/x-crystal",
matchBrackets: true,
indentUnit: 2
});
</script>

<p><strong>MIME types defined:</strong> <code>text/x-crystal</code>.</p>
</article>
7 changes: 3 additions & 4 deletions mode/css/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
"use strict";

CodeMirror.defineMode("css", function(config, parserConfig) {
var provided = parserConfig;
var inline = parserConfig.inline
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
parserConfig.inline = provided.inline;

var indentUnit = config.indentUnit,
tokenHooks = parserConfig.tokenHooks,
Expand Down Expand Up @@ -368,9 +367,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return {
startState: function(base) {
return {tokenize: null,
state: parserConfig.inline ? "block" : "top",
state: inline ? "block" : "top",
stateArg: null,
context: new Context(parserConfig.inline ? "block" : "top", base || 0, null)};
context: new Context(inline ? "block" : "top", base || 0, null)};
},

token: function(stream, state) {
Expand Down
9 changes: 8 additions & 1 deletion mode/django/django.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
"truncatechars_html", "truncatewords", "truncatewords_html",
"unordered_list", "upper", "urlencode", "urlize",
"urlizetrunc", "wordcount", "wordwrap", "yesno"],
operators = ["==", "!=", "<", ">", "<=", ">=", "in", "not", "or", "and"];
operators = ["==", "!=", "<", ">", "<=", ">="],
wordOperators = ["in", "not", "or", "and"];

keywords = new RegExp("^\\b(" + keywords.join("|") + ")\\b");
filters = new RegExp("^\\b(" + filters.join("|") + ")\\b");
operators = new RegExp("^\\b(" + operators.join("|") + ")\\b");
wordOperators = new RegExp("^\\b(" + wordOperators.join("|") + ")\\b");

// We have to return "null" instead of null, in order to avoid string
// styling as the default, when using Django templates inside HTML
Expand Down Expand Up @@ -270,6 +272,11 @@
return "operator";
}

// Attempt to match a word operator
if (stream.match(wordOperators)) {
return "keyword";
}

// Attempt to match a keyword
var keywordMatch = stream.match(keywords);
if (keywordMatch) {
Expand Down
15 changes: 12 additions & 3 deletions mode/handlebars/handlebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

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

CodeMirror.defineSimpleMode("handlebars", {
CodeMirror.defineSimpleMode("handlebars-tags", {
start: [
{ regex: /\{\{!--/, push: "dash_comment", token: "comment" },
{ regex: /\{\{!/, push: "comment", token: "comment" },
Expand Down Expand Up @@ -49,5 +49,14 @@
]
});

CodeMirror.defineMode("handlebars", function(config, parserConfig) {
var handlebars = CodeMirror.getMode(config, "handlebars-tags");
if (!parserConfig || !parserConfig.base) return handlebars;
return CodeMirror.multiplexingMode(
CodeMirror.getMode(config, parserConfig.base),
{open: "{{", close: "}}", mode: handlebars, parseDelimiters: true}
);
});

CodeMirror.defineMIME("text/x-handlebars-template", "handlebars");
});
10 changes: 1 addition & 9 deletions mode/handlebars/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,10 @@ <h1>{{t 'article_list'}}</h1>

</textarea></form>
<script>
CodeMirror.defineMode("htmlhandlebars", function(config) {
return CodeMirror.multiplexingMode(
CodeMirror.getMode(config, "text/html"),
{open: "{{", close: "}}",
mode: CodeMirror.getMode(config, "handlebars"),
parseDelimiters: true});
});

var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "htmlhandlebars"
mode: {name: "handlebars", base: "text/html"}
});
</script>
</script>
Expand Down
3 changes: 3 additions & 0 deletions mode/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ <h2>Language modes</h2>
<li><a href="cobol/index.html">COBOL</a></li>
<li><a href="coffeescript/index.html">CoffeeScript</a></li>
<li><a href="commonlisp/index.html">Common Lisp</a></li>
<li><a href="crystal/index.html">Crystal</a></li>
<li><a href="css/index.html">CSS</a></li>
<li><a href="cypher/index.html">Cypher</a></li>
<li><a href="python/index.html">Cython</a></li>
Expand Down Expand Up @@ -86,6 +87,7 @@ <h2>Language modes</h2>
<li><a href="mathematica/index.html">Mathematica</a></li>
<li><a href="mirc/index.html">mIRC</a></li>
<li><a href="modelica/index.html">Modelica</a></li>
<li><a href="mscgen/index.html">MscGen</a></li>
<li><a href="mumps/index.html">MUMPS</a></li>
<li><a href="nginx/index.html">Nginx</a></li>
<li><a href="nsis/index.html">NSIS</a></li>
Expand Down Expand Up @@ -147,6 +149,7 @@ <h2>Language modes</h2>
<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>
<li><a href="yaml-frontmatter/index.html">YAML frontmatter</a></li>
<li><a href="z80/index.html">Z80</a></li>
</ul>
</div>
Expand Down
26 changes: 18 additions & 8 deletions mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,20 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("interface"),
"extends": kw("extends"),
"constructor": kw("constructor"),
"interface": kw("class"),
"implements": C,
"namespace": C,
"module": kw("module"),
"enum": kw("module"),

// scope modifiers
"public": kw("public"),
"private": kw("private"),
"protected": kw("protected"),
"static": kw("static"),
"public": kw("modifier"),
"private": kw("modifier"),
"protected": kw("modifier"),
"abstract": kw("modifier"),

// operators
"as": operator,

// types
"string": type, "number": type, "boolean": type, "any": type
Expand Down Expand Up @@ -121,7 +126,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (/^(?:operator|sof|keyword c|case|new|[\[{}\(,;:])$/.test(state.lastType)) {
} else if (/^(?:operator|sof|keyword c|case|new|[\[{}\(,;:])$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - 1)))) {
readRegexp(stream);
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
return ret("regexp", "string-2");
Expand Down Expand Up @@ -355,6 +361,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
Expand Down Expand Up @@ -459,6 +466,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (type == "modifier") {
return cont(objprop)
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
} else if (type == "spread") {
Expand Down Expand Up @@ -511,6 +520,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (type == "modifier") return cont(pattern)
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(pattern, "]");
Expand Down
4 changes: 2 additions & 2 deletions mode/markdown/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
}

function footnoteLink(stream, state) {
if (stream.match(/^[^\]]*\]:/, false)) {
if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) {
state.f = footnoteLinkInside;
stream.next(); // Consume [
if (modeCfg.highlightFormatting) state.formatting = "link";
Expand All @@ -639,7 +639,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return returnType;
}

stream.match(/^[^\]]+/, true);
stream.match(/^([^\]\\]|\\.)+/, true);

return tokenTypes.linkText;
}
Expand Down
9 changes: 9 additions & 0 deletions mode/markdown/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,15 @@
"[link [[foo]]:] [string&url http://example.com/]",
"(bar\" hello");

MT("labelEscape",
"[link [[foo \\]] ]]:] [string&url http://example.com/]");

MT("labelEscapeColon",
"[link [[foo \\]]: bar]]:] [string&url http://example.com/]");

MT("labelEscapeEnd",
"[[foo\\]]: http://example.com/");

MT("linkWeb",
"[link <http://example.com/>] foo");

Expand Down
1 change: 1 addition & 0 deletions mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
{name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]},
{name: "Cypher", mime: "application/x-cypher-query", mode: "cypher", ext: ["cyp", "cypher"]},
{name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]},
{name: "Crystal", mime: "text/x-crystal", mode: "crystal", ext: ["cr"]},
{name: "CSS", mime: "text/css", mode: "css", ext: ["css"]},
{name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]},
{name: "D", mime: "text/x-d", mode: "d", ext: ["d"]},
Expand Down
100 changes: 93 additions & 7 deletions mode/mscgen/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!doctype html>

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

Expand All @@ -24,11 +24,10 @@
<article>
<h2>MscGen mode</h2>

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

msc {
# options
hscale="1.2";
Expand All @@ -54,12 +53,99 @@ <h2>MscGen mode</h2>
}
</textarea></div>

<h2>Xù mode</h2>

<div><textarea id="xu-code">
# Xù - expansions to MscGen to support inline expressions
# https://github.com/sverweij/mscgen_js/blob/master/wikum/xu.md
# More samples: https://sverweij.github.io/mscgen_js
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...
/*
* Interesting stuff happens.
*/
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>

<h2>MsGenny mode</h2>
<div><textarea id="msgenny-code">
# MsGenny - simplified version of MscGen / Xù
# https://github.com/sverweij/mscgen_js/blob/master/wikum/msgenny.md
# More samples: https://sverweij.github.io/mscgen_js
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>

<p>
Simple mode for highlighting MscGen and two derived sequence
chart languages.
</p>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
var mscgenEditor = CodeMirror.fromTextArea(document.getElementById("mscgen-code"), {
lineNumbers: true,
mode: "mscgen",
mode: "text/x-mscgen",
});
var xuEditor = CodeMirror.fromTextArea(document.getElementById("xu-code"), {
lineNumbers: true,
mode: "text/x-xu",
});
var msgennyEditor = CodeMirror.fromTextArea(document.getElementById("msgenny-code"), {
lineNumbers: true,
mode: "text/x-msgenny",
});
</script>

<p><strong>MIME types defined:</strong> <code>text/x-mscgen</code></p>
<p><strong>MIME types defined:</strong>
<code>text/x-mscgen</code>
<code>text/x-xu</code>
<code>text/x-msgenny</code>
</p>

</article>
44 changes: 0 additions & 44 deletions mode/mscgen/index_msgenny.html

This file was deleted.

70 changes: 0 additions & 70 deletions mode/mscgen/index_xu.html

This file was deleted.

4 changes: 2 additions & 2 deletions mode/mscgen/mscgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@
CodeMirror.defineMIME("text/x-msgenny", {name: "mscgen", language: "msgenny"});

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

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

function startStateFn() {
Expand Down
4 changes: 3 additions & 1 deletion mode/sparql/sparql.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ CodeMirror.defineMode("sparql", function(config) {
return context.col + (closing ? 0 : 1);
else
return context.indent + (closing ? 0 : indentUnit);
}
},

lineComment: "#"
};
});

Expand Down
20 changes: 10 additions & 10 deletions mode/swift/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,42 +35,42 @@ <h2>Swift mode</h2>
// Created by Main Account on 12/18/14.
// Copyright (c) 2014 Razeware LLC. All rights reserved.
//

import Foundation

class TipCalculatorModel {

var total: Double
var taxPct: Double
var subtotal: Double {
get {
return total / (taxPct + 1)
}
}

init(total: Double, taxPct: Double) {
self.total = total
self.taxPct = taxPct
}

func calcTipWithTipPct(tipPct: Double) -> Double {
return subtotal * tipPct
}

func returnPossibleTips() -> [Int: Double] {

let possibleTipsInferred = [0.15, 0.18, 0.20]
let possibleTipsExplicit:[Double] = [0.15, 0.18, 0.20]

var retval = [Int: Double]()
for possibleTip in possibleTipsInferred {
let intPct = Int(possibleTip*100)
retval[intPct] = calcTipWithTipPct(possibleTip)
}
return retval

}

}
</textarea></form>

Expand Down
296 changes: 128 additions & 168 deletions mode/swift/swift.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,189 +13,149 @@
})(function(CodeMirror) {
"use strict"

function trim(str) { return /^\s*(.*?)\s*$/.exec(str)[1] }
function wordSet(words) {
var set = {}
for (var i = 0; i < words.length; i++) set[words[i]] = true
return set
}

var keywords = wordSet(["var","let","class","deinit","enum","extension","func","import","init","protocol",
"static","struct","subscript","typealias","as","dynamicType","is","new","super",
"self","Self","Type","__COLUMN__","__FILE__","__FUNCTION__","__LINE__","break","case",
"continue","default","do","else","fallthrough","if","in","for","return","switch",
"where","while","associativity","didSet","get","infix","inout","left","mutating",
"none","nonmutating","operator","override","postfix","precedence","prefix","right",
"set","unowned","weak","willSet"])
var definingKeywords = wordSet(["var","let","class","enum","extension","func","import","protocol","struct",
"typealias","dynamicType","for"])
var atoms = wordSet(["Infinity","NaN","undefined","null","true","false","on","off","yes","no","nil","null",
"this","super"])
var types = wordSet(["String","bool","int","string","double","Double","Int","Float","float","public",
"private","extension"])
var operators = "+-/*%=|&<>#"
var punc = ";,.(){}[]"
var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/
var number = /^-?(?:(?:[\d_]+\.[_\d]*|\.[_\d]+|0o[0-7_\.]+|0b[01_\.]+)(?:e-?[\d_]+)?|0x[\d_a-f\.]+(?:p-?[\d_]+)?)/i
var identifier = /^[_A-Za-z$][_A-Za-z$0-9]*/
var property = /^[@\.][_A-Za-z$][_A-Za-z$0-9]*/
var regexp = /^\/(?!\s)(?:\/\/)?(?:\\.|[^\/])+\//

var separators = [" ","\\\+","\\\-","\\\(","\\\)","\\\*","/",":","\\\?","\\\<","\\\>"," ","\\\."]
var tokens = new RegExp(separators.join("|"),"g")
function tokenBase(stream, state, prev) {
if (stream.eatSpace()) return null

function getWord(string, pos) {
var index = -1, count = 1
var words = string.split(tokens)
for (var i = 0; i < words.length; i++) {
for(var j = 1; j <= words[i].length; j++) {
if (count==pos) index = i
count++
var ch = stream.peek()
if (ch == "/") {
if (stream.match("//")) {
stream.skipToEnd()
return "comment"
}
count++
if (stream.match("/*")) {
state.tokenize.push(tokenComment)
return tokenComment(stream, state)
}
if (stream.match(regexp)) return "string-2"
}
var ret = ["", ""]
if (pos == 0) {
ret[1] = words[0]
ret[0] = null
} else {
ret[1] = words[index]
ret[0] = words[index-1]
if (operators.indexOf(ch) > -1) {
stream.next()
return "operator"
}
if (punc.indexOf(ch) > -1) {
stream.match(delimiters)
return "punctuation"
}
if (ch == '"' || ch == "'") {
stream.next()
var tokenize = tokenString(ch)
state.tokenize.push(tokenize)
return tokenize(stream, state)
}
return ret
}

CodeMirror.defineMode("swift", function() {
var keywords=["var","let","class","deinit","enum","extension","func","import","init","let","protocol","static","struct","subscript","typealias","var","as","dynamicType","is","new","super","self","Self","Type","__COLUMN__","__FILE__","__FUNCTION__","__LINE__","break","case","continue","default","do","else","fallthrough","if","in","for","return","switch","where","while","associativity","didSet","get","infix","inout","left","mutating","none","nonmutating","operator","override","postfix","precedence","prefix","right","set","unowned","unowned(safe)","unowned(unsafe)","weak","willSet"]
var commonConstants=["Infinity","NaN","undefined","null","true","false","on","off","yes","no","nil","null","this","super"]
var types=["String","bool","int","string","double","Double","Int","Float","float","public","private","extension"]
var numbers=["0","1","2","3","4","5","6","7","8","9"]
var operators=["+","-","/","*","%","=","|","&","<",">"]
var punc=[";",",",".","(",")","{","}","[","]"]
var delimiters=/^(?:[()\[\]{},:`=;]|\.\.?\.?)/
var identifiers=/^[_A-Za-z$][_A-Za-z$0-9]*/
var properties=/^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/
var regexPrefixes=/^(\/{3}|\/)/
if (stream.match(number)) return "number"
if (stream.match(property)) return "property"

return {
startState: function() {
return {
prev: false,
string: false,
escape: false,
inner: false,
comment: false,
num_left: 0,
num_right: 0,
doubleString: false,
singleString: false
if (stream.match(identifier)) {
var ident = stream.current()
if (keywords.hasOwnProperty(ident)) {
if (definingKeywords.hasOwnProperty(ident))
state.prev = "define"
return "keyword"
}
if (types.hasOwnProperty(ident)) return "variable-2"
if (atoms.hasOwnProperty(ident)) return "atom"
if (prev == "define") return "def"
return "variable"
}

stream.next()
return null
}

function tokenUntilClosingParen() {
var depth = 0
return function(stream, state, prev) {
var inner = tokenBase(stream, state, prev)
if (inner == "punctuation") {
if (stream.current() == "(") ++depth
else if (stream.current() == ")") {
if (depth == 0) {
stream.backUp(1)
state.tokenize.pop()
return state.tokenize[state.tokenize.length - 1](stream, state)
}
else --depth
}
},
token: function(stream, state) {
if (stream.eatSpace()) return null
}
return inner
}
}

var ch = stream.next()
if (state.string) {
if (state.escape) {
state.escape = false
function tokenString(quote) {
return function(stream, state) {
var ch, escaped = false
while (ch = stream.next()) {
if (escaped) {
if (ch == "(") {
state.tokenize.push(tokenUntilClosingParen())
return "string"
} else {
if ((ch == "\"" && (state.doubleString && !state.singleString) ||
(ch == "'" && (!state.doubleString && state.singleString))) &&
!state.escape) {
state.string = false
state.doubleString = false
state.singleString = false
return "string"
} else if (ch == "\\" && stream.peek() == "(") {
state.inner = true
state.string = false
return "keyword"
} else if (ch == "\\" && stream.peek() != "(") {
state.escape = true
state.string = true
return "string"
} else {
return "string"
}
}
} else if (state.comment) {
if (ch == "*" && stream.peek() == "/") {
state.prev = "*"
return "comment"
} else if (ch == "/" && state.prev == "*") {
state.prev = false
state.comment = false
return "comment"
}
return "comment"
escaped = false
} else if (ch == quote) {
break
} else {
if (ch == "/") {
if (stream.peek() == "/") {
stream.skipToEnd()
return "comment"
}
if (stream.peek() == "*") {
state.comment = true
return "comment"
}
}
if (ch == "(" && state.inner) {
state.num_left++
return null
}
if (ch == ")" && state.inner) {
state.num_right++
if (state.num_left == state.num_right) {
state.inner=false
state.string=true
}
return null
}

var ret = getWord(stream.string, stream.pos)
var the_word = ret[1]
var prev_word = ret[0]

if (operators.indexOf(ch + "") > -1) return "operator"
if (punc.indexOf(ch) > -1) return "punctuation"

if (typeof the_word != "undefined") {
the_word = trim(the_word)
if (typeof prev_word != "undefined") prev_word = trim(prev_word)
if (the_word.charAt(0) == "#") return null

if (types.indexOf(the_word) > -1) return "def"
if (commonConstants.indexOf(the_word) > -1) return "atom"
if (numbers.indexOf(the_word) > -1) return "number"
escaped = ch == "\\"
}
}
state.tokenize.pop()
return "string"
}
}

if ((numbers.indexOf(the_word.charAt(0) + "") > -1 ||
operators.indexOf(the_word.charAt(0) + "") > -1) &&
numbers.indexOf(ch) > -1) {
return "number"
}
function tokenComment(stream, state) {
stream.match(/^(?:[^*]|\*(?!\/))*/)
if (stream.match("*/")) state.tokenize.pop()
return "comment"
}

if (keywords.indexOf(the_word) > -1 ||
keywords.indexOf(the_word.split(tokens)[0]) > -1)
return "keyword"
if (keywords.indexOf(prev_word) > -1) return "def"
}
if (ch == '"' && !state.doubleString) {
state.string = true
state.doubleString = true
return "string"
}
if (ch == "'" && !state.singleString) {
state.string = true
state.singleString = true
return "string"
}
if (ch == "(" && state.inner)
state.num_left++
if (ch == ")" && state.inner) {
state.num_right++
if (state.num_left == state.num_right) {
state.inner = false
state.string = true
}
return null
}
if (stream.match(/^-?[0-9\.]/, false)) {
if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i) ||
stream.match(/^-?\d+\.\d*/) ||
stream.match(/^-?\.\d+/)) {
if (stream.peek() == ".") stream.backUp(1)
return "number"
}
if (stream.match(/^-?0x[0-9a-f]+/i) ||
stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/) ||
stream.match(/^-?0(?![\dx])/i))
return "number"
}
if (stream.match(regexPrefixes)) {
if (stream.current()!="/" || stream.match(/^.*\//,false)) return "string"
else stream.backUp(1)
}
if (stream.match(delimiters)) return "punctuation"
if (stream.match(identifiers)) return "variable"
if (stream.match(properties)) return "property"
return "variable"
CodeMirror.defineMode("swift", function() {
return {
startState: function() {
return {
prev: null,
tokenize: []
}
}
},
token: function(stream, state) {
var prev = state.prev
state.prev = null
var tokenize = state.tokenize[state.tokenize.length - 1] || tokenBase
var style = tokenize(stream, state, prev)
if (!style || style == "comment") state.prev = prev
else if (!state.prev) state.prev = style
return style
},
lineComment: "//",
blockCommentStart: "/*",
blockCommentEnd: "*/"
}
})

Expand Down
121 changes: 121 additions & 0 deletions mode/yaml-frontmatter/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<!doctype html>

<title>CodeMirror: YAML front matter 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="../../addon/mode/overlay.js"></script>
<script src="../markdown/markdown.js"></script>
<script src="../gfm/gfm.js"></script>
<script src="../yaml/yaml.js"></script>
<script src="yaml-frontmatter.js"></script>
<style>.CodeMirror { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; }</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="#">YAML-Frontmatter</a>
</ul>
</div>

<article>
<h2>YAML front matter mode</h2>
<form><textarea id="code" name="code">
---
receipt: Oz-Ware Purchase Invoice
date: 2007-08-06
customer:
given: Dorothy
family: Gale

items:
- part_no: A4786
descrip: Water Bucket (Filled)
price: 1.47
quantity: 4

- part_no: E1628
descrip: High Heeled "Ruby" Slippers
size: 8
price: 100.27
quantity: 1

bill-to: &id001
street: |
123 Tornado Alley
Suite 16
city: East Centerville
state: KS

ship-to: *id001

specialDelivery: >
Follow the Yellow Brick
Road to the Emerald City.
Pay no attention to the
man behind the curtain.
---

GitHub Flavored Markdown
========================

Everything from markdown plus GFM features:

## URL autolinking

Underscores_are_allowed_between_words.

## Strikethrough text

GFM adds syntax to strikethrough text, which is missing from standard Markdown.

~~Mistaken text.~~
~~**works with other fomatting**~~

~~spans across
lines~~

## Fenced code blocks (and syntax highlighting)

```javascript
for (var i = 0; i &lt; items.length; i++) {
console.log(items[i], i); // log them
}
```

## Task Lists

- [ ] Incomplete task list item
- [x] **Completed** task list item

## A bit of GitHub spice

* SHA: be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
* User@SHA ref: mojombo@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
* User/Project@SHA: mojombo/god@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
* \#Num: #1
* User/#Num: mojombo#1
* User/Project#Num: mojombo/god#1

See http://github.github.com/github-flavored-markdown/.
</textarea></form>

<p>Defines a mode that parses
a <a href="http://jekyllrb.com/docs/frontmatter/">YAML frontmatter</a>
at the start of a file, switching to a base mode at the end of that.
Takes a mode configuration option <code>base</code> to configure the
base mode, which defaults to <code>"gfm"</code>.</p>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "yaml-frontmatter"});
</script>

</article>
68 changes: 68 additions & 0 deletions mode/yaml-frontmatter/yaml-frontmatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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"), require("../yaml/yaml"))
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../yaml/yaml"], mod)
else // Plain browser env
mod(CodeMirror)
})(function (CodeMirror) {

var START = 0, FRONTMATTER = 1, BODY = 2

// a mixed mode for Markdown text with an optional YAML front matter
CodeMirror.defineMode("yaml-frontmatter", function (config, parserConfig) {
var yamlMode = CodeMirror.getMode(config, "yaml")
var innerMode = CodeMirror.getMode(config, parserConfig && parserConfig.base || "gfm")

function curMode(state) {
return state.state == BODY ? innerMode : yamlMode
}

return {
startState: function () {
return {
state: START,
inner: CodeMirror.startState(yamlMode)
}
},
copyState: function (state) {
return {
state: state.state,
inner: CodeMirror.copyState(curMode(state), state.inner)
}
},
token: function (stream, state) {
if (state.state == START) {
if (stream.match(/---/, false)) {
state.state = FRONTMATTER
return yamlMode.token(stream, state.inner)
} else {
stream.state = BODY
state.inner = CodeMirror.startState(innerMode)
return innerMode.token(stream, state.inner)
}
} else if (state.state == FRONTMATTER) {
var end = stream.sol() && stream.match(/---/, false)
var style = yamlMode.token(stream, state.inner)
if (end) {
state.state = BODY
state.inner = CodeMirror.startState(innerMode)
}
return style
} else {
return innerMode.token(stream, state.inner)
}
},
innerMode: function (state) {
return {mode: curMode(state), state: state.inner}
},
blankLine: function (state) {
var mode = curMode(state)
if (mode.blankLine) return mode.blankLine(state.inner)
}
}
})
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror",
"version":"5.9.0",
"version":"5.10.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"license": "MIT",
Expand Down
2 changes: 2 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,8 @@ testCM("atomicMarker", function(cm) {
testCM("selectionBias", function(cm) {
cm.markText(Pos(0, 1), Pos(0, 3), {atomic: true});
cm.setCursor(Pos(0, 2));
eqPos(cm.getCursor(), Pos(0, 1));
cm.setCursor(Pos(0, 2));
eqPos(cm.getCursor(), Pos(0, 3));
cm.setCursor(Pos(0, 2));
eqPos(cm.getCursor(), Pos(0, 1));
Expand Down
34 changes: 31 additions & 3 deletions test/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2148,9 +2148,18 @@ testVim('S_normal', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('j', 'S');
helpers.doKeys('<Esc>');
helpers.assertCursorAt(1, 0);
eq('aa\n\ncc', cm.getValue());
}, { value: 'aa\nbb\ncc'});
helpers.assertCursorAt(1, 1);
eq('aa{\n \ncc', cm.getValue());
helpers.doKeys('j', 'S');
eq('aa{\n \n ', cm.getValue());
helpers.assertCursorAt(2, 2);
helpers.doKeys('<Esc>');
helpers.doKeys('d', 'd', 'd', 'd');
helpers.assertCursorAt(0, 0);
helpers.doKeys('S');
is(vim.insertMode);
eq('', cm.getValue());
}, { value: 'aa{\nbb\ncc'});
testVim('blockwise_paste', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '3', 'j', 'l', 'y');
Expand Down Expand Up @@ -3110,6 +3119,25 @@ forEach(['zb','zz','zt','z-','z.','z<CR>'], function(e, idx){
return new Array(500).join('\n');
})()});
});
testVim('zb_to_bottom', function(cm, vim, helpers){
var lineNum = 250;
cm.setSize(600, 35*cm.defaultTextHeight());
cm.setCursor(lineNum, 0);
helpers.doKeys('z', 'b');
var scrollInfo = cm.getScrollInfo();
eq(scrollInfo.top + scrollInfo.clientHeight, cm.charCoords(Pos(lineNum, 0), 'local').bottom);
}, { value: (function(){
return new Array(500).join('\n');
})()});
testVim('zt_to_top', function(cm, vim, helpers){
var lineNum = 250;
cm.setSize(600, 35*cm.defaultTextHeight());
cm.setCursor(lineNum, 0);
helpers.doKeys('z', 't');
eq(cm.getScrollInfo().top, cm.charCoords(Pos(lineNum, 0), 'local').top);
}, { value: (function(){
return new Array(500).join('\n');
})()});
testVim('zb<zz', function(cm, vim, helpers){
eq(zVals[0]<zVals[1], true);
});
Expand Down