7 changes: 6 additions & 1 deletion mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@
{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: "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"},
{name: "ECL", mime: "text/x-ecl", mode: "ecl", ext: ["ecl"]},
{name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel", ext: ["e"]},
{name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]},
{name: "Embedded Ruby", mime: "application/x-erb", mode: "htmlembedded", ext: ["erb"]},
{name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]},
{name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90"]},
{name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"], alias: ["fsharp"]},
Expand Down Expand Up @@ -63,7 +66,7 @@
{name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]},
{name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"], alias: ["ls"]},
{name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]},
{name: "Markdown (GitHub-flavour)", mime: "text/x-markdown", mode: "markdown", ext: ["markdown", "md", "mkd"]},
{name: "Markdown", mime: "text/x-markdown", mode: "markdown", ext: ["markdown", "md", "mkd"]},
{name: "mIRC", mime: "text/mirc", mode: "mirc"},
{name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"},
{name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]},
Expand Down Expand Up @@ -102,7 +105,9 @@
{name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]},
{name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"},
{name: "Solr", mime: "text/x-solr", mode: "solr"},
{name: "Soy", mime: "text/x-soy", mode: "soy", ext: ["soy"], alias: ["closure template"]},
{name: "SPARQL", mime: "application/sparql-query", mode: "sparql", ext: ["rq", "sparql"], alias: ["sparul"]},
{name: "Spreadsheet", mime: "text/x-spreadsheet", mode: "spreadsheet", alias: ["excel", "formula"]},
{name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]},
{name: "MariaDB", mime: "text/x-mariadb", mode: "sql"},
{name: "sTeX", mime: "text/x-stex", mode: "stex"},
Expand Down
4 changes: 2 additions & 2 deletions mode/puppet/puppet.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ CodeMirror.defineMode("puppet", function () {
return words[word];
}
// Is there a match on a reference?
if (/(\s+)?[A-Z]/.test(word)) {
if (/(^|\s+)[A-Z][\w:_]+/.test(word)) {
// Negate the next()
stream.backUp(1);
// Match the full reference
stream.match(/(\s+)?[A-Z][\w:_]+/);
stream.match(/(^|\s+)[A-Z][\w:_]+/);
return 'def';
}
// Have we matched the prior resource regex?
Expand Down
3 changes: 2 additions & 1 deletion mode/shell/shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ CodeMirror.defineMode('shell', function() {
token: function(stream, state) {
return tokenize(stream, state);
},
lineComment: '#'
lineComment: '#',
fold: "brace"
};
});

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

<title>CodeMirror: Soy (Closure Template) 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="../htmlmixed/htmlmixed.js"></script>
<script src="../xml/xml.js"></script>
<script src="../javascript/javascript.js"></script>
<script src="../css/css.js"></script>
<script src="soy.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>

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

<article>
<h2>Soy (Closure Template) mode</h2>
<form><textarea id="code" name="code">
{namespace example}

/**
* Says hello to the world.
*/
{template .helloWorld}
{@param name: string}
{@param? score: number}
Hello <b>{$name}</b>!
<div>
{if $score}
<em>{$score} points</em>
{else}
no score
{/if}
</div>
{/template}

{template .alertHelloWorld kind="js"}
alert('Hello World');
{/template}
</textarea></form>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-soy",
indentUnit: 2,
indentWithTabs: false
});
</script>

<p>A mode for <a href="https://developers.google.com/closure/templates/">Closure Templates</a> (Soy).</p>
<p><strong>MIME type defined:</strong> <code>text/x-soy</code>.</p>
</article>
198 changes: 198 additions & 0 deletions mode/soy/soy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// 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("../htmlmixed/htmlmixed"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

var indentingTags = ["template", "literal", "msg", "fallbackmsg", "let", "if", "elseif",
"else", "switch", "case", "default", "foreach", "ifempty", "for",
"call", "param", "deltemplate", "delcall", "log"];

CodeMirror.defineMode("soy", function(config) {
var textMode = CodeMirror.getMode(config, "text/plain");
var modes = {
html: CodeMirror.getMode(config, {name: "text/html", multilineTagIndentFactor: 2, multilineTagIndentPastTag: false}),
attributes: textMode,
text: textMode,
uri: textMode,
css: CodeMirror.getMode(config, "text/css"),
js: CodeMirror.getMode(config, {name: "text/javascript", statementIndent: 2 * config.indentUnit})
};

function last(array) {
return array[array.length - 1];
}

function tokenUntil(stream, state, untilRegExp) {
var oldString = stream.string;
var match = untilRegExp.exec(oldString.substr(stream.pos));
if (match) {
// We don't use backUp because it backs up just the position, not the state.
// This uses an undocumented API.
stream.string = oldString.substr(0, stream.pos + match.index);
}
var result = stream.hideFirstChars(state.indent, function() {
return state.localMode.token(stream, state.localState);
});
stream.string = oldString;
return result;
}

return {
startState: function() {
return {
kind: [],
kindTag: [],
soyState: [],
indent: 0,
localMode: modes.html,
localState: CodeMirror.startState(modes.html)
};
},

copyState: function(state) {
return {
tag: state.tag, // Last seen Soy tag.
kind: state.kind.concat([]), // Values of kind="" attributes.
kindTag: state.kindTag.concat([]), // Opened tags with kind="" attributes.
soyState: state.soyState.concat([]),
indent: state.indent, // Indentation of the following line.
localMode: state.localMode,
localState: CodeMirror.copyState(state.localMode, state.localState)
};
},

token: function(stream, state) {
var match;

switch (last(state.soyState)) {
case "comment":
if (stream.match(/^.*?\*\//)) {
state.soyState.pop();
} else {
stream.skipToEnd();
}
return "comment";

case "variable":
if (stream.match(/^}/)) {
state.indent -= 2 * config.indentUnit;
state.soyState.pop();
return "variable-2";
}
stream.next();
return null;

case "tag":
if (stream.match(/^\/?}/)) {
if (state.tag == "/template" || state.tag == "/deltemplate") state.indent = 0;
else state.indent -= (stream.current() == "/}" || indentingTags.indexOf(state.tag) == -1 ? 2 : 1) * config.indentUnit;
state.soyState.pop();
return "keyword";
} else if (stream.match(/^(\w+)(?==)/)) {
if (stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) {
var kind = match[1];
state.kind.push(kind);
state.kindTag.push(state.tag);
state.localMode = modes[kind] || modes.html;
state.localState = CodeMirror.startState(state.localMode);
}
return "attribute";
} else if (stream.match(/^"/)) {
state.soyState.push("string");
return "string";
}
stream.next();
return null;

case "literal":
if (stream.match(/^(?=\{\/literal})/)) {
state.indent -= config.indentUnit;
state.soyState.pop();
return this.token(stream, state);
}
return tokenUntil(stream, state, /\{\/literal}/);

case "string":
if (stream.match(/^.*?"/)) {
state.soyState.pop();
} else {
stream.skipToEnd();
}
return "string";
}

if (stream.match(/^\/\*/)) {
state.soyState.push("comment");
return "comment";
} else if (stream.match(stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) {
return "comment";
} else if (stream.match(/^\{\$\w*/)) {
state.indent += 2 * config.indentUnit;
state.soyState.push("variable");
return "variable-2";
} else if (stream.match(/^\{literal}/)) {
state.indent += config.indentUnit;
state.soyState.push("literal");
return "keyword";
} else if (match = stream.match(/^\{([\/@\\]?\w*)/)) {
if (match[1] != "/switch")
state.indent += (/^(\/|(else|elseif|case|default)$)/.test(match[1]) && state.tag != "switch" ? 1 : 2) * config.indentUnit;
state.tag = match[1];
if (state.tag == "/" + last(state.kindTag)) {
// We found the tag that opened the current kind="".
state.kind.pop();
state.kindTag.pop();
state.localMode = modes[last(state.kind)] || modes.html;
state.localState = CodeMirror.startState(state.localMode);
}
state.soyState.push("tag");
return "keyword";
}

return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/);
},

indent: function(state, textAfter) {
var indent = state.indent, top = last(state.soyState);
if (top == "comment") return CodeMirror.Pass;

if (top == "literal") {
if (/^\{\/literal}/.test(textAfter)) indent -= config.indentUnit;
} else {
if (/^\s*\{\/(template|deltemplate)\b/.test(textAfter)) return 0;
if (/^\{(\/|(fallbackmsg|elseif|else|ifempty)\b)/.test(textAfter)) indent -= config.indentUnit;
if (state.tag != "switch" && /^\{(case|default)\b/.test(textAfter)) indent -= config.indentUnit;
if (/^\{\/switch\b/.test(textAfter)) indent -= config.indentUnit;
}
if (indent && state.localMode.indent)
indent += state.localMode.indent(state.localState, textAfter);
return indent;
},

innerMode: function(state) {
if (state.soyState.length && last(state.soyState) != "literal") return null;
else return {state: state.localState, mode: state.localMode};
},

electricInput: /^\s*\{(\/|\/template|\/deltemplate|\/switch|fallbackmsg|elseif|else|case|default|ifempty|\/literal\})$/,
lineComment: "//",
blockCommentStart: "/*",
blockCommentEnd: "*/",
blockCommentContinue: " * ",
fold: "indent"
};
}, "htmlmixed");

CodeMirror.registerHelper("hintWords", "soy", indentingTags.concat(
["delpackage", "namespace", "alias", "print", "css", "debugger"]));

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

<title>CodeMirror: Spreadsheet 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="spreadsheet.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>

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

<article>
<h2>Spreadsheet mode</h2>
<form><textarea id="code" name="code">=IF(A1:B2, TRUE, FALSE) / 100</textarea></form>

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

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

<h3>The Spreadsheet Mode</h3>
<p> Created by <a href="https://github.com/robertleeplummerjr">Robert Plummer</a></p>
</article>
109 changes: 109 additions & 0 deletions mode/spreadsheet/spreadsheet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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("spreadsheet", function () {
return {
startState: function () {
return {
stringType: null,
stack: []
};
},
token: function (stream, state) {
if (!stream) return;

//check for state changes
if (state.stack.length === 0) {
//strings
if ((stream.peek() == '"') || (stream.peek() == "'")) {
state.stringType = stream.peek();
stream.next(); // Skip quote
state.stack.unshift("string");
}
}

//return state
//stack has
switch (state.stack[0]) {
case "string":
while (state.stack[0] === "string" && !stream.eol()) {
if (stream.peek() === state.stringType) {
stream.next(); // Skip quote
state.stack.shift(); // Clear flag
} else if (stream.peek() === "\\") {
stream.next();
stream.next();
} else {
stream.match(/^.[^\\\"\']*/);
}
}
return "string";

case "characterClass":
while (state.stack[0] === "characterClass" && !stream.eol()) {
if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./)))
state.stack.shift();
}
return "operator";
}

var peek = stream.peek();

//no stack
switch (peek) {
case "[":
stream.next();
state.stack.unshift("characterClass");
return "bracket";
case ":":
stream.next();
return "operator";
case "\\":
if (stream.match(/\\[a-z]+/)) return "string-2";
else return null;
case ".":
case ",":
case ";":
case "*":
case "-":
case "+":
case "^":
case "<":
case "/":
case "=":
stream.next();
return "atom";
case "$":
stream.next();
return "builtin";
}

if (stream.match(/\d+/)) {
if (stream.match(/^\w+/)) return "error";
return "number";
} else if (stream.match(/^[a-zA-Z_]\w*/)) {
if (stream.match(/(?=[\(.])/, false)) return "keyword";
return "variable-2";
} else if (["[", "]", "(", ")", "{", "}"].indexOf(peek) != -1) {
stream.next();
return "bracket";
} else if (!stream.eatSpace()) {
stream.next();
}
return null;
}
};
});

CodeMirror.defineMIME("text/x-spreadsheet", "spreadsheet");
});
10 changes: 4 additions & 6 deletions mode/stex/stex.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@
if (ch == "%") {
source.skipToEnd();
return "comment";
}
else if (ch == '}' || ch == ']') {
} else if (ch == '}' || ch == ']') {
plug = peekCommand(state);
if (plug) {
plug.closeBracket(ch);
Expand All @@ -145,12 +144,10 @@
plug = new plug();
pushCommand(state, plug);
return "bracket";
}
else if (/\d/.test(ch)) {
} else if (/\d/.test(ch)) {
source.eatWhile(/[\w.%]/);
return "atom";
}
else {
} else {
source.eatWhile(/[\w\-_]/);
plug = getMostPowerful(state);
if (plug.name == 'begin') {
Expand Down Expand Up @@ -242,6 +239,7 @@
},
blankLine: function(state) {
state.f = normal;
state.cmdState.length = 0;
},
lineComment: "%"
};
Expand Down
19 changes: 15 additions & 4 deletions mode/textile/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,16 @@
'[variable-2 # bar]');

MT('ulFormatting',
'[variable-2 * ][variable-2&em _foo_][variable-2 bar]',
'[variable-2 * ][variable-2&em _foo_][variable-2 bar]',
'[variable-2 * ][variable-2&strong *][variable-2&em&strong _foo_]' +
'[variable-2&strong *][variable-2 bar]',
'[variable-2 * ][variable-2&strong *foo*][variable-2 bar]');
'[variable-2 * ][variable-2&strong *foo*][variable-2 bar]');

MT('olFormatting',
'[variable-2 # ][variable-2&em _foo_][variable-2 bar]',
'[variable-2 # ][variable-2&em _foo_][variable-2 bar]',
'[variable-2 # ][variable-2&strong *][variable-2&em&strong _foo_]' +
'[variable-2&strong *][variable-2 bar]',
'[variable-2 # ][variable-2&strong *foo*][variable-2 bar]');
'[variable-2 # ][variable-2&strong *foo*][variable-2 bar]');

MT('ulNested',
'[variable-2 * foo]',
Expand Down Expand Up @@ -403,4 +403,15 @@
'[operator pre.. *No* formatting]',
'',
'[operator *No* formatting]');

/* Only toggling phrases between non-word chars. */

MT('phrase-in-word',
'foo_bar_baz');

MT('phrase-non-word',
'[negative -x-] aaa-bbb ccc-ddd [negative -eee-] fff [negative -ggg-]');

MT('phrase-lone-dash',
'foo - bar - baz');
})();
868 changes: 392 additions & 476 deletions mode/textile/textile.js

Large diffs are not rendered by default.

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

lineComment: "#"
};
});

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror",
"version":"4.8.0",
"version":"4.9.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
Expand Down
3 changes: 3 additions & 0 deletions test/emacs_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@
sim("clearMark", "abcde", Pos(0, 2), "Ctrl-Space", "Ctrl-F", "Ctrl-F",
"Ctrl-G", "Ctrl-W", txt("abcde"));

sim("delRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Delete", txt("cde"));
sim("backspaceRegion", "abcde", "Ctrl-Space", "Ctrl-F", "Ctrl-F", "Backspace", txt("cde"));

testCM("save", function(cm) {
var saved = false;
CodeMirror.commands.save = function(cm) { saved = cm.getValue(); };
Expand Down
1 change: 1 addition & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ <h2>Test Suite</h2>
<script src="test.js"></script>
<script src="doc_test.js"></script>
<script src="multi_test.js"></script>
<script src="scroll_test.js"></script>
<script src="comment_test.js"></script>
<script src="search_test.js"></script>
<script src="mode_test.js"></script>
Expand Down
105 changes: 105 additions & 0 deletions test/scroll_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
(function() {
"use strict";

namespace = "scroll_";

testCM("bars_hidden", function(cm) {
for (var i = 0;; i++) {
var wrapBox = cm.getWrapperElement().getBoundingClientRect();
var scrollBox = cm.getScrollerElement().getBoundingClientRect();
is(wrapBox.bottom < scrollBox.bottom - 10);
is(wrapBox.right < scrollBox.right - 10);
if (i == 1) break;
cm.getWrapperElement().style.height = "auto";
cm.refresh();
}
});

function barH(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; }
function barV(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-vscrollbar")[0]; }

function displayBottom(cm, scrollbar) {
if (scrollbar)
return barH(cm).getBoundingClientRect().top;
else
return cm.getWrapperElement().getBoundingClientRect().bottom - 1;
}

function displayRight(cm, scrollbar) {
if (scrollbar)
return barV(cm).getBoundingClientRect().left;
else
return cm.getWrapperElement().getBoundingClientRect().right - 1;
}

function testMovedownFixed(cm, hScroll) {
cm.setSize("100px", "100px");
if (hScroll) cm.setValue(new Array(100).join("x"));
var bottom = displayBottom(cm, hScroll);
for (var i = 0; i < 30; i++) {
cm.replaceSelection("x\n");
var cursorBottom = cm.cursorCoords(null, "window").bottom;
is(cursorBottom <= bottom);
}
is(cursorBottom >= bottom - 5);
}

testCM("movedown_fixed", function(cm) {testMovedownFixed(cm, false);});
testCM("movedown_hscroll_fixed", function(cm) {testMovedownFixed(cm, true);});

function testMovedownResize(cm, hScroll) {
cm.getWrapperElement().style.height = "auto";
if (hScroll) cm.setValue(new Array(100).join("x"));
cm.refresh();
for (var i = 0; i < 30; i++) {
cm.replaceSelection("x\n");
var bottom = displayBottom(cm, hScroll);
var cursorBottom = cm.cursorCoords(null, "window").bottom;
is(cursorBottom <= bottom);
is(cursorBottom >= bottom - 5);
}
}

testCM("movedown_resize", function(cm) {testMovedownResize(cm, false);});
testCM("movedown_hscroll_resize", function(cm) {testMovedownResize(cm, true);});

function testMoveright(cm, wrap, scroll) {
cm.setSize("100px", "100px");
if (wrap) cm.setOption("lineWrapping", true);
if (scroll) {
cm.setValue("\n" + new Array(100).join("x\n"));
cm.setCursor(Pos(0, 0));
}
var right = displayRight(cm, scroll);
for (var i = 0; i < 10; i++) {
cm.replaceSelection("xxxxxxxxxx");
var cursorRight = cm.cursorCoords(null, "window").right;
is(cursorRight < right);
}
if (!wrap) is(cursorRight > right - 20);
}

testCM("moveright", function(cm) {testMoveright(cm, false, false);});
testCM("moveright_wrap", function(cm) {testMoveright(cm, true, false);});
testCM("moveright_scroll", function(cm) {testMoveright(cm, false, true);});
testCM("moveright_scroll_wrap", function(cm) {testMoveright(cm, true, true);});

testCM("suddenly_wide", function(cm) {
addDoc(cm, 100, 100);
cm.replaceSelection(new Array(600).join("l ") + "\n");
cm.execCommand("goLineUp");
cm.execCommand("goLineEnd");
is(barH(cm).scrollLeft > cm.getScrollerElement().scrollLeft - 1);
});

testCM("wrap_changes_height", function(cm) {
var line = new Array(20).join("a ") + "\n";
cm.setValue(new Array(20).join(line));
var box = cm.getWrapperElement().getBoundingClientRect();
cm.setSize(cm.cursorCoords(Pos(0), "window").right - box.left + 2,
cm.cursorCoords(Pos(19, 0), "window").bottom - box.top + 2);
cm.setCursor(Pos(19, 0));
cm.replaceSelection("\n");
is(cm.cursorCoords(null, "window").bottom < displayBottom(cm, false));
}, {lineWrapping: true});
})();
42 changes: 34 additions & 8 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,17 @@ testCM("deleteSpanCollapsedInclusiveLeft", function(cm) {
cm.replaceRange("", from, to);
}, {value: "abc\nX\ndef"});

testCM("markTextCSS", function(cm) {
function present() {
var spans = cm.display.lineDiv.getElementsByTagName("span");
for (var i = 0; i < spans.length; i++)
if (spans[i].style.color == "cyan" && span[i].textContent == "cdefg") return true;
}
var m = cm.markText(Pos(0, 2), Pos(0, 6), {css: "color: cyan"});
m.clear();
is(!present());
}, {value: "abcdefgh"});

testCM("bookmark", function(cm) {
function p(v) { return v && Pos(v[0], v[1]); }
forEach([{a: [1, 0], b: [1, 1], c: "", d: [1, 4]},
Expand Down Expand Up @@ -1533,33 +1544,48 @@ testCM("jumpTheGap", function(cm) {
}, {lineWrapping: true, value: "abc\ndef\nghi\njkl\n"});

testCM("addLineClass", function(cm) {
function cls(line, text, bg, wrap) {
function cls(line, text, bg, wrap, gutter) {
var i = cm.lineInfo(line);
eq(i.textClass, text);
eq(i.bgClass, bg);
eq(i.wrapClass, wrap);
if (typeof i.handle.gutterClass !== 'undefined') {
eq(i.handle.gutterClass, gutter);
}
}
cm.addLineClass(0, "text", "foo");
cm.addLineClass(0, "text", "bar");
cm.addLineClass(1, "background", "baz");
cm.addLineClass(1, "wrap", "foo");
cls(0, "foo bar", null, null);
cls(1, null, "baz", "foo");
cm.addLineClass(1, "gutter", "gutter-class");
cls(0, "foo bar", null, null, null);
cls(1, null, "baz", "foo", "gutter-class");
var lines = cm.display.lineDiv;
eq(byClassName(lines, "foo").length, 2);
eq(byClassName(lines, "bar").length, 1);
eq(byClassName(lines, "baz").length, 1);
eq(byClassName(lines, "gutter-class").length, 1);
cm.removeLineClass(0, "text", "foo");
cls(0, "bar", null, null);
cls(0, "bar", null, null, null);
cm.removeLineClass(0, "text", "foo");
cls(0, "bar", null, null);
cls(0, "bar", null, null, null);
cm.removeLineClass(0, "text", "bar");
cls(0, null, null, null);

cm.addLineClass(1, "wrap", "quux");
cls(1, null, "baz", "foo quux");
cls(1, null, "baz", "foo quux", "gutter-class");
cm.removeLineClass(1, "wrap");
cls(1, null, "baz", null);
}, {value: "hohoho\n"});
cls(1, null, "baz", null, "gutter-class");
cm.removeLineClass(1, "gutter", "gutter-class");
eq(byClassName(lines, "gutter-class").length, 0);
cls(1, null, "baz", null, null);

cm.addLineClass(1, "gutter", "gutter-class");
cls(1, null, "baz", null, "gutter-class");
cm.removeLineClass(1, "gutter", "gutter-class");
cls(1, null, "baz", null, null);

}, {value: "hohoho\n", lineNumbers: true});

testCM("atomicMarker", function(cm) {
addDoc(cm, 10, 10);
Expand Down
83 changes: 83 additions & 0 deletions test/vim_test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
CodeMirror.Vim.suppressErrorLogging = true;

var code = '' +
' wOrd1 (#%\n' +
' word3] \n' +
Expand Down Expand Up @@ -502,6 +504,80 @@ testVim('{', function(cm, vim, helpers) {
helpers.doKeys('6', '{');
helpers.assertCursorAt(0, 0);
}, { value: 'a\n\nb\nc\n\nd' });
testVim('paragraph motions', function(cm, vim, helpers) {
cm.setCursor(10, 0);
helpers.doKeys('{');
helpers.assertCursorAt(4, 0);
helpers.doKeys('{');
helpers.assertCursorAt(0, 0);
helpers.doKeys('2', '}');
helpers.assertCursorAt(7, 0);
helpers.doKeys('2', '}');
helpers.assertCursorAt(16, 0);

cm.setCursor(9, 0);
helpers.doKeys('}');
helpers.assertCursorAt(14, 0);

cm.setCursor(6, 0);
helpers.doKeys('}');
helpers.assertCursorAt(7, 0);

// ip inside empty space
cm.setCursor(10, 0);
helpers.doKeys('v', 'i', 'p');
eqPos(Pos(7, 0), cm.getCursor('anchor'));
eqPos(Pos(12, 0), cm.getCursor('head'));
helpers.doKeys('i', 'p');
eqPos(Pos(7, 0), cm.getCursor('anchor'));
eqPos(Pos(13, 1), cm.getCursor('head'));
helpers.doKeys('2', 'i', 'p');
eqPos(Pos(7, 0), cm.getCursor('anchor'));
eqPos(Pos(16, 1), cm.getCursor('head'));

// should switch to visualLine mode
cm.setCursor(14, 0);
helpers.doKeys('<Esc>', 'v', 'i', 'p');
helpers.assertCursorAt(14, 0);

cm.setCursor(14, 0);
helpers.doKeys('<Esc>', 'V', 'i', 'p');
eqPos(Pos(16, 1), cm.getCursor('head'));

// ap inside empty space
cm.setCursor(10, 0);
helpers.doKeys('<Esc>', 'v', 'a', 'p');
eqPos(Pos(7, 0), cm.getCursor('anchor'));
eqPos(Pos(13, 1), cm.getCursor('head'));
helpers.doKeys('a', 'p');
eqPos(Pos(7, 0), cm.getCursor('anchor'));
eqPos(Pos(16, 1), cm.getCursor('head'));

cm.setCursor(13, 0);
helpers.doKeys('v', 'a', 'p');
eqPos(Pos(13, 0), cm.getCursor('anchor'));
eqPos(Pos(14, 0), cm.getCursor('head'));

cm.setCursor(16, 0);
helpers.doKeys('v', 'a', 'p');
eqPos(Pos(14, 0), cm.getCursor('anchor'));
eqPos(Pos(16, 1), cm.getCursor('head'));

cm.setCursor(0, 0);
helpers.doKeys('v', 'a', 'p');
eqPos(Pos(0, 0), cm.getCursor('anchor'));
eqPos(Pos(4, 0), cm.getCursor('head'));

cm.setCursor(0, 0);
helpers.doKeys('d', 'i', 'p');
var register = helpers.getRegisterController().getRegister();
eq('a\na\n', register.toString());
is(register.linewise);
helpers.doKeys('3', 'j', 'p');
helpers.doKeys('y', 'i', 'p');
is(register.linewise);
eq('b\na\na\nc\n', register.toString());
}, { value: 'a\na\n\n\n\nb\nc\n\n\n\n\n\n\nd\n\ne\nf' });

// Operator tests
testVim('dl', function(cm, vim, helpers) {
Expand Down Expand Up @@ -1793,6 +1869,13 @@ testVim('visual_line', function(cm, vim, helpers) {
helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd');
eq(' 4\n 5', cm.getValue());
}, { value: ' 1\n 2\n 3\n 4\n 5' });
testVim('visual_block_move_to_eol', function(cm, vim, helpers) {
// moveToEol should move all block cursors to end of line
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', 'G', '$');
var selections = cm.getSelections().join();
eq("123,45,6", selections);
}, {value: '123\n45\n6'});
testVim('visual_block_different_line_lengths', function(cm, vim, helpers) {
// test the block selection with lines of different length
// i.e. extending the selection
Expand Down
35 changes: 35 additions & 0 deletions theme/tomorrow-night-bright.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Name: Tomorrow Night - Bright
Author: Chris Kempson
Port done by Gerard Braad <me@gbraad.nl>
*/

.cm-s-tomorrow-night-bright.CodeMirror {background: #000000; color: #eaeaea;}
.cm-s-tomorrow-night-bright div.CodeMirror-selected {background: #424242 !important;}
.cm-s-tomorrow-night-bright .CodeMirror-gutters {background: #000000; border-right: 0px;}
.cm-s-tomorrow-night-bright .CodeMirror-guttermarker { color: #e78c45; }
.cm-s-tomorrow-night-bright .CodeMirror-guttermarker-subtle { color: #777; }
.cm-s-tomorrow-night-bright .CodeMirror-linenumber {color: #424242;}
.cm-s-tomorrow-night-bright .CodeMirror-cursor {border-left: 1px solid #6A6A6A !important;}

.cm-s-tomorrow-night-bright span.cm-comment {color: #d27b53;}
.cm-s-tomorrow-night-bright span.cm-atom {color: #a16a94;}
.cm-s-tomorrow-night-bright span.cm-number {color: #a16a94;}

.cm-s-tomorrow-night-bright span.cm-property, .cm-s-tomorrow-night-bright span.cm-attribute {color: #99cc99;}
.cm-s-tomorrow-night-bright span.cm-keyword {color: #d54e53;}
.cm-s-tomorrow-night-bright span.cm-string {color: #e7c547;}

.cm-s-tomorrow-night-bright span.cm-variable {color: #b9ca4a;}
.cm-s-tomorrow-night-bright span.cm-variable-2 {color: #7aa6da;}
.cm-s-tomorrow-night-bright span.cm-def {color: #e78c45;}
.cm-s-tomorrow-night-bright span.cm-bracket {color: #eaeaea;}
.cm-s-tomorrow-night-bright span.cm-tag {color: #d54e53;}
.cm-s-tomorrow-night-bright span.cm-link {color: #a16a94;}
.cm-s-tomorrow-night-bright span.cm-error {background: #d54e53; color: #6A6A6A;}

.cm-s-tomorrow-night-bright .CodeMirror-activeline-background {background: #2a2a2a !important;}
.cm-s-tomorrow-night-bright .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
37 changes: 37 additions & 0 deletions theme/zenburn.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* "
* Using Zenburn color palette from the Emacs Zenburn Theme
* https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el
*
* Also using parts of https://github.com/xavi/coderay-lighttable-theme
* "
* From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css
*/

.cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; }
.cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { color: #999; }
.cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white !important; }
.cm-s-zenburn { background-color: #3f3f3f; color: #dcdccc; }
.cm-s-zenburn span.cm-builtin { color: #dcdccc; font-weight: bold; }
.cm-s-zenburn span.cm-comment { color: #7f9f7f; }
.cm-s-zenburn span.cm-keyword { color: #f0dfaf; font-weight: bold; }
.cm-s-zenburn span.cm-atom { color: #bfebbf; }
.cm-s-zenburn span.cm-def { color: #dcdccc; }
.cm-s-zenburn span.cm-variable { color: #dfaf8f; }
.cm-s-zenburn span.cm-variable-2 { color: #dcdccc; }
.cm-s-zenburn span.cm-string { color: #cc9393; }
.cm-s-zenburn span.cm-string-2 { color: #cc9393; }
.cm-s-zenburn span.cm-number { color: #dcdccc; }
.cm-s-zenburn span.cm-tag { color: #93e0e3; }
.cm-s-zenburn span.cm-property { color: #dfaf8f; }
.cm-s-zenburn span.cm-attribute { color: #dfaf8f; }
.cm-s-zenburn span.cm-qualifier { color: #7cb8bb; }
.cm-s-zenburn span.cm-meta { color: #f0dfaf; }
.cm-s-zenburn span.cm-header { color: #f0efd0; }
.cm-s-zenburn span.cm-operator { color: #f0efd0; }
.cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box; background: transparent; border-bottom: 1px solid; }
.cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid; background: none; }
.cm-s-zenburn .CodeMirror-activeline { background: #000000; }
.cm-s-zenburn .CodeMirror-activeline-background { background: #000000; }
.cm-s-zenburn .CodeMirror-selected { background: #545454; }
.cm-s-zenburn .CodeMirror-focused .CodeMirror-selected { background: #4f4f4f; }