32 changes: 14 additions & 18 deletions mode/xml/xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,19 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
}
else if (stream.match("--")) return chain(inBlock("comment", "-->"));
else if (stream.match("DOCTYPE", true, true)) {
} else if (stream.match("--")) {
return chain(inBlock("comment", "-->"));
} else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
} else {
return null;
}
else return null;
}
else if (stream.eat("?")) {
} else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
}
else {
} else {
var isClose = stream.eat("/");
tagName = "";
var c;
Expand All @@ -81,8 +80,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
state.tokenize = inTag;
return "tag";
}
}
else if (ch == "&") {
} else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
Expand All @@ -94,8 +92,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
}
else {
} else {
stream.eatWhile(/[^&<]/);
return null;
}
Expand All @@ -107,16 +104,15 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag";
}
else if (ch == "=") {
} else if (ch == "=") {
type = "equals";
return null;
}
else if (/[\'\"]/.test(ch)) {
} else if (ch == "<") {
return "error";
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
return state.tokenize(stream, state);
}
else {
} else {
stream.eatWhile(/[^\s\u00a0=<>\"\']/);
return "word";
}
Expand Down
8 changes: 3 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror",
"version":"3.13.00",
"version":"3.14.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
Expand All @@ -14,8 +14,6 @@
"maintainers":[{"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"web": "http://marijnhaverbeke.nl"}],
"repositories": [{"type": "git",
"url": "http://marijnhaverbeke.nl/git/codemirror"},
{"type": "git",
"url": "https://github.com/marijnh/CodeMirror.git"}]
"repository": {"type": "git",
"url": "http://marijnhaverbeke.nl/git/codemirror"}
}
5 changes: 5 additions & 0 deletions test/comment_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ namespace = "comment_";
test("indented", "javascript", function(cm) {
cm.lineComment(Pos(1, 0), Pos(2), {indent: true});
}, simpleProg, "function foo() {\n // return bar;\n // }");

test("singleEmptyLine", "javascript", function(cm) {
cm.setCursor(1);
cm.execCommand("toggleComment");
}, "a;\n\nb;", "a;\n// \nb;");
})();
2 changes: 1 addition & 1 deletion test/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function test(name, run, expectedFail) {
var namespace = "";
function testCM(name, run, opts, expectedFail) {
return test(namespace + name, function() {
var place = document.getElementById("testground"), cm = CodeMirror(place, opts);
var place = document.getElementById("testground"), cm = window.cm = CodeMirror(place, opts);
var successful = false;
try {
run(cm);
Expand Down
135 changes: 135 additions & 0 deletions test/emacs_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
(function() {
"use strict";

var Pos = CodeMirror.Pos;
namespace = "emacs_";

var eventCache = {};
function fakeEvent(keyName) {
var event = eventCache[key];
if (event) return event;

var ctrl, shift, alt;
var key = keyName.replace(/\w+-/g, function(type) {
if (type == "Ctrl-") ctrl = true;
else if (type == "Alt-") alt = true;
else if (type == "Shift-") shift = true;
return "";
});
var code;
for (var c in CodeMirror.keyNames)
if (CodeMirror.keyNames[c] == key) { code = c; break; }
if (c == null) throw new Error("Unknown key: " + key);

return eventCache[keyName] = {
type: "keydown", keyCode: code, ctrlKey: ctrl, shiftKey: shift, altKey: alt,
preventDefault: function(){}, stopPropagation: function(){}
};
}

function sim(name, start /*, actions... */) {
var keys = Array.prototype.slice.call(arguments, 2);
testCM(name, function(cm) {
for (var i = 0; i < keys.length; ++i) {
var cur = keys[i];
if (cur instanceof Pos) cm.setCursor(cur);
else if (cur.call) cur(cm);
else cm.triggerOnKeyDown(fakeEvent(cur));
}
}, {keyMap: "emacs", value: start, mode: "javascript"});
}

function at(line, ch) { return function(cm) { eqPos(cm.getCursor(), Pos(line, ch)); }; }
function txt(str) { return function(cm) { eq(cm.getValue(), str); }; }

sim("motionHSimple", "abc", "Ctrl-F", "Ctrl-F", "Ctrl-B", at(0, 1));
sim("motionHMulti", "abcde",
"Ctrl-4", "Ctrl-F", at(0, 4), "Ctrl--", "Ctrl-2", "Ctrl-F", at(0, 2),
"Ctrl-5", "Ctrl-B", at(0, 0));

sim("motionHWord", "abc. def ghi",
"Alt-F", at(0, 3), "Alt-F", at(0, 8),
"Ctrl-B", "Alt-B", at(0, 5), "Alt-B", at(0, 0));
sim("motionHWordMulti", "abc. def ghi ",
"Ctrl-3", "Alt-F", at(0, 12), "Ctrl-2", "Alt-B", at(0, 5),
"Ctrl--", "Alt-B", at(0, 8));

sim("motionVSimple", "a\nb\nc\n", "Ctrl-N", "Ctrl-N", "Ctrl-P", at(1, 0));
sim("motionVMulti", "a\nb\nc\nd\ne\n",
"Ctrl-2", "Ctrl-N", at(2, 0), "Ctrl-F", "Ctrl--", "Ctrl-N", at(1, 1),
"Ctrl--", "Ctrl-3", "Ctrl-P", at(4, 1));

sim("killYank", "abc\ndef\nghi",
"Ctrl-F", "Ctrl-Space", "Ctrl-N", "Ctrl-N", "Ctrl-W", "Ctrl-E", "Ctrl-Y",
txt("ahibc\ndef\ng"));
sim("killRing", "abcdef",
"Ctrl-Space", "Ctrl-F", "Ctrl-W", "Ctrl-Space", "Ctrl-F", "Ctrl-W",
"Ctrl-Y", "Alt-Y",
txt("acdef"));
sim("copyYank", "abcd",
"Ctrl-Space", "Ctrl-E", "Alt-W", "Ctrl-Y",
txt("abcdabcd"));

sim("killLineSimple", "foo\nbar", "Ctrl-F", "Ctrl-K", txt("f\nbar"));
sim("killLineEmptyLine", "foo\n \nbar", "Ctrl-N", "Ctrl-K", txt("foo\nbar"));
sim("killLineMulti", "foo\nbar\nbaz",
"Ctrl-F", "Ctrl-F", "Ctrl-K", "Ctrl-K", "Ctrl-K", "Ctrl-A", "Ctrl-Y",
txt("o\nbarfo\nbaz"));

sim("moveByParagraph", "abc\ndef\n\n\nhij\nklm\n\n",
"Ctrl-F", "Ctrl-Down", at(2, 0), "Ctrl-Down", at(6, 0),
"Ctrl-N", "Ctrl-Up", at(3, 0), "Ctrl-Up", at(0, 0),
Pos(1, 2), "Ctrl-Down", at(2, 0), Pos(4, 2), "Ctrl-Up", at(3, 0));
sim("moveByParagraphMulti", "abc\n\ndef\n\nhij\n\nklm",
"Ctrl-U", "2", "Ctrl-Down", at(3, 0),
"Shift-Alt-.", "Ctrl-3", "Ctrl-Up", at(1, 0));

sim("moveBySentence", "sentence one! sentence\ntwo\n\nparagraph two",
"Alt-E", at(0, 13), "Alt-E", at(1, 3), "Ctrl-F", "Alt-A", at(0, 13));

sim("moveByExpr", "function foo(a, b) {}",
"Ctrl-Alt-F", at(0, 8), "Ctrl-Alt-F", at(0, 12), "Ctrl-Alt-F", at(0, 18),
"Ctrl-Alt-B", at(0, 12), "Ctrl-Alt-B", at(0, 9));
sim("moveByExprMulti", "foo bar baz bug",
"Ctrl-2", "Ctrl-Alt-F", at(0, 7),
"Ctrl--", "Ctrl-Alt-F", at(0, 4),
"Ctrl--", "Ctrl-2", "Ctrl-Alt-B", at(0, 11));
sim("delExpr", "var x = [\n a,\n b\n c\n];",
Pos(0, 8), "Ctrl-Alt-K", txt("var x = ;"), "Ctrl-/",
Pos(4, 1), "Ctrl-Alt-Backspace", txt("var x = ;"));
sim("delExprMulti", "foo bar baz",
"Ctrl-2", "Ctrl-Alt-K", txt(" baz"),
"Ctrl-/", "Ctrl-E", "Ctrl-2", "Ctrl-Alt-Backspace", txt("foo "));

sim("justOneSpace", "hi bye ",
Pos(0, 4), "Alt-Space", txt("hi bye "),
Pos(0, 4), "Alt-Space", txt("hi b ye "),
"Ctrl-A", "Alt-Space", "Ctrl-E", "Alt-Space", txt(" hi b ye "));

sim("openLine", "foo bar", "Alt-F", "Ctrl-O", txt("foo\n bar"))

sim("transposeChar", "abcd\n\ne",
"Ctrl-F", "Ctrl-T", "Ctrl-T", txt("bcad\n\ne"), at(0, 3),
"Ctrl-F", "Ctrl-T", "Ctrl-T", "Ctrl-T", txt("bcda\n\ne"), at(0, 4),
"Ctrl-F", "Ctrl-T", txt("bcd\na\ne"), at(1, 1));

sim("manipWordCase", "foo BAR bAZ",
"Alt-C", "Alt-L", "Alt-U", txt("Foo bar BAZ"),
"Ctrl-A", "Alt-U", "Alt-L", "Alt-C", txt("FOO bar Baz"));
sim("manipWordCaseMulti", "foo Bar bAz",
"Ctrl-2", "Alt-U", txt("FOO BAR bAz"),
"Ctrl-A", "Ctrl-3", "Alt-C", txt("Foo Bar Baz"));

sim("upExpr", "foo {\n bar[];\n baz(blah);\n}",
Pos(2, 7), "Ctrl-Alt-U", at(2, 5), "Ctrl-Alt-U", at(0, 4));
sim("transposeExpr", "do foo[bar] dah",
Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah"));

testCM("save", function(cm) {
var saved = false;
CodeMirror.commands.save = function(cm) { saved = cm.getValue(); };
cm.triggerOnKeyDown(fakeEvent("Ctrl-X"));
cm.triggerOnKeyDown(fakeEvent("Ctrl-S"));
is(saved, "hi");
}, {value: "hi", keyMap: "emacs"});
})();
5 changes: 5 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
<link rel="stylesheet" href="mode_test.css">
<script src="../lib/codemirror.js"></script>
<script src="../addon/mode/overlay.js"></script>
<script src="../addon/mode/multiplex.js"></script>
<script src="../addon/search/searchcursor.js"></script>
<script src="../addon/dialog/dialog.js"></script>
<script src="../addon/edit/matchbrackets.js"></script>
<script src="../addon/comment/comment.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/xml/xml.js"></script>
<script src="../keymap/vim.js"></script>
<script src="../keymap/emacs.js"></script>

<style type="text/css">
.ok {color: #090;}
Expand Down Expand Up @@ -73,7 +76,9 @@ <h1>CodeMirror: Test Suite</h1>
<script src="../mode/stex/test.js"></script>
<script src="../mode/xquery/xquery.js"></script>
<script src="../mode/xquery/test.js"></script>
<script src="../addon/mode/multiplex_test.js"></script>
<script src="vim_test.js"></script>
<script src="emacs_test.js"></script>
<script>
window.onload = runHarness;
CodeMirror.on(window, 'hashchange', runHarness);
Expand Down
2 changes: 1 addition & 1 deletion test/lint/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function checkDir(dir) {
fs.readdirSync(dir).forEach(function(file) {
var fname = dir + "/" + file;
if (/\.js$/.test(file)) checkFile(fname);
else if (fs.lstatSync(fname).isDirectory()) checkDir(fname);
else if (file != "dep" && fs.lstatSync(fname).isDirectory()) checkDir(fname);
});
}

Expand Down
6 changes: 3 additions & 3 deletions test/mode_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@
var s = '';
if (pass) {
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + text + '</pre>';
s += '<pre>' + text.replace('&', '&amp;').replace('<', '&lt;') + '</pre>';
s += '<div class="cm-s-default">';
s += prettyPrintOutputTable(observedOutput);
s += '</div>';
s += '</div>';
return s;
} else {
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + text + '</pre>';
s += '<pre>' + text.replace('&', '&amp;').replace('<', '&lt;') + '</pre>';
s += '<div class="cm-s-default">';
s += 'expected:';
s += prettyPrintOutputTable(expectedOutput);
Expand Down Expand Up @@ -178,7 +178,7 @@
s +=
'<td class="mt-token">' +
'<span class="cm-' + String(style).replace(/ +/g, " cm-") + '">' +
val.replace(/ /g,'\xb7') +
val.replace(/ /g,'\xb7').replace('&', '&amp;').replace('<', '&lt;') +
'</span>' +
'</td>';
}
Expand Down
1 change: 1 addition & 0 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var lint = require("./lint/lint");
lint.checkDir("mode");
lint.checkDir("lib");
lint.checkDir("addon");
lint.checkDir("keymap");

var ok = lint.success();

Expand Down
95 changes: 87 additions & 8 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ testCM("indent", function(cm) {
eq(cm.getLine(1), "\t\t blah();");
}, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8});

testCM("indentByNumber", function(cm) {
cm.indentLine(0, 2);
eq(cm.getLine(0), " foo");
cm.indentLine(0, -200);
eq(cm.getLine(0), "foo");
cm.setSelection(Pos(0, 0), Pos(1, 2));
cm.indentSelection(3);
eq(cm.getValue(), " foo\n bar\nbaz");
}, {value: "foo\nbar\nbaz"});

test("core_defaults", function() {
var defsCopy = {}, defs = CodeMirror.defaults;
for (var opt in defs) defsCopy[opt] = defs[opt];
Expand Down Expand Up @@ -211,15 +221,18 @@ testCM("coords", function(cm) {

testCM("coordsChar", function(cm) {
addDoc(cm, 35, 70);
for (var ch = 0; ch <= 35; ch += 5) {
for (var line = 0; line < 70; line += 5) {
cm.setCursor(line, ch);
var coords = cm.charCoords(Pos(line, ch));
var pos = cm.coordsChar({left: coords.left, top: coords.top + 5});
eqPos(pos, Pos(line, ch));
for (var i = 0; i < 2; ++i) {
var sys = i ? "local" : "page";
for (var ch = 0; ch <= 35; ch += 5) {
for (var line = 0; line < 70; line += 5) {
cm.setCursor(line, ch);
var coords = cm.charCoords(Pos(line, ch), sys);
var pos = cm.coordsChar({left: coords.left + 1, top: coords.top + 1}, sys);
eqPos(pos, Pos(line, ch));
}
}
}
});
}, {lineNumbers: true});

testCM("posFromIndex", function(cm) {
cm.setValue(
Expand Down Expand Up @@ -447,6 +460,13 @@ testCM("markClearBetween", function(cm) {
eq(cm.findMarksAt(Pos(1, 1)).length, 0);
});

testCM("deleteSpanCollapsedInclusiveLeft", function(cm) {
var from = Pos(1, 0), to = Pos(1, 1);
var m = cm.markText(from, to, {collapsed: true, inclusiveLeft: true});
// Delete collapsed span.
cm.replaceRange("", from, to);
}, {value: "abc\nX\ndef"});

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 @@ -513,6 +533,25 @@ testCM("scrollSnap", function(cm) {
is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom");
});

testCM("scrollIntoView", function(cm) {
if (phantom) return;
var outer = cm.getWrapperElement().getBoundingClientRect();
function test(line, ch) {
var pos = Pos(line, ch);
cm.scrollIntoView(pos);
var box = cm.charCoords(pos, "window");
is(box.left >= outer.left && box.right <= outer.right &&
box.top >= outer.top && box.bottom <= outer.bottom);
}
addDoc(cm, 200, 200);
test(199, 199);
test(0, 0);
test(100, 100);
test(199, 0);
test(0, 199);
test(100, 100);
});

testCM("selectionPos", function(cm) {
if (phantom) return;
cm.setSize(100, 100);
Expand Down Expand Up @@ -684,6 +723,7 @@ testCM("everythingFolded", function(cm) {
});

testCM("structuredFold", function(cm) {
if (phantom) return;
addDoc(cm, 4, 8);
var range = cm.markText(Pos(1, 2), Pos(6, 2), {
replacedWith: document.createTextNode("Q")
Expand All @@ -697,7 +737,7 @@ testCM("structuredFold", function(cm) {
eq(cm.getValue(), "xxxx\nxxxx\nxxxx");
addDoc(cm, 4, 8);
range = cm.markText(Pos(1, 2), Pos(6, 2), {
replacedWith: document.createTextNode("x"),
replacedWith: document.createTextNode("M"),
clearOnEnter: true
});
var cleared = 0;
Expand All @@ -718,6 +758,12 @@ testCM("structuredFold", function(cm) {
cm.setCursor(1, 2);
CodeMirror.commands.goCharRight(cm);
eqPos(cm.getCursor(), Pos(1, 3));
range = cm.markText(Pos(2, 0), Pos(4, 4), {
replacedWith: document.createTextNode("M")
});
cm.setCursor(1, 0);
CodeMirror.commands.goLineDown(cm);
eqPos(cm.getCursor(), Pos(2, 0));
}, null);

testCM("nestedFold", function(cm) {
Expand Down Expand Up @@ -757,6 +803,26 @@ testCM("badNestedFold", function(cm) {
is(/overlap/i.test(caught.message), "wrong error");
});

testCM("wrappingInlineWidget", function(cm) {
cm.setSize("11em");
var w = document.createElement("span");
w.style.color = "red";
w.innerHTML = "one two three four";
cm.markText(Pos(0, 6), Pos(0, 9), {replacedWith: w});
var cur0 = cm.cursorCoords(Pos(0, 0)), cur1 = cm.cursorCoords(Pos(0, 10));
is(cur0.top < cur1.top);
is(cur0.bottom < cur1.bottom);
var curL = cm.cursorCoords(Pos(0, 6)), curR = cm.cursorCoords(Pos(0, 9));
eq(curL.top, cur0.top);
eq(curL.bottom, cur0.bottom);
eq(curR.top, cur1.top);
eq(curR.bottom, cur1.bottom);
cm.replaceRange("", Pos(0, 9), Pos(0));
curR = cm.cursorCoords(Pos(0, 9));
eq(curR.top, cur1.top);
eq(curR.bottom, cur1.bottom);
}, {value: "1 2 3 xxx 4", lineWrapping: true});

testCM("inlineWidget", function(cm) {
var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")});
cm.setCursor(0, 2);
Expand Down Expand Up @@ -1358,6 +1424,19 @@ testCM("beforeChange", function(cm) {
eq(cm.getValue(), "hello,_i_am_a\nhey_hey_hey");
}, {value: "abcdefghijk"});

testCM("beforeChangeUndo", function(cm) {
cm.setLine(0, "hi");
cm.setLine(0, "bye");
eq(cm.historySize().undo, 2);
cm.on("beforeChange", function(cm, change) {
is(!change.update);
change.cancel();
});
cm.undo();
eq(cm.historySize().undo, 0);
eq(cm.getValue(), "bye\ntwo");
}, {value: "one\ntwo"});

testCM("beforeSelectionChange", function(cm) {
function notAtEnd(cm, pos) {
var len = cm.getLine(pos.line).length;
Expand Down
292 changes: 290 additions & 2 deletions test/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var code = '' +
' bufp = buf;\n' +
' }\n' +
'\n' +
' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' +
' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' +
' \n' +
'}\n';

Expand Down Expand Up @@ -124,6 +124,31 @@ function testVim(name, run, opts, expectedFail) {
}
}
}
function doInsertModeKeysFn(cm) {
return function(args) {
if (args instanceof Array) { arguments = args; }
function executeHandler(handler) {
if (typeof handler == 'string') {
CodeMirror.commands[handler](cm);
} else {
handler(cm);
}
return true;
}
for (var i = 0; i < arguments.length; i++) {
var key = arguments[i];
// Find key in keymap and handle.
var handled = CodeMirror.lookupKey(key, ['vim-insert'], executeHandler);
// Record for insert mode.
if (handled === true && cm.vimState.insertMode && arguments[i] != 'Esc') {
var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges;
if (lastChange) {
lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key));
}
}
}
}
}
function doExFn(cm) {
return function(command) {
cm.openDialog = helpers.fakeOpenDialog(command);
Expand All @@ -148,6 +173,10 @@ function testVim(name, run, opts, expectedFail) {
}
var helpers = {
doKeys: doKeysFn(cm),
// Warning: Only emulates keymap events, not character insertions. Use
// replaceRange to simulate character insertions.
// Keys are in CodeMirror format, NOT vim format.
doInsertModeKeys: doInsertModeKeysFn(cm),
doEx: doExFn(cm),
assertCursorAt: assertCursorAtFn(cm),
fakeOpenDialog: fakeOpenDialog,
Expand Down Expand Up @@ -782,6 +811,19 @@ testVim('dd_multiply_repeat', function(cm, vim, helpers) {
is(register.linewise);
helpers.assertCursorAt(0, lines[6].textStart);
});
testVim('dd_lastline', function(cm, vim, helpers) {
cm.setCursor(cm.lineCount(), 0);
var expectedLineCount = cm.lineCount() - 1;
helpers.doKeys('d', 'd');
eq(expectedLineCount, cm.lineCount());
helpers.assertCursorAt(cm.lineCount() - 1, 0);
});
testVim('cw', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('c', '2', 'w');
eq(' word3', cm.getValue());
helpers.assertCursorAt(0, 0);
}, { value: 'word1 word2 word3'});
// Yank commands should behave the exact same as d commands, expect that nothing
// gets deleted.
testVim('yw_repeat', function(cm, vim, helpers) {
Expand Down Expand Up @@ -1031,8 +1073,22 @@ testVim('i', function(cm, vim, helpers) {
helpers.assertCursorAt(0, 1);
eq('vim-insert', cm.getOption('keyMap'));
});
testVim('i_repeat', function(cm, vim, helpers) {
helpers.doKeys('3', 'i');
cm.replaceRange('test', cm.getCursor());
helpers.doInsertModeKeys('Esc');
eq('testtesttest', cm.getValue());
helpers.assertCursorAt(0, 11);
}, { value: '' });
testVim('i_repeat_delete', function(cm, vim, helpers) {
cm.setCursor(0, 4);
helpers.doKeys('2', 'i');
cm.replaceRange('z', cm.getCursor());
helpers.doInsertModeKeys('Backspace', 'Backspace', 'Esc');
eq('abe', cm.getValue());
helpers.assertCursorAt(0, 1);
}, { value: 'abcde' });
testVim('A', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('A');
helpers.assertCursorAt(0, lines[0].length);
eq('vim-insert', cm.getOption('keyMap'));
Expand All @@ -1043,13 +1099,29 @@ testVim('I', function(cm, vim, helpers) {
helpers.assertCursorAt(0, lines[0].textStart);
eq('vim-insert', cm.getOption('keyMap'));
});
testVim('I_repeat', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('3', 'I');
cm.replaceRange('test', cm.getCursor());
helpers.doInsertModeKeys('Esc');
eq('testtesttestblah', cm.getValue());
helpers.assertCursorAt(0, 11);
}, { value: 'blah' });
testVim('o', function(cm, vim, helpers) {
cm.setCursor(0, 4);
helpers.doKeys('o');
eq('word1\n\nword2', cm.getValue());
helpers.assertCursorAt(1, 0);
eq('vim-insert', cm.getOption('keyMap'));
}, { value: 'word1\nword2' });
testVim('o_repeat', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('3', 'o');
cm.replaceRange('test', cm.getCursor());
helpers.doInsertModeKeys('Esc');
eq('\ntest\ntest\ntest', cm.getValue());
helpers.assertCursorAt(3, 3);
}, { value: '' });
testVim('O', function(cm, vim, helpers) {
cm.setCursor(0, 4);
helpers.doKeys('O');
Expand Down Expand Up @@ -1552,6 +1624,97 @@ testVim('._repeat', function(cm, vim, helpers) {
helpers.doKeys('3', '.');
eq('6', cm.getValue());
}, { value: '1 2 3 4 5 6'});
testVim('._insert', function(cm, vim, helpers) {
helpers.doKeys('i');
cm.replaceRange('test', cm.getCursor());
helpers.doInsertModeKeys('Esc');
helpers.doKeys('.');
eq('testestt', cm.getValue());
helpers.assertCursorAt(0, 6);
}, { value: ''});
testVim('._insert_repeat', function(cm, vim, helpers) {
helpers.doKeys('i');
cm.replaceRange('test', cm.getCursor());
cm.setCursor(0, 4);
helpers.doInsertModeKeys('Esc');
helpers.doKeys('2', '.');
eq('testesttestt', cm.getValue());
helpers.assertCursorAt(0, 10);
}, { value: ''});
testVim('._repeat_insert', function(cm, vim, helpers) {
helpers.doKeys('3', 'i');
cm.replaceRange('te', cm.getCursor());
cm.setCursor(0, 2);
helpers.doInsertModeKeys('Esc');
helpers.doKeys('.');
eq('tetettetetee', cm.getValue());
helpers.assertCursorAt(0, 10);
}, { value: ''});
testVim('._insert_o', function(cm, vim, helpers) {
helpers.doKeys('o');
cm.replaceRange('z', cm.getCursor());
cm.setCursor(1, 1);
helpers.doInsertModeKeys('Esc');
helpers.doKeys('.');
eq('\nz\nz', cm.getValue());
helpers.assertCursorAt(2, 0);
}, { value: ''});
testVim('._insert_o_repeat', function(cm, vim, helpers) {
helpers.doKeys('o');
cm.replaceRange('z', cm.getCursor());
helpers.doInsertModeKeys('Esc');
cm.setCursor(1, 0);
helpers.doKeys('2', '.');
eq('\nz\nz\nz', cm.getValue());
helpers.assertCursorAt(3, 0);
}, { value: ''});
testVim('._insert_o_indent', function(cm, vim, helpers) {
helpers.doKeys('o');
cm.replaceRange('z', cm.getCursor());
helpers.doInsertModeKeys('Esc');
cm.setCursor(1, 2);
helpers.doKeys('.');
eq('{\n z\n z', cm.getValue());
helpers.assertCursorAt(2, 2);
}, { value: '{'});
testVim('._insert_cw', function(cm, vim, helpers) {
helpers.doKeys('c', 'w');
cm.replaceRange('test', cm.getCursor());
helpers.doInsertModeKeys('Esc');
cm.setCursor(0, 3);
helpers.doKeys('2', 'l');
helpers.doKeys('.');
eq('test test word3', cm.getValue());
helpers.assertCursorAt(0, 8);
}, { value: 'word1 word2 word3' });
testVim('._insert_cw_repeat', function(cm, vim, helpers) {
// For some reason, repeat cw in desktop VIM will does not repeat insert mode
// changes. Will conform to that behavior.
helpers.doKeys('c', 'w');
cm.replaceRange('test', cm.getCursor());
helpers.doInsertModeKeys('Esc');
cm.setCursor(0, 4);
helpers.doKeys('l');
helpers.doKeys('2', '.');
eq('test test', cm.getValue());
helpers.assertCursorAt(0, 8);
}, { value: 'word1 word2 word3' });
testVim('._delete', function(cm, vim, helpers) {
cm.setCursor(0, 5);
helpers.doKeys('i');
helpers.doInsertModeKeys('Backspace', 'Esc');
helpers.doKeys('.');
eq('zace', cm.getValue());
helpers.assertCursorAt(0, 1);
}, { value: 'zabcde'});
testVim('._delete_repeat', function(cm, vim, helpers) {
cm.setCursor(0, 6);
helpers.doKeys('i');
helpers.doInsertModeKeys('Backspace', 'Esc');
helpers.doKeys('2', '.');
eq('zzce', cm.getValue());
helpers.assertCursorAt(0, 1);
}, { value: 'zzabcde'});
testVim('f;', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('f', 'x');
Expand Down Expand Up @@ -1933,6 +2096,59 @@ testVim('ex_write', function(cm, vim, helpers) {
}
CodeMirror.commands.save = tmp;
});
testVim('ex_sort', function(cm, vim, helpers) {
helpers.doEx('sort');
eq('Z\na\nb\nc\nd', cm.getValue());
}, { value: 'b\nZ\nd\nc\na'});
testVim('ex_sort_reverse', function(cm, vim, helpers) {
helpers.doEx('sort!');
eq('d\nc\nb\na', cm.getValue());
}, { value: 'b\nd\nc\na'});
testVim('ex_sort_range', function(cm, vim, helpers) {
helpers.doEx('2,3sort');
eq('b\nc\nd\na', cm.getValue());
}, { value: 'b\nd\nc\na'});
testVim('ex_sort_oneline', function(cm, vim, helpers) {
helpers.doEx('2sort');
// Expect no change.
eq('b\nd\nc\na', cm.getValue());
}, { value: 'b\nd\nc\na'});
testVim('ex_sort_ignoreCase', function(cm, vim, helpers) {
helpers.doEx('sort i');
eq('a\nb\nc\nd\nZ', cm.getValue());
}, { value: 'b\nZ\nd\nc\na'});
testVim('ex_sort_unique', function(cm, vim, helpers) {
helpers.doEx('sort u');
eq('Z\na\nb\nc\nd', cm.getValue());
}, { value: 'b\nZ\na\na\nd\na\nc\na'});
testVim('ex_sort_decimal', function(cm, vim, helpers) {
helpers.doEx('sort d');
eq('d3\n s5\n6\n.9', cm.getValue());
}, { value: '6\nd3\n s5\n.9'});
testVim('ex_sort_decimal_negative', function(cm, vim, helpers) {
helpers.doEx('sort d');
eq('z-9\nd3\n s5\n6\n.9', cm.getValue());
}, { value: '6\nd3\n s5\n.9\nz-9'});
testVim('ex_sort_decimal_reverse', function(cm, vim, helpers) {
helpers.doEx('sort! d');
eq('.9\n6\n s5\nd3', cm.getValue());
}, { value: '6\nd3\n s5\n.9'});
testVim('ex_sort_hex', function(cm, vim, helpers) {
helpers.doEx('sort x');
eq(' s5\n6\n.9\n&0xB\nd3', cm.getValue());
}, { value: '6\nd3\n s5\n&0xB\n.9'});
testVim('ex_sort_octal', function(cm, vim, helpers) {
helpers.doEx('sort o');
eq('.8\n.9\nd3\n s5\n6', cm.getValue());
}, { value: '6\nd3\n s5\n.9\n.8'});
testVim('ex_sort_decimal_mixed', function(cm, vim, helpers) {
helpers.doEx('sort d');
eq('y\nz\nc1\nb2\na3', cm.getValue());
}, { value: 'a3\nz\nc1\ny\nb2'});
testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) {
helpers.doEx('sort! d');
eq('a3\nb2\nc1\nz\ny', cm.getValue());
}, { value: 'a3\nz\nc1\ny\nb2'});
testVim('ex_substitute_same_line', function(cm, vim, helpers) {
cm.setCursor(1, 0);
helpers.doEx('s/one/two');
Expand Down Expand Up @@ -1978,6 +2194,78 @@ testVim('ex_substitute_count_with_range', function(cm, vim, helpers) {
helpers.doEx('1,3s/\\d/0/ 3');
eq('1\n2\n0\n0', cm.getValue());
}, { value: '1\n2\n3\n4' });
function testSubstituteConfirm(name, command, initialValue, expectedValue, keys, finalPos) {
testVim(name, function(cm, vim, helpers) {
var savedOpenDialog = cm.openDialog;
var savedKeyName = CodeMirror.keyName;
var onKeyDown;
var recordedCallback;
var closed = true; // Start out closed, set false on second openDialog.
function close() {
closed = true;
}
// First openDialog should save callback.
cm.openDialog = function(template, callback, options) {
recordedCallback = callback;
}
// Do first openDialog.
helpers.doKeys(':');
// Second openDialog should save keyDown handler.
cm.openDialog = function(template, callback, options) {
onKeyDown = options.onKeyDown;
closed = false;
};
// Return the command to Vim and trigger second openDialog.
recordedCallback(command);
// The event should really use keyCode, but here just mock it out and use
// key and replace keyName to just return key.
CodeMirror.keyName = function (e) { return e.key; }
keys = keys.toUpperCase();
for (var i = 0; i < keys.length; i++) {
is(!closed);
onKeyDown({ key: keys.charAt(i) }, '', close);
}
try {
eq(expectedValue, cm.getValue());
helpers.assertCursorAt(finalPos);
is(closed);
} catch(e) {
throw e
} finally {
// Restore overriden functions.
CodeMirror.keyName = savedKeyName;
cm.openDialog = savedOpenDialog;
}
}, { value: initialValue });
};
testSubstituteConfirm('ex_substitute_confirm_emptydoc',
'%s/x/b/c', '', '', '', makeCursor(0, 0));
testSubstituteConfirm('ex_substitute_confirm_nomatch',
'%s/x/b/c', 'ba a\nbab', 'ba a\nbab', '', makeCursor(0, 0));
testSubstituteConfirm('ex_substitute_confirm_accept',
'%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'yyy', makeCursor(1, 1));
testSubstituteConfirm('ex_substitute_confirm_random_keys',
'%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'ysdkywerty', makeCursor(1, 1));
testSubstituteConfirm('ex_substitute_confirm_some',
'%s/a/b/c', 'ba a\nbab', 'bb a\nbbb', 'yny', makeCursor(1, 1));
testSubstituteConfirm('ex_substitute_confirm_all',
'%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'a', makeCursor(1, 1));
testSubstituteConfirm('ex_substitute_confirm_accept_then_all',
'%s/a/b/c', 'ba a\nbab', 'bb b\nbbb', 'ya', makeCursor(1, 1));
testSubstituteConfirm('ex_substitute_confirm_quit',
'%s/a/b/c', 'ba a\nbab', 'bb a\nbab', 'yq', makeCursor(0, 3));
testSubstituteConfirm('ex_substitute_confirm_last',
'%s/a/b/c', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
testSubstituteConfirm('ex_substitute_confirm_oneline',
'1s/a/b/c', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3));
testSubstituteConfirm('ex_substitute_confirm_range_accept',
'1,2s/a/b/c', 'aa\na \na\na', 'bb\nb \na\na', 'yyy', makeCursor(1, 0));
testSubstituteConfirm('ex_substitute_confirm_range_some',
'1,3s/a/b/c', 'aa\na \na\na', 'ba\nb \nb\na', 'ynyy', makeCursor(2, 0));
testSubstituteConfirm('ex_substitute_confirm_range_all',
'1,3s/a/b/c', 'aa\na \na\na', 'bb\nb \nb\na', 'a', makeCursor(2, 0));
testSubstituteConfirm('ex_substitute_confirm_range_last',
'1,3s/a/b/c', 'aa\na \na\na', 'bb\nb \na\na', 'yyl', makeCursor(1, 0));
//:noh should clear highlighting of search-results but allow to resume search through n
testVim('ex_noh_clearSearchHighlight', function(cm, vim, helpers) {
cm.openDialog = helpers.fakeOpenDialog('match');
Expand Down