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

<h2>Version 4.x</h2>

<p class="rel">22-01-2015: <a href="http://codemirror.net/codemirror-4.12.zip">Version 4.12</a>:</p>

<ul class="rel-note">
<li>The <a href="manual.html#addon_closetag"><code>closetag</code></a>
addon now defines a <code>"closeTag"</code> command.</li>
<li>Adds a <code>findModeByFileName</code> to the <a href="manual.html#addon_meta">mode metadata</a>
addon.</li>
<li><a href="../demo/simplemode.html">Simple mode</a> rules can
now contain a <code>sol</code> property to only match at the start
of a line.</li>
<li>New
addon: <a href="manual.html#addon_selection-pointer"><code>selection-pointer</code></a>
to style the mouse cursor over the selection.</li>
<li>Improvements to the <a href="../mode/sass/index.html">Sass mode</a>'s indentation.</li>
<li>The <a href="../demo/vim.html">Vim keymap</a>'s search functionality now
supports <a href="manual.html#addon_matchesonscrollbar">scrollbar
annotation</a>.</li>
<li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.11.0...4.12.0">list of patches</a>.</li>
</ul>

<p class="rel">9-01-2015: <a href="http://codemirror.net/codemirror-4.11.zip">Version 4.11</a>:</p>

<p class="rel-note">Unfortunately, 4.10 did not take care of the
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ <h2>This is CodeMirror</h2>
</script>
<div style="position: relative; margin: 1em 0;">
<a class="bigbutton left" href="http://codemirror.net/codemirror.zip">DOWNLOAD LATEST RELEASE</a>
<div><strong>version 4.11</strong> (<a href="doc/releases.html">Release notes</a>)</div>
<div><strong>version 4.12</strong> (<a href="doc/releases.html">Release notes</a>)</div>
<div>or use the <a href="doc/compress.html">minification helper</a></div>
<div style="position: absolute; top: 0; right: 0; text-align: right">
<span class="bigbutton right" onclick="document.getElementById('paypal').submit();">DONATE WITH PAYPAL</span>
Expand Down
1 change: 1 addition & 0 deletions keymap/emacs.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@
"Ctrl-X U": repeated("undo"),
"Ctrl-X K": "close",
"Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); },
"Ctrl-X H": "selectAll",

"Ctrl-Q Tab": repeated("insertTab"),
"Ctrl-U": addPrefixMap
Expand Down
25 changes: 22 additions & 3 deletions keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -3469,6 +3469,12 @@
},
setReversed: function(reversed) {
vimGlobalState.isReversed = reversed;
},
getScrollbarAnnotate: function() {
return this.annotate;
},
setScrollbarAnnotate: function(annotate) {
this.annotate = annotate;
}
};
function getSearchState(cm) {
Expand Down Expand Up @@ -3747,14 +3753,21 @@
};
}
function highlightSearchMatches(cm, query) {
var overlay = getSearchState(cm).getOverlay();
var searchState = getSearchState(cm);
var overlay = searchState.getOverlay();
if (!overlay || query != overlay.query) {
if (overlay) {
cm.removeOverlay(overlay);
}
overlay = searchOverlay(query);
cm.addOverlay(overlay);
getSearchState(cm).setOverlay(overlay);
if (cm.showMatchesOnScrollbar) {
if (searchState.getScrollbarAnnotate()) {
searchState.getScrollbarAnnotate().clear();
}
searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
}
searchState.setOverlay(overlay);
}
}
function findNext(cm, prev, query, repeat) {
Expand All @@ -3779,8 +3792,13 @@
});
}
function clearSearchHighlight(cm) {
var state = getSearchState(cm);
cm.removeOverlay(getSearchState(cm).getOverlay());
getSearchState(cm).setOverlay(null);
state.setOverlay(null);
if (state.getScrollbarAnnotate()) {
state.getScrollbarAnnotate().clear();
state.setScrollbarAnnotate(null);
}
}
/**
* Check if pos is in the specified range, INCLUSIVE.
Expand Down Expand Up @@ -4789,6 +4807,7 @@
var macroModeState = vimGlobalState.macroModeState;
var lastChange = macroModeState.lastInsertModeChanges;
var keyName = CodeMirror.keyName(e);
if (!keyName) { return; }
function onKeyFound() {
lastChange.changes.push(new InsertModeKey(keyName));
return true;
Expand Down
46 changes: 31 additions & 15 deletions lib/codemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@
maybeUpdateLineNumberWidth(this);
for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
endOperation(this);
// Suppress optimizelegibility in Webkit, since it breaks text
// measuring on line wrapping boundaries.
if (webkit && options.lineWrapping &&
getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
display.lineDiv.style.textRendering = "auto";
}

// DISPLAY CONSTRUCTOR
Expand Down Expand Up @@ -1847,7 +1852,8 @@

// Converts a {top, bottom, left, right} box from line-local
// coordinates into another coordinate system. Context may be one of
// "line", "div" (display.lineDiv), "local"/null (editor), or "page".
// "line", "div" (display.lineDiv), "local"/null (editor), "window",
// or "page".
function intoCoordSystem(cm, lineObj, rect, context) {
if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
var size = widgetHeight(lineObj.widgets[i]);
Expand Down Expand Up @@ -2759,7 +2765,9 @@
// Return true when the given mouse event happened in a widget
function eventInWidget(display, e) {
for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
if (!n || n.getAttribute("cm-ignore-events") == "true" || n.parentNode == display.sizer && n != display.mover) return true;
if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
(n.parentNode == display.sizer && n != display.mover))
return true;
}
}

Expand Down Expand Up @@ -3788,7 +3796,9 @@

var lendiff = change.text.length - (to.line - from.line) - 1;
// Remember that these lines changed, for updating the display
if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
if (change.full)
regChange(cm);
else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
regLineChange(cm, from.line, "text");
else
regChange(cm, from.line, to.line + 1, lendiff);
Expand Down Expand Up @@ -5570,6 +5580,7 @@
// spans partially within the change. Returns an array of span
// arrays with one element for each line in (after) the change.
function stretchSpansOverChange(doc, change) {
if (change.full) return null;
var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
if (!oldFirst && !oldLast) return null;
Expand Down Expand Up @@ -5879,7 +5890,9 @@
if (!contains(document.body, widget.node)) {
var parentStyle = "position: relative;";
if (widget.coverGutter)
parentStyle += "margin-left: -" + widget.cm.getGutterElement().offsetWidth + "px;";
parentStyle += "margin-left: -" + widget.cm.display.gutters.offsetWidth + "px;";
if (widget.noHScroll)
parentStyle += "width: " + widget.cm.display.wrapper.clientWidth + "px;";
removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
}
return widget.height = widget.node.offsetHeight;
Expand Down Expand Up @@ -6342,26 +6355,32 @@
updateLine(line, text, spans, estimateHeight);
signalLater(line, "change", line, change);
}
function linesFor(start, end) {
for (var i = start, result = []; i < end; ++i)
result.push(new Line(text[i], spansFor(i), estimateHeight));
return result;
}

var from = change.from, to = change.to, text = change.text;
var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;

// Adjust the line structure
if (isWholeLineUpdate(doc, change)) {
if (change.full) {
doc.insert(0, linesFor(0, text.length));
doc.remove(text.length, doc.size - text.length);
} else if (isWholeLineUpdate(doc, change)) {
// This is a whole-line replace. Treated specially to make
// sure line objects move the way they are supposed to.
for (var i = 0, added = []; i < text.length - 1; ++i)
added.push(new Line(text[i], spansFor(i), estimateHeight));
var added = linesFor(0, text.length - 1);
update(lastLine, lastLine.text, lastSpans);
if (nlines) doc.remove(from.line, nlines);
if (added.length) doc.insert(from.line, added);
} else if (firstLine == lastLine) {
if (text.length == 1) {
update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
} else {
for (var added = [], i = 1; i < text.length - 1; ++i)
added.push(new Line(text[i], spansFor(i), estimateHeight));
var added = linesFor(1, text.length - 1);
added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
doc.insert(from.line + 1, added);
Expand All @@ -6372,8 +6391,7 @@
} else {
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
for (var i = 1, added = []; i < text.length - 1; ++i)
added.push(new Line(text[i], spansFor(i), estimateHeight));
var added = linesFor(1, text.length - 1);
if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
doc.insert(from.line + 1, added);
}
Expand Down Expand Up @@ -6584,7 +6602,7 @@
setValue: docMethodOp(function(code) {
var top = Pos(this.first, 0), last = this.first + this.size - 1;
makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
text: splitLines(code), origin: "setValue"}, true);
text: splitLines(code), origin: "setValue", full: true}, true);
setSelection(this, simpleSelection(top));
}),
replaceRange: function(code, from, to, origin) {
Expand Down Expand Up @@ -7464,13 +7482,11 @@
if (array[i] == elt) return i;
return -1;
}
if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); };
function map(array, f) {
var out = [];
for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
return out;
}
if ([].map) map = function(array, f) { return array.map(f); };

function createObj(base, props) {
var inst;
Expand Down Expand Up @@ -8023,7 +8039,7 @@

// THE END

CodeMirror.version = "4.11.0";
CodeMirror.version = "4.12.0";

return CodeMirror;
});
8 changes: 4 additions & 4 deletions mode/clike/clike.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,15 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
"mat2 mat3 mat4 " +
"sampler1D sampler2D sampler3D samplerCube " +
"sampler1DShadow sampler2DShadow" +
"sampler1DShadow sampler2DShadow " +
"const attribute uniform varying " +
"break continue discard return " +
"for while do if else struct " +
"in out inout"),
blockKeywords: words("for while do if else struct"),
builtin: words("radians degrees sin cos tan asin acos atan " +
"pow exp log exp2 sqrt inversesqrt " +
"abs sign floor ceil fract mod min max clamp mix step smootstep " +
"abs sign floor ceil fract mod min max clamp mix step smoothstep " +
"length distance dot cross normalize ftransform faceforward " +
"reflect refract matrixCompMult " +
"lessThan lessThanEqual greaterThan greaterThanEqual " +
Expand All @@ -433,12 +433,12 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " +
"gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " +
"gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " +
"gl_FogCoord " +
"gl_FogCoord gl_PointCoord " +
"gl_Position gl_PointSize gl_ClipVertex " +
"gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " +
"gl_TexCoord gl_FogFragCoord " +
"gl_FragCoord gl_FrontFacing " +
"gl_FragColor gl_FragData gl_FragDepth " +
"gl_FragData gl_FragDepth " +
"gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " +
"gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " +
"gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " +
Expand Down
4 changes: 2 additions & 2 deletions mode/css/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,14 +525,14 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et",
"ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-table", "inset", "inside", "intrinsic", "invert",
"inline-block", "inline-flex", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer",
"landscape", "lao", "large", "larger", "left", "level", "lighter",
"line-through", "linear", "lines", "list-item", "listbox", "listitem",
Expand Down
6 changes: 5 additions & 1 deletion mode/htmlmixed/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/selection/selection-pointer.js"></script>
<script src="../xml/xml.js"></script>
<script src="../javascript/javascript.js"></script>
<script src="../css/css.js"></script>
Expand Down Expand Up @@ -62,7 +63,10 @@ <h1>Mixed HTML Example</h1>
{matches: /(text|application)\/(x-)?vb(a|script)/i,
mode: "vbscript"}]
};
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: mixedMode});
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: mixedMode,
selectionPointer: true
});
</script>

<p>The HTML mixed mode depends on the XML, JavaScript, and CSS modes.</p>
Expand Down
8 changes: 7 additions & 1 deletion mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "if") return cont(expression, comprehension);
}

function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}

// Interface

return {
Expand Down Expand Up @@ -651,7 +657,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
Expand Down
41 changes: 26 additions & 15 deletions mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@

CodeMirror.modeInfo = [
{name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]},
{name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk"},
{name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk", file: /^extensions\.conf$/i},
{name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h"]},
{name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "hpp", "h++"], alias: ["cpp"]},
{name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"], alias: ["cpp"]},
{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: "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"]},
{name: "Cypher", mime: "application/x-cypher-query", mode: "cypher"},
{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: "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"]},
{name: "Dart", mimes: ["application/dart", "text/x-dart"], mode: "dart", ext: ["dart"]},
{name: "diff", mime: "text/x-diff", mode: "diff", ext: ["diff", "patch"]},
{name: "Django", mime: "text/x-django", mode: "django"},
{name: "Dockerfile", mime: "text/x-dockerfile", mode: "dockerfile"},
{name: "Dockerfile", mime: "text/x-dockerfile", mode: "dockerfile", file: /^Dockerfile$/},
{name: "DTD", mime: "application/xml-dtd", mode: "dtd", ext: ["dtd"]},
{name: "Dylan", mime: "text/x-dylan", mode: "dylan", ext: ["dylan", "dyl", "intr"]},
{name: "EBNF", mime: "text/x-ebnf", mode: "ebnf"},
Expand All @@ -42,7 +42,7 @@
{name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"], alias: ["fsharp"]},
{name: "Gas", mime: "text/x-gas", mode: "gas", ext: ["s"]},
{name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]},
{name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm"},
{name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history).md$/i},
{name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]},
{name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy"]},
{name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]},
Expand All @@ -59,7 +59,7 @@
{name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"],
mode: "javascript", ext: ["js"], alias: ["ecmascript", "js", "node"]},
{name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"], alias: ["json5"]},
{name: "JSON-LD", mime: "application/ld+json", mode: "javascript", alias: ["jsonld"]},
{name: "JSON-LD", mime: "application/ld+json", mode: "javascript", ext: ["jsonld"], alias: ["jsonld"]},
{name: "Jinja2", mime: "null", mode: "jinja2"},
{name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"]},
{name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin", ext: ["kt"]},
Expand All @@ -72,18 +72,18 @@
{name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]},
{name: "MS SQL", mime: "text/x-mssql", mode: "sql"},
{name: "MySQL", mime: "text/x-mysql", mode: "sql"},
{name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx"},
{name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i},
{name: "NTriples", mime: "text/n-triples", mode: "ntriples", ext: ["nt"]},
{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: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]},
{name: "PEG.js", mime: "null", mode: "pegjs"},
{name: "PEG.js", mime: "null", mode: "pegjs", ext: ["jsonld"]},
{name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]},
{name: "PHP", mime: "application/x-httpd-php", mode: "php", ext: ["php", "php3", "php4", "php5", "phtml"]},
{name: "Pig", mime: "text/x-pig", mode: "pig"},
{name: "Pig", mime: "text/x-pig", mode: "pig", ext: ["pig"]},
{name: "Plain Text", mime: "text/plain", mode: "null", ext: ["txt", "text", "conf", "def", "list", "log"]},
{name: "PLSQL", mime: "text/x-plsql", mode: "sql"},
{name: "PLSQL", mime: "text/x-plsql", mode: "sql", ext: ["pls"]},
{name: "Properties files", mime: "text/x-properties", mode: "properties", ext: ["properties", "ini", "in"], alias: ["ini", "properties"]},
{name: "Python", mime: "text/x-python", mode: "python", ext: ["py", "pyw"]},
{name: "Puppet", mime: "text/x-puppet", mode: "puppet", ext: ["pp"]},
Expand All @@ -99,8 +99,8 @@
{name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]},
{name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]},
{name: "Shell", mime: "text/x-sh", mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"]},
{name: "Sieve", mime: "application/sieve", mode: "sieve"},
{name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim"},
{name: "Sieve", mime: "application/sieve", mode: "sieve", ext: ["siv", "sieve"]},
{name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim", ext: ["slim"]},
{name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]},
{name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]},
{name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"},
Expand All @@ -114,15 +114,15 @@
{name: "LaTeX", mime: "text/x-latex", mode: "stex", ext: ["text", "ltx"], alias: ["tex"]},
{name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog", ext: ["v"]},
{name: "Tcl", mime: "text/x-tcl", mode: "tcl", ext: ["tcl"]},
{name: "Textile", mime: "text/x-textile", mode: "textile"},
{name: "Textile", mime: "text/x-textile", mode: "textile", ext: ["textile"]},
{name: "TiddlyWiki ", mime: "text/x-tiddlywiki", mode: "tiddlywiki"},
{name: "Tiki wiki", mime: "text/tiki", mode: "tiki"},
{name: "TOML", mime: "text/x-toml", mode: "toml"},
{name: "TOML", mime: "text/x-toml", mode: "toml", ext: ["toml"]},
{name: "Tornado", mime: "text/x-tornado", mode: "tornado"},
{name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]},
{name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: ["ts"], alias: ["ts"]},
{name: "VB.NET", mime: "text/x-vb", mode: "vb", ext: ["vb"]},
{name: "VBScript", mime: "text/vbscript", mode: "vbscript"},
{name: "VBScript", mime: "text/vbscript", mode: "vbscript", ext: ["vbs"]},
{name: "Velocity", mime: "text/velocity", mode: "velocity", ext: ["vtl"]},
{name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]},
{name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd"], alias: ["rss", "wsdl", "xsd"]},
Expand All @@ -137,6 +137,7 @@
}

CodeMirror.findModeByMIME = function(mime) {
mime = mime.toLowerCase();
for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
var info = CodeMirror.modeInfo[i];
if (info.mime == mime) return info;
Expand All @@ -153,6 +154,16 @@
}
};

CodeMirror.findModeByFileName = function(filename) {
for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
var info = CodeMirror.modeInfo[i];
if (info.file && info.file.test(filename)) return info;
}
var dot = filename.lastIndexOf(".");
var ext = dot > -1 && filename.substring(dot + 1, filename.length);
if (ext) return CodeMirror.findModeByExtension(ext);
};

CodeMirror.findModeByName = function(name) {
name = name.toLowerCase();
for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
Expand Down
25 changes: 15 additions & 10 deletions mode/perl/perl.js
Original file line number Diff line number Diff line change
Expand Up @@ -780,16 +780,21 @@ CodeMirror.defineMode("perl",function(){
return "meta";}
return null;}

return{
startState:function(){
return{
tokenize:tokenPerl,
chain:null,
style:null,
tail:null};},
token:function(stream,state){
return (state.tokenize||tokenPerl)(stream,state);}
};});
return {
startState: function() {
return {
tokenize: tokenPerl,
chain: null,
style: null,
tail: null
};
},
token: function(stream, state) {
return (state.tokenize || tokenPerl)(stream, state);
},
lineComment: '#'
};
});

CodeMirror.registerHelper("wordChars", "perl", /[\w$]/);

Expand Down
285 changes: 186 additions & 99 deletions mode/sass/sass.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ CodeMirror.defineMode("sass", function(config) {
var keywords = ["true", "false", "null", "auto"];
var keywordsRegexp = new RegExp("^" + keywords.join("|"));

var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-", "\\!=", "/", "\\*", "%", "and", "or", "not"];
var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-",
"\\!=", "/", "\\*", "%", "and", "or", "not", ";","\\{","\\}",":"];
var opRegexp = tokenRegexp(operators);

var pseudoElementsRegexp = /^::?[\w\-]+/;
var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/;

function urlTokens(stream, state) {
var ch = stream.peek();
Expand Down Expand Up @@ -56,15 +57,15 @@ CodeMirror.defineMode("sass", function(config) {
stream.next();
state.tokenizer = tokenBase;
} else {
stream.next();
stream.skipToEnd();
}

return "comment";
};
}

function buildStringTokenizer(quote, greedy) {
if(greedy == null) { greedy = true; }
if (greedy == null) { greedy = true; }

function stringTokenizer(stream, state) {
var nextChar = stream.next();
Expand Down Expand Up @@ -135,128 +136,213 @@ CodeMirror.defineMode("sass", function(config) {
return "operator";
}

if (ch === ".") {
// Strings
if (ch === '"' || ch === "'") {
stream.next();
state.tokenizer = buildStringTokenizer(ch);
return "string";
}

// Match class selectors
if (stream.match(/^[\w-]+/)) {
indent(state);
return "atom";
} else if (stream.peek() === "#") {
indent(state);
return "atom";
} else {
return "operator";
if(!state.cursorHalf){// state.cursorHalf === 0
// first half i.e. before : for key-value pairs
// including selectors

if (ch === ".") {
stream.next();
if (stream.match(/^[\w-]+/)) {
indent(state);
return "atom";
} else if (stream.peek() === "#") {
indent(state);
return "atom";
}
}
}

if (ch === "#") {
stream.next();
if (ch === "#") {
stream.next();
// ID selectors
if (stream.match(/^[\w-]+/)) {
indent(state);
return "atom";
}
if (stream.peek() === "#") {
indent(state);
return "atom";
}
}

// Variables
if (ch === "$") {
stream.next();
stream.eatWhile(/[\w-]/);
return "variable-2";
}

// Hex numbers
if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/))
// Numbers
if (stream.match(/^-?[0-9\.]+/))
return "number";

// ID selectors
if (stream.match(/^[\w-]+/)) {
indent(state);
// Units
if (stream.match(/^(px|em|in)\b/))
return "unit";

if (stream.match(keywordsRegexp))
return "keyword";

if (stream.match(/^url/) && stream.peek() === "(") {
state.tokenizer = urlTokens;
return "atom";
}

if (stream.peek() === "#") {
indent(state);
return "atom";
if (ch === "=") {
// Match shortcut mixin definition
if (stream.match(/^=[\w-]+/)) {
indent(state);
return "meta";
}
}
}

// Numbers
if (stream.match(/^-?[0-9\.]+/))
return "number";
if (ch === "+") {
// Match shortcut mixin definition
if (stream.match(/^\+[\w-]+/)){
return "variable-3";
}
}

// Units
if (stream.match(/^(px|em|in)\b/))
return "unit";
if(ch === "@"){
if(stream.match(/@extend/)){
if(!stream.match(/\s*[\w]/))
dedent(state);
}
}

if (stream.match(keywordsRegexp))
return "keyword";

if (stream.match(/^url/) && stream.peek() === "(") {
state.tokenizer = urlTokens;
return "atom";
}
// Indent Directives
if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) {
indent(state);
return "meta";
}

// Variables
if (ch === "$") {
stream.next();
stream.eatWhile(/[\w-]/);
// Other Directives
if (ch === "@") {
stream.next();
stream.eatWhile(/[\w-]/);
return "meta";
}

if (stream.peek() === ":") {
if (stream.eatWhile(/[\w-]/)){
if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){
return "propery";
}
else if(stream.match(/ *:/,false)){
indent(state);
state.cursorHalf = 1;
return "atom";
}
else if(stream.match(/ *,/,false)){
return "atom";
}
else{
indent(state);
return "atom";
}
}

if(ch === ":"){
if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element
return "keyword";
}
stream.next();
return "variable-2";
} else {
return "variable-3";
state.cursorHalf=1;
return "operator";
}
}

if (ch === "!") {
stream.next();
return stream.match(/^[\w]+/) ? "keyword": "operator";
}
} // cursorHalf===0 ends here
else{

if (ch === "=") {
stream.next();
if (ch === "#") {
stream.next();
// Hex numbers
if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){
if(!stream.peek()){
state.cursorHalf = 0;
}
return "number";
}
}

// Match shortcut mixin definition
if (stream.match(/^[\w-]+/)) {
indent(state);
return "meta";
} else {
return "operator";
// Numbers
if (stream.match(/^-?[0-9\.]+/)){
if(!stream.peek()){
state.cursorHalf = 0;
}
return "number";
}
}

if (ch === "+") {
stream.next();
// Units
if (stream.match(/^(px|em|in)\b/)){
if(!stream.peek()){
state.cursorHalf = 0;
}
return "unit";
}

// Match shortcut mixin definition
if (stream.match(/^[\w-]+/))
if (stream.match(keywordsRegexp)){
if(!stream.peek()){
state.cursorHalf = 0;
}
return "keyword";
}

if (stream.match(/^url/) && stream.peek() === "(") {
state.tokenizer = urlTokens;
if(!stream.peek()){
state.cursorHalf = 0;
}
return "atom";
}

// Variables
if (ch === "$") {
stream.next();
stream.eatWhile(/[\w-]/);
if(!stream.peek()){
state.cursorHalf = 0;
}
return "variable-3";
else
return "operator";
}
}

// Indent Directives
if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) {
indent(state);
return "meta";
}
// bang character for !important, !default, etc.
if (ch === "!") {
stream.next();
if(!stream.peek()){
state.cursorHalf = 0;
}
return stream.match(/^[\w]+/) ? "keyword": "operator";
}

// Other Directives
if (ch === "@") {
stream.next();
stream.eatWhile(/[\w-]/);
return "meta";
}
if (stream.match(opRegexp)){
if(!stream.peek()){
state.cursorHalf = 0;
}
return "operator";
}

// Strings
if (ch === '"' || ch === "'") {
stream.next();
state.tokenizer = buildStringTokenizer(ch);
return "string";
}
// attributes
if (stream.eatWhile(/[\w-]/)) {
if(!stream.peek()){
state.cursorHalf = 0;
}
return "attribute";
}

// Pseudo element selectors
if (ch == ":" && stream.match(pseudoElementsRegexp))
return "keyword";
//stream.eatSpace();
if(!stream.peek()){
state.cursorHalf = 0;
return null;
}

// atoms
if (stream.eatWhile(/[\w-&]/)) {
// matches a property definition
if (stream.peek() === ":" && !stream.match(pseudoElementsRegexp, false))
return "property";
else
return "atom";
}
} // else ends here

if (stream.match(opRegexp))
return "operator";
Expand All @@ -272,14 +358,13 @@ CodeMirror.defineMode("sass", function(config) {
var style = state.tokenizer(stream, state);
var current = stream.current();

if (current === "@return")
if (current === "@return" || current === "}"){
dedent(state);

if (style === "atom")
indent(state);
}

if (style !== null) {
var startOfToken = stream.pos - current.length;

var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount);

var newScopes = [];
Expand All @@ -304,6 +389,8 @@ CodeMirror.defineMode("sass", function(config) {
tokenizer: tokenBase,
scopes: [{offset: 0, type: "sass"}],
indentCount: 0,
cursorHalf: 0, // cursor half tells us if cursor lies after (1)
// or before (0) colon (well... more or less)
definedVars: [],
definedMixins: []
};
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "codemirror",
"version":"4.11.0",
"version":"4.12.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
"url": "http://codemirror.net/LICENSE"}],
"directories": {"lib": "./lib"},
"scripts": {"test": "node ./test/run.js"},
"devDependencies": {"node-static": "0.6.0",
"phantomjs": "1.9.2-5"},
"phantomjs": "1.9.2-5",
"blint": ">=0.1.1"},
"bugs": "http://github.com/codemirror/CodeMirror/issues",
"keywords": ["JavaScript", "CodeMirror", "Editor"],
"homepage": "http://codemirror.net",
Expand Down
11 changes: 11 additions & 0 deletions test/lint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var blint = require("blint");

["mode", "lib", "addon", "keymap"].forEach(function(dir) {
blint.checkDir(dir, {
browser: true,
allowedGlobals: ["CodeMirror", "define", "test", "requirejs"],
blob: "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http:\/\/codemirror.net\/LICENSE\n\n"
});
});

module.exports = {ok: blint.success()};
1,782 changes: 0 additions & 1,782 deletions test/lint/acorn.js

This file was deleted.

166 changes: 0 additions & 166 deletions test/lint/lint.js

This file was deleted.

313 changes: 0 additions & 313 deletions test/lint/walk.js

This file was deleted.

9 changes: 1 addition & 8 deletions test/run.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
#!/usr/bin/env node

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

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

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

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

Expand Down
8 changes: 8 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,14 @@ testCM("getAllMarks", function(cm) {
eq(cm.getAllMarks().length, 2);
});

testCM("setValueClears", function(cm) {
cm.addLineClass(0, "wrap", "foo");
var mark = cm.markText(Pos(0, 0), Pos(1, 1), {inclusiveLeft: true, inclusiveRight: true});
cm.setValue("foo");
is(!cm.lineInfo(0).wrapClass);
is(!mark.find());
}, {value: "a\nb"});

testCM("bug577", function(cm) {
cm.setValue("a\nb");
cm.clearHistory();
Expand Down