Ross Mountjoy 62191b21b8 ##### Updated to version 0.6!
> Version 0.6 brings DashMachine one big step forward to being a finished product by adding a gui to edit the various settings in the config.ini.

**Changelog**
- improvements to /home including 'pinned' cards, multi-select tag filtering, 'action providers' allowing you to do web searches from the searchbar
- rebuilt sidenav with list view, mirroring filter/search/collapse state of the homepage
- /settings and /home now on same route
- dynamic reloading of settings (no more page reloads)
- dedicated config.ini editor slide-out
- settings editor slide-out
- card editor slide-out
- better access group control
- dedicated documentation pages
- improved documentation
- new system for automatically generating documentation for platforms
- ability to load custom platforms
- added an 'on_starup' method for platforms allowing for registering api routes. (example coming soon)
2020-05-07 09:27:18 -04:00

2675 lines
96 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var Pos = CodeMirror.Pos;
CodeMirror.defaults.rtlMoveVisually = true;
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i], i);
}
function addDoc(cm, width, height) {
var content = [], line = "";
for (var i = 0; i < width; ++i) line += "x";
for (var i = 0; i < height; ++i) content.push(line);
cm.setValue(content.join("\n"));
}
function byClassName(elt, cls) {
if (elt.getElementsByClassName) return elt.getElementsByClassName(cls);
var found = [], re = new RegExp("\\b" + cls + "\\b");
function search(elt) {
if (elt.nodeType == 3) return;
if (re.test(elt.className)) found.push(elt);
for (var i = 0, e = elt.childNodes.length; i < e; ++i)
search(elt.childNodes[i]);
}
search(elt);
return found;
}
var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
var mac = /Mac/.test(navigator.platform);
var opera = /Opera\/\./.test(navigator.userAgent);
var opera_version = opera && navigator.userAgent.match(/Version\/(\d+\.\d+)/);
if (opera_version) opera_version = Number(opera_version);
var opera_lt10 = opera && (!opera_version || opera_version < 10);
namespace = "core_";
test("core_fromTextArea", function() {
var te = document.getElementById("code");
te.value = "CONTENT";
var cm = CodeMirror.fromTextArea(te);
is(!te.offsetHeight);
eq(cm.getValue(), "CONTENT");
cm.setValue("foo\nbar");
eq(cm.getValue(), "foo\nbar");
cm.save();
is(/^foo\r?\nbar$/.test(te.value));
cm.setValue("xxx");
cm.toTextArea();
is(te.offsetHeight);
eq(te.value, "xxx");
});
testCM("getRange", function(cm) {
eq(cm.getLine(0), "1234");
eq(cm.getLine(1), "5678");
eq(cm.getLine(2), null);
eq(cm.getLine(-1), null);
eq(cm.getRange(Pos(0, 0), Pos(0, 3)), "123");
eq(cm.getRange(Pos(0, -1), Pos(0, 200)), "1234");
eq(cm.getRange(Pos(0, 2), Pos(1, 2)), "34\n56");
eq(cm.getRange(Pos(1, 2), Pos(100, 0)), "78");
}, {value: "1234\n5678"});
testCM("replaceRange", function(cm) {
eq(cm.getValue(), "");
cm.replaceRange("foo\n", Pos(0, 0));
eq(cm.getValue(), "foo\n");
cm.replaceRange("a\nb", Pos(0, 1));
eq(cm.getValue(), "fa\nboo\n");
eq(cm.lineCount(), 3);
cm.replaceRange("xyzzy", Pos(0, 0), Pos(1, 1));
eq(cm.getValue(), "xyzzyoo\n");
cm.replaceRange("abc", Pos(0, 0), Pos(10, 0));
eq(cm.getValue(), "abc");
eq(cm.lineCount(), 1);
});
testCM("selection", function(cm) {
cm.setSelection(Pos(0, 4), Pos(2, 2));
is(cm.somethingSelected());
eq(cm.getSelection(), "11\n222222\n33");
eqCursorPos(cm.getCursor(false), Pos(2, 2));
eqCursorPos(cm.getCursor(true), Pos(0, 4));
cm.setSelection(Pos(1, 0));
is(!cm.somethingSelected());
eq(cm.getSelection(), "");
eqCursorPos(cm.getCursor(true), Pos(1, 0));
cm.replaceSelection("abc", "around");
eq(cm.getSelection(), "abc");
eq(cm.getValue(), "111111\nabc222222\n333333");
cm.replaceSelection("def", "end");
eq(cm.getSelection(), "");
eqCursorPos(cm.getCursor(true), Pos(1, 3));
cm.setCursor(Pos(2, 1));
eqCursorPos(cm.getCursor(true), Pos(2, 1));
cm.setCursor(1, 2);
eqCursorPos(cm.getCursor(true), Pos(1, 2));
}, {value: "111111\n222222\n333333"});
testCM("extendSelection", function(cm) {
cm.setExtending(true);
addDoc(cm, 10, 10);
cm.setSelection(Pos(3, 5));
eqCursorPos(cm.getCursor("head"), Pos(3, 5));
eqCursorPos(cm.getCursor("anchor"), Pos(3, 5));
cm.setSelection(Pos(2, 5), Pos(5, 5));
eqCursorPos(cm.getCursor("head"), Pos(5, 5));
eqCursorPos(cm.getCursor("anchor"), Pos(2, 5));
eqCursorPos(cm.getCursor("start"), Pos(2, 5));
eqCursorPos(cm.getCursor("end"), Pos(5, 5));
cm.setSelection(Pos(5, 5), Pos(2, 5));
eqCursorPos(cm.getCursor("head"), Pos(2, 5));
eqCursorPos(cm.getCursor("anchor"), Pos(5, 5));
eqCursorPos(cm.getCursor("start"), Pos(2, 5));
eqCursorPos(cm.getCursor("end"), Pos(5, 5));
cm.extendSelection(Pos(3, 2));
eqCursorPos(cm.getCursor("head"), Pos(3, 2));
eqCursorPos(cm.getCursor("anchor"), Pos(5, 5));
cm.extendSelection(Pos(6, 2));
eqCursorPos(cm.getCursor("head"), Pos(6, 2));
eqCursorPos(cm.getCursor("anchor"), Pos(5, 5));
cm.extendSelection(Pos(6, 3), Pos(6, 4));
eqCursorPos(cm.getCursor("head"), Pos(6, 4));
eqCursorPos(cm.getCursor("anchor"), Pos(5, 5));
cm.extendSelection(Pos(0, 3), Pos(0, 4));
eqCursorPos(cm.getCursor("head"), Pos(0, 3));
eqCursorPos(cm.getCursor("anchor"), Pos(5, 5));
cm.extendSelection(Pos(4, 5), Pos(6, 5));
eqCursorPos(cm.getCursor("head"), Pos(6, 5));
eqCursorPos(cm.getCursor("anchor"), Pos(4, 5));
cm.setExtending(false);
cm.extendSelection(Pos(0, 3), Pos(0, 4));
eqCursorPos(cm.getCursor("head"), Pos(0, 3));
eqCursorPos(cm.getCursor("anchor"), Pos(0, 4));
});
testCM("lines", function(cm) {
eq(cm.getLine(0), "111111");
eq(cm.getLine(1), "222222");
eq(cm.getLine(-1), null);
cm.replaceRange("", Pos(1, 0), Pos(2, 0))
cm.replaceRange("abc", Pos(1, 0), Pos(1));
eq(cm.getValue(), "111111\nabc");
}, {value: "111111\n222222\n333333"});
testCM("indent", function(cm) {
cm.indentLine(1);
eq(cm.getLine(1), " blah();");
cm.setOption("indentUnit", 8);
cm.indentLine(1);
eq(cm.getLine(1), "\tblah();");
cm.setOption("indentUnit", 10);
cm.setOption("tabSize", 4);
cm.indentLine(1);
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];
defs.indentUnit = 5;
defs.value = "uu";
defs.indentWithTabs = true;
defs.tabindex = 55;
var place = document.getElementById("testground"), cm = CodeMirror(place);
try {
eq(cm.getOption("indentUnit"), 5);
cm.setOption("indentUnit", 10);
eq(defs.indentUnit, 5);
eq(cm.getValue(), "uu");
eq(cm.getOption("indentWithTabs"), true);
eq(cm.getInputField().tabIndex, 55);
}
finally {
for (var opt in defsCopy) defs[opt] = defsCopy[opt];
place.removeChild(cm.getWrapperElement());
}
});
testCM("lineInfo", function(cm) {
eq(cm.lineInfo(-1), null);
var mark = document.createElement("span");
var lh = cm.setGutterMarker(1, "FOO", mark);
var info = cm.lineInfo(1);
eq(info.text, "222222");
eq(info.gutterMarkers.FOO, mark);
eq(info.line, 1);
eq(cm.lineInfo(2).gutterMarkers, null);
cm.setGutterMarker(lh, "FOO", null);
eq(cm.lineInfo(1).gutterMarkers, null);
cm.setGutterMarker(1, "FOO", mark);
cm.setGutterMarker(0, "FOO", mark);
cm.clearGutter("FOO");
eq(cm.lineInfo(0).gutterMarkers, null);
eq(cm.lineInfo(1).gutterMarkers, null);
}, {value: "111111\n222222\n333333"});
testCM("coords", function(cm) {
cm.setSize(null, 100);
addDoc(cm, 32, 200);
var top = cm.charCoords(Pos(0, 0));
var bot = cm.charCoords(Pos(200, 30));
is(top.left < bot.left);
is(top.top < bot.top);
is(top.top < top.bottom);
cm.scrollTo(null, 100);
var top2 = cm.charCoords(Pos(0, 0));
is(top.top > top2.top);
eq(top.left, top2.left);
});
testCM("coordsChar", function(cm) {
addDoc(cm, 35, 70);
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);
eqCharPos(pos, Pos(line, ch));
}
}
}
}, {lineNumbers: true});
testCM("coordsCharBidi", function(cm) {
addDoc(cm, 35, 70);
// Put an rtl character into each line to trigger the bidi code path in coordsChar
cm.setValue(cm.getValue().replace(/\bx/g, 'و'))
for (var i = 0; i < 2; ++i) {
var sys = i ? "local" : "page";
for (var ch = 2; 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);
eqCharPos(pos, Pos(line, ch));
}
}
}
}, {lineNumbers: true});
testCM("badBidiOptimization", function(cm) {
if (window.automatedTests) return
var coords = cm.charCoords(Pos(0, 34))
eqCharPos(cm.coordsChar({left: coords.right, top: coords.top + 2}), Pos(0, 34))
}, {value: "----------<p class=\"title\">هل يمكنك اختيار مستوى قسط التأمين الذي ترغب بدفعه؟</p>"})
testCM("posFromIndex", function(cm) {
cm.setValue(
"This function should\n" +
"convert a zero based index\n" +
"to line and ch."
);
var examples = [
{ index: -1, line: 0, ch: 0 }, // <- Tests clipping
{ index: 0, line: 0, ch: 0 },
{ index: 10, line: 0, ch: 10 },
{ index: 39, line: 1, ch: 18 },
{ index: 55, line: 2, ch: 7 },
{ index: 63, line: 2, ch: 15 },
{ index: 64, line: 2, ch: 15 } // <- Tests clipping
];
for (var i = 0; i < examples.length; i++) {
var example = examples[i];
var pos = cm.posFromIndex(example.index);
eq(pos.line, example.line);
eq(pos.ch, example.ch);
if (example.index >= 0 && example.index < 64)
eq(cm.indexFromPos(pos), example.index);
}
});
testCM("undo", function(cm) {
cm.replaceRange("def", Pos(0, 0), Pos(0));
eq(cm.historySize().undo, 1);
cm.undo();
eq(cm.getValue(), "abc");
eq(cm.historySize().undo, 0);
eq(cm.historySize().redo, 1);
cm.redo();
eq(cm.getValue(), "def");
eq(cm.historySize().undo, 1);
eq(cm.historySize().redo, 0);
cm.setValue("1\n\n\n2");
cm.clearHistory();
eq(cm.historySize().undo, 0);
for (var i = 0; i < 20; ++i) {
cm.replaceRange("a", Pos(0, 0));
cm.replaceRange("b", Pos(3, 0));
}
eq(cm.historySize().undo, 40);
for (var i = 0; i < 40; ++i)
cm.undo();
eq(cm.historySize().redo, 40);
eq(cm.getValue(), "1\n\n\n2");
}, {value: "abc"});
testCM("undoDepth", function(cm) {
cm.replaceRange("d", Pos(0));
cm.replaceRange("e", Pos(0));
cm.replaceRange("f", Pos(0));
cm.undo(); cm.undo(); cm.undo();
eq(cm.getValue(), "abcd");
}, {value: "abc", undoDepth: 4});
testCM("undoDoesntClearValue", function(cm) {
cm.undo();
eq(cm.getValue(), "x");
}, {value: "x"});
testCM("undoMultiLine", function(cm) {
cm.operation(function() {
cm.replaceRange("x", Pos(0, 0));
cm.replaceRange("y", Pos(1, 0));
});
cm.undo();
eq(cm.getValue(), "abc\ndef\nghi");
cm.operation(function() {
cm.replaceRange("y", Pos(1, 0));
cm.replaceRange("x", Pos(0, 0));
});
cm.undo();
eq(cm.getValue(), "abc\ndef\nghi");
cm.operation(function() {
cm.replaceRange("y", Pos(2, 0));
cm.replaceRange("x", Pos(1, 0));
cm.replaceRange("z", Pos(2, 0));
});
cm.undo();
eq(cm.getValue(), "abc\ndef\nghi", 3);
}, {value: "abc\ndef\nghi"});
testCM("undoComposite", function(cm) {
cm.replaceRange("y", Pos(1));
cm.operation(function() {
cm.replaceRange("x", Pos(0));
cm.replaceRange("z", Pos(2));
});
eq(cm.getValue(), "ax\nby\ncz\n");
cm.undo();
eq(cm.getValue(), "a\nby\nc\n");
cm.undo();
eq(cm.getValue(), "a\nb\nc\n");
cm.redo(); cm.redo();
eq(cm.getValue(), "ax\nby\ncz\n");
}, {value: "a\nb\nc\n"});
testCM("undoSelection", function(cm) {
cm.setSelection(Pos(0, 2), Pos(0, 4));
cm.replaceSelection("");
cm.setCursor(Pos(1, 0));
cm.undo();
eqCursorPos(cm.getCursor(true), Pos(0, 2));
eqCursorPos(cm.getCursor(false), Pos(0, 4));
cm.setCursor(Pos(1, 0));
cm.redo();
eqCursorPos(cm.getCursor(true), Pos(0, 2));
eqCursorPos(cm.getCursor(false), Pos(0, 2));
}, {value: "abcdefgh\n"});
testCM("undoSelectionAsBefore", function(cm) {
cm.replaceSelection("abc", "around");
cm.undo();
cm.redo();
eq(cm.getSelection(), "abc");
});
testCM("selectionChangeConfusesHistory", function(cm) {
cm.replaceSelection("abc", null, "dontmerge");
cm.operation(function() {
cm.setCursor(Pos(0, 0));
cm.replaceSelection("abc", null, "dontmerge");
});
eq(cm.historySize().undo, 2);
});
testCM("markTextSingleLine", function(cm) {
forEach([{a: 0, b: 1, c: "", f: 2, t: 5},
{a: 0, b: 4, c: "", f: 0, t: 2},
{a: 1, b: 2, c: "x", f: 3, t: 6},
{a: 4, b: 5, c: "", f: 3, t: 5},
{a: 4, b: 5, c: "xx", f: 3, t: 7},
{a: 2, b: 5, c: "", f: 2, t: 3},
{a: 2, b: 5, c: "abcd", f: 6, t: 7},
{a: 2, b: 6, c: "x", f: null, t: null},
{a: 3, b: 6, c: "", f: null, t: null},
{a: 0, b: 9, c: "hallo", f: null, t: null},
{a: 4, b: 6, c: "x", f: 3, t: 4},
{a: 4, b: 8, c: "", f: 3, t: 4},
{a: 6, b: 6, c: "a", f: 3, t: 6},
{a: 8, b: 9, c: "", f: 3, t: 6}], function(test) {
cm.setValue("1234567890");
var r = cm.markText(Pos(0, 3), Pos(0, 6), {className: "foo"});
cm.replaceRange(test.c, Pos(0, test.a), Pos(0, test.b));
var f = r.find();
eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t);
});
});
testCM("markTextMultiLine", function(cm) {
function p(v) { return v && Pos(v[0], v[1]); }
forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]},
{a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]},
{a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]},
{a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]},
{a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]},
{a: [0, 6], b: [2, 4], c: "", f: [0, 5], t: [0, 7]},
{a: [0, 6], b: [2, 4], c: "aa", f: [0, 5], t: [0, 9]},
{a: [1, 2], b: [1, 8], c: "", f: [0, 5], t: [2, 5]},
{a: [0, 5], b: [2, 5], c: "xx", f: null, t: null},
{a: [0, 0], b: [2, 10], c: "x", f: null, t: null},
{a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]},
{a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]},
{a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]},
{a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]},
{a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) {
cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n");
var r = cm.markText(Pos(0, 5), Pos(2, 5),
{className: "CodeMirror-matchingbracket"});
cm.replaceRange(test.c, p(test.a), p(test.b));
var f = r.find();
eqCursorPos(f && f.from, p(test.f)); eqCursorPos(f && f.to, p(test.t));
});
});
testCM("markTextUndo", function(cm) {
var marker1, marker2, bookmark;
marker1 = cm.markText(Pos(0, 1), Pos(0, 3),
{className: "CodeMirror-matchingbracket"});
marker2 = cm.markText(Pos(0, 0), Pos(2, 1),
{className: "CodeMirror-matchingbracket"});
bookmark = cm.setBookmark(Pos(1, 5));
cm.operation(function(){
cm.replaceRange("foo", Pos(0, 2));
cm.replaceRange("bar\nbaz\nbug\n", Pos(2, 0), Pos(3, 0));
});
var v1 = cm.getValue();
cm.setValue("");
eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null);
cm.undo();
eqCursorPos(bookmark.find(), Pos(1, 5), "still there");
cm.undo();
var m1Pos = marker1.find(), m2Pos = marker2.find();
eqCursorPos(m1Pos.from, Pos(0, 1)); eqCursorPos(m1Pos.to, Pos(0, 3));
eqCursorPos(m2Pos.from, Pos(0, 0)); eqCursorPos(m2Pos.to, Pos(2, 1));
eqCursorPos(bookmark.find(), Pos(1, 5));
cm.redo(); cm.redo();
eq(bookmark.find(), null);
cm.undo();
eqCursorPos(bookmark.find(), Pos(1, 5));
eq(cm.getValue(), v1);
}, {value: "1234\n56789\n00\n"});
testCM("markTextStayGone", function(cm) {
var m1 = cm.markText(Pos(0, 0), Pos(0, 1));
cm.replaceRange("hi", Pos(0, 2));
m1.clear();
cm.undo();
eq(m1.find(), null);
}, {value: "hello"});
testCM("markTextAllowEmpty", function(cm) {
var m1 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false});
is(m1.find());
cm.replaceRange("x", Pos(0, 0));
is(m1.find());
cm.replaceRange("y", Pos(0, 2));
is(m1.find());
cm.replaceRange("z", Pos(0, 3), Pos(0, 4));
is(!m1.find());
var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false,
inclusiveLeft: true,
inclusiveRight: true});
cm.replaceRange("q", Pos(0, 1), Pos(0, 2));
is(m2.find());
cm.replaceRange("", Pos(0, 0), Pos(0, 3));
is(!m2.find());
var m3 = cm.markText(Pos(0, 1), Pos(0, 1), {clearWhenEmpty: false});
cm.replaceRange("a", Pos(0, 3));
is(m3.find());
cm.replaceRange("b", Pos(0, 1));
is(!m3.find());
}, {value: "abcde"});
testCM("markTextStacked", function(cm) {
var m1 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
var m2 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
cm.replaceRange("B", Pos(0, 1));
is(m1.find() && m2.find());
}, {value: "A"});
testCM("undoPreservesNewMarks", function(cm) {
cm.markText(Pos(0, 3), Pos(0, 4));
cm.markText(Pos(1, 1), Pos(1, 3));
cm.replaceRange("", Pos(0, 3), Pos(3, 1));
var mBefore = cm.markText(Pos(0, 0), Pos(0, 1));
var mAfter = cm.markText(Pos(0, 5), Pos(0, 6));
var mAround = cm.markText(Pos(0, 2), Pos(0, 4));
cm.undo();
eqCursorPos(mBefore.find().from, Pos(0, 0));
eqCursorPos(mBefore.find().to, Pos(0, 1));
eqCursorPos(mAfter.find().from, Pos(3, 3));
eqCursorPos(mAfter.find().to, Pos(3, 4));
eqCursorPos(mAround.find().from, Pos(0, 2));
eqCursorPos(mAround.find().to, Pos(3, 2));
var found = cm.findMarksAt(Pos(2, 2));
eq(found.length, 1);
eq(found[0], mAround);
}, {value: "aaaa\nbbbb\ncccc\ndddd"});
testCM("markClearBetween", function(cm) {
cm.setValue("aaa\nbbb\nccc\nddd\n");
cm.markText(Pos(0, 0), Pos(2));
cm.replaceRange("aaa\nbbb\nccc", Pos(0, 0), Pos(2));
eq(cm.findMarksAt(Pos(1, 1)).length, 0);
});
testCM("findMarksMiddle", function(cm) {
var mark = cm.markText(Pos(1, 1), Pos(3, 1));
var found = cm.findMarks(Pos(2, 1), Pos(2, 2));
eq(found.length, 1);
eq(found[0], mark);
}, {value: "line 0\nline 1\nline 2\nline 3"});
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("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 && spans[i].textContent == "cdef") return true;
}
var m = cm.markText(Pos(0, 2), Pos(0, 6), {css: "color: cyan"});
is(present());
m.clear();
is(!present());
}, {value: "abcdefgh"});
testCM("markTextWithAttributes", function(cm) {
function present() {
var spans = cm.display.lineDiv.getElementsByTagName("span");
for (var i = 0; i < spans.length; i++)
if (spans[i].getAttribute("label") == "label" && spans[i].textContent == "cdef") return true;
}
var m = cm.markText(Pos(0, 2), Pos(0, 6), {attributes: {label: "label"}});
is(present());
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]},
{a: [1, 1], b: [1, 1], c: "xx", d: [1, 7]},
{a: [1, 4], b: [1, 5], c: "ab", d: [1, 6]},
{a: [1, 4], b: [1, 6], c: "", d: null},
{a: [1, 5], b: [1, 6], c: "abc", d: [1, 5]},
{a: [1, 6], b: [1, 8], c: "", d: [1, 5]},
{a: [1, 4], b: [1, 4], c: "\n\n", d: [3, 1]},
{bm: [1, 9], a: [1, 1], b: [1, 1], c: "\n", d: [2, 8]}], function(test) {
cm.setValue("1234567890\n1234567890\n1234567890");
var b = cm.setBookmark(p(test.bm) || Pos(1, 5));
cm.replaceRange(test.c, p(test.a), p(test.b));
eqCursorPos(b.find(), p(test.d));
});
});
testCM("bookmarkInsertLeft", function(cm) {
var br = cm.setBookmark(Pos(0, 2), {insertLeft: false});
var bl = cm.setBookmark(Pos(0, 2), {insertLeft: true});
cm.setCursor(Pos(0, 2));
cm.replaceSelection("hi");
eqCursorPos(br.find(), Pos(0, 2));
eqCursorPos(bl.find(), Pos(0, 4));
cm.replaceRange("", Pos(0, 4), Pos(0, 5));
cm.replaceRange("", Pos(0, 2), Pos(0, 4));
cm.replaceRange("", Pos(0, 1), Pos(0, 2));
// Verify that deleting next to bookmarks doesn't kill them
eqCursorPos(br.find(), Pos(0, 1));
eqCursorPos(bl.find(), Pos(0, 1));
}, {value: "abcdef"});
testCM("bookmarkCursor", function(cm) {
var pos01 = cm.cursorCoords(Pos(0, 1)), pos11 = cm.cursorCoords(Pos(1, 1)),
pos20 = cm.cursorCoords(Pos(2, 0)), pos30 = cm.cursorCoords(Pos(3, 0)),
pos41 = cm.cursorCoords(Pos(4, 1));
cm.setBookmark(Pos(0, 1), {widget: document.createTextNode("←"), insertLeft: true});
cm.setBookmark(Pos(2, 0), {widget: document.createTextNode("←"), insertLeft: true});
cm.setBookmark(Pos(1, 1), {widget: document.createTextNode("→")});
cm.setBookmark(Pos(3, 0), {widget: document.createTextNode("→")});
var new01 = cm.cursorCoords(Pos(0, 1)), new11 = cm.cursorCoords(Pos(1, 1)),
new20 = cm.cursorCoords(Pos(2, 0)), new30 = cm.cursorCoords(Pos(3, 0));
near(new01.left, pos01.left, 1);
near(new01.top, pos01.top, 1);
is(new11.left > pos11.left, "at right, middle of line");
near(new11.top == pos11.top, 1);
near(new20.left, pos20.left, 1);
near(new20.top, pos20.top, 1);
is(new30.left > pos30.left, "at right, empty line");
near(new30.top, pos30, 1);
cm.setBookmark(Pos(4, 0), {widget: document.createTextNode("→")});
is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug");
}, {value: "foo\nbar\n\n\nx\ny"});
testCM("multiBookmarkCursor", function(cm) {
var ms = [], m;
function add(insertLeft) {
for (var i = 0; i < 3; ++i) {
var node = document.createElement("span");
node.innerHTML = "X";
ms.push(cm.setBookmark(Pos(0, 1), {widget: node, insertLeft: insertLeft}));
}
}
var base1 = cm.cursorCoords(Pos(0, 1)).left, base4 = cm.cursorCoords(Pos(0, 4)).left;
add(true);
near(base1, cm.cursorCoords(Pos(0, 1)).left, 1);
while (m = ms.pop()) m.clear();
add(false);
near(base4, cm.cursorCoords(Pos(0, 1)).left, 1);
}, {value: "abcdefg"});
testCM("getAllMarks", function(cm) {
addDoc(cm, 10, 10);
var m1 = cm.setBookmark(Pos(0, 2));
var m2 = cm.markText(Pos(0, 2), Pos(3, 2));
var m3 = cm.markText(Pos(1, 2), Pos(1, 8));
var m4 = cm.markText(Pos(8, 0), Pos(9, 0));
eq(cm.getAllMarks().length, 4);
m1.clear();
m3.clear();
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();
cm.setValue("fooooo");
cm.undo();
});
testCM("scrollSnap", function(cm) {
cm.setSize(100, 100);
addDoc(cm, 200, 200);
cm.setCursor(Pos(100, 180));
var info = cm.getScrollInfo();
is(info.left > 0 && info.top > 0);
cm.setCursor(Pos(0, 0));
info = cm.getScrollInfo();
is(info.left == 0 && info.top == 0, "scrolled clean to top");
cm.setCursor(Pos(100, 180));
cm.setCursor(Pos(199, 0));
info = cm.getScrollInfo();
is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom");
});
testCM("scrollIntoView", function(cm) {
function test(line, ch, msg) {
var pos = Pos(line, ch);
cm.scrollIntoView(pos);
var outer = cm.getWrapperElement().getBoundingClientRect();
var box = cm.charCoords(pos, "window");
is(box.left >= outer.left, msg + " (left)");
is(box.right <= outer.right, msg + " (right)");
is(box.top >= outer.top, msg + " (top)");
is(box.bottom <= outer.bottom, msg + " (bottom)");
}
addDoc(cm, 200, 200);
test(199, 199, "bottom right");
test(0, 0, "top left");
test(100, 100, "center");
test(199, 0, "bottom left");
test(0, 199, "top right");
test(100, 100, "center again");
});
testCM("scrollBackAndForth", function(cm) {
addDoc(cm, 1, 200);
cm.operation(function() {
cm.scrollIntoView(Pos(199, 0));
cm.scrollIntoView(Pos(4, 0));
});
is(cm.getScrollInfo().top > 0);
});
testCM("selectAllNoScroll", function(cm) {
addDoc(cm, 1, 200);
cm.execCommand("selectAll");
eq(cm.getScrollInfo().top, 0);
cm.setCursor(199);
cm.execCommand("selectAll");
is(cm.getScrollInfo().top > 0);
});
testCM("selectionPos", function(cm) {
if (cm.getOption("inputStyle") != "textarea") return;
cm.setSize(100, 100);
addDoc(cm, 200, 100);
cm.setSelection(Pos(1, 100), Pos(98, 100));
var lineWidth = cm.charCoords(Pos(0, 200), "local").left;
var lineHeight = (cm.charCoords(Pos(99)).top - cm.charCoords(Pos(0)).top) / 100;
cm.scrollTo(0, 0);
var selElt = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
var outer = cm.getWrapperElement().getBoundingClientRect();
var sawMiddle, sawTop, sawBottom;
for (var i = 0, e = selElt.length; i < e; ++i) {
var box = selElt[i].getBoundingClientRect();
var atLeft = box.left - outer.left < 30;
var width = box.right - box.left;
var atRight = box.right - outer.left > .8 * lineWidth;
if (atLeft && atRight) {
sawMiddle = true;
is(box.bottom - box.top > 90 * lineHeight, "middle high");
is(width > .9 * lineWidth, "middle wide");
} else {
is(width > .4 * lineWidth, "top/bot wide enough");
is(width < .6 * lineWidth, "top/bot slim enough");
if (atLeft) {
sawBottom = true;
is(box.top - outer.top > 96 * lineHeight, "bot below");
} else if (atRight) {
sawTop = true;
is(box.top - outer.top < 2.1 * lineHeight, "top above");
}
}
}
is(sawTop && sawBottom && sawMiddle, "all parts");
}, null);
testCM("restoreHistory", function(cm) {
cm.setValue("abc\ndef");
cm.replaceRange("hello", Pos(1, 0), Pos(1));
cm.replaceRange("goop", Pos(0, 0), Pos(0));
cm.undo();
var storedVal = cm.getValue(), storedHist = cm.getHistory();
if (window.JSON) storedHist = JSON.parse(JSON.stringify(storedHist));
eq(storedVal, "abc\nhello");
cm.setValue("");
cm.clearHistory();
eq(cm.historySize().undo, 0);
cm.setValue(storedVal);
cm.setHistory(storedHist);
cm.redo();
eq(cm.getValue(), "goop\nhello");
cm.undo(); cm.undo();
eq(cm.getValue(), "abc\ndef");
});
testCM("doubleScrollbar", function(cm) {
var dummy = document.body.appendChild(document.createElement("p"));
dummy.style.cssText = "height: 50px; overflow: scroll; width: 50px";
var scrollbarWidth = dummy.offsetWidth + 1 - dummy.clientWidth;
document.body.removeChild(dummy);
if (scrollbarWidth < 2) return;
cm.setSize(null, 100);
addDoc(cm, 1, 300);
var wrap = cm.getWrapperElement();
is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth * 1.5);
});
testCM("weirdLinebreaks", function(cm) {
cm.setValue("foo\nbar\rbaz\r\nquux\n\rplop");
is(cm.getValue(), "foo\nbar\nbaz\nquux\n\nplop");
is(cm.lineCount(), 6);
cm.setValue("\n\n");
is(cm.lineCount(), 3);
});
testCM("setSize", function(cm) {
cm.setSize(100, 100);
var wrap = cm.getWrapperElement();
is(wrap.offsetWidth, 100);
is(wrap.offsetHeight, 100);
cm.setSize("100%", "3em");
is(wrap.style.width, "100%");
is(wrap.style.height, "3em");
cm.setSize(null, 40);
is(wrap.style.width, "100%");
is(wrap.style.height, "40px");
});
function foldLines(cm, start, end, autoClear) {
return cm.markText(Pos(start, 0), Pos(end - 1), {
inclusiveLeft: true,
inclusiveRight: true,
collapsed: true,
clearOnEnter: autoClear
});
}
testCM("collapsedLines", function(cm) {
addDoc(cm, 4, 10);
var range = foldLines(cm, 4, 5), cleared = 0;
CodeMirror.on(range, "clear", function() {cleared++;});
cm.setCursor(Pos(3, 0));
CodeMirror.commands.goLineDown(cm);
eqCharPos(cm.getCursor(), Pos(5, 0));
cm.replaceRange("abcdefg", Pos(3, 0), Pos(3));
cm.setCursor(Pos(3, 6));
CodeMirror.commands.goLineDown(cm);
eqCharPos(cm.getCursor(), Pos(5, 4));
cm.replaceRange("ab", Pos(3, 0), Pos(3));
cm.setCursor(Pos(3, 2));
CodeMirror.commands.goLineDown(cm);
eqCharPos(cm.getCursor(), Pos(5, 2));
cm.operation(function() {range.clear(); range.clear();});
eq(cleared, 1);
});
testCM("collapsedRangeCoordsChar", function(cm) {
var pos_1_3 = cm.charCoords(Pos(1, 3));
pos_1_3.left += 2; pos_1_3.top += 2;
var opts = {collapsed: true, inclusiveLeft: true, inclusiveRight: true};
var m1 = cm.markText(Pos(0, 0), Pos(2, 0), opts);
eqCharPos(cm.coordsChar(pos_1_3), Pos(3, 3));
m1.clear();
var m1 = cm.markText(Pos(0, 0), Pos(1, 1), {collapsed: true, inclusiveLeft: true});
var m2 = cm.markText(Pos(1, 1), Pos(2, 0), {collapsed: true, inclusiveRight: true});
eqCharPos(cm.coordsChar(pos_1_3), Pos(3, 3));
m1.clear(); m2.clear();
var m1 = cm.markText(Pos(0, 0), Pos(1, 6), opts);
eqCharPos(cm.coordsChar(pos_1_3), Pos(3, 3));
}, {value: "123456\nabcdef\nghijkl\nmnopqr\n"});
testCM("collapsedRangeBetweenLinesSelected", function(cm) {
if (cm.getOption("inputStyle") != "textarea") return;
var widget = document.createElement("span");
widget.textContent = "\u2194";
cm.markText(Pos(0, 3), Pos(1, 0), {replacedWith: widget});
cm.setSelection(Pos(0, 3), Pos(1, 0));
var selElts = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
for (var i = 0, w = 0; i < selElts.length; i++)
w += selElts[i].offsetWidth;
is(w > 0);
}, {value: "one\ntwo"});
testCM("randomCollapsedRanges", function(cm) {
addDoc(cm, 20, 500);
cm.operation(function() {
for (var i = 0; i < 200; i++) {
var start = Pos(Math.floor(Math.random() * 500), Math.floor(Math.random() * 20));
if (i % 4)
try { cm.markText(start, Pos(start.line + 2, 1), {collapsed: true}); }
catch(e) { if (!/overlapping/.test(String(e))) throw e; }
else
cm.markText(start, Pos(start.line, start.ch + 4), {"className": "foo"});
}
});
});
testCM("hiddenLinesAutoUnfold", function(cm) {
var range = foldLines(cm, 1, 3, true), cleared = 0;
CodeMirror.on(range, "clear", function() {cleared++;});
cm.setCursor(Pos(3, 0));
eq(cleared, 0);
cm.execCommand("goCharLeft");
eq(cleared, 1);
range = foldLines(cm, 1, 3, true);
CodeMirror.on(range, "clear", function() {cleared++;});
eqCursorPos(cm.getCursor(), Pos(3, 0));
cm.setCursor(Pos(0, 3));
cm.execCommand("goCharRight");
eq(cleared, 2);
}, {value: "abc\ndef\nghi\njkl"});
testCM("hiddenLinesSelectAll", function(cm) { // Issue #484
addDoc(cm, 4, 20);
foldLines(cm, 0, 10);
foldLines(cm, 11, 20);
CodeMirror.commands.selectAll(cm);
eqCursorPos(cm.getCursor(true), Pos(10, 0));
eqCursorPos(cm.getCursor(false), Pos(10, 4));
});
testCM("clickFold", function(cm) { // Issue #5392
cm.setValue("foo { bar }")
var widget = document.createElement("span")
widget.textContent = "<>"
cm.markText(Pos(0, 5), Pos(0, 10), {replacedWith: widget})
var after = cm.charCoords(Pos(0, 10))
var foundOn = cm.coordsChar({left: after.left - 1, top: after.top + 4})
is(foundOn.ch <= 5 || foundOn.ch >= 10, "Position is not inside the folded range")
})
testCM("everythingFolded", function(cm) {
addDoc(cm, 2, 2);
function enterPress() {
cm.triggerOnKeyDown({type: "keydown", keyCode: 13, preventDefault: function(){}, stopPropagation: function(){}});
}
var fold = foldLines(cm, 0, 2);
enterPress();
eq(cm.getValue(), "xx\nxx");
fold.clear();
fold = foldLines(cm, 0, 2, true);
eq(fold.find(), null);
enterPress();
eq(cm.getValue(), "\nxx\nxx");
});
testCM("structuredFold", function(cm) {
addDoc(cm, 4, 8);
var range = cm.markText(Pos(1, 2), Pos(6, 2), {
replacedWith: document.createTextNode("Q")
});
cm.setCursor(0, 3);
CodeMirror.commands.goLineDown(cm);
eqCharPos(cm.getCursor(), Pos(6, 2));
CodeMirror.commands.goCharLeft(cm);
eqCharPos(cm.getCursor(), Pos(1, 2));
CodeMirror.commands.delCharAfter(cm);
eq(cm.getValue(), "xxxx\nxxxx\nxxxx");
addDoc(cm, 4, 8);
range = cm.markText(Pos(1, 2), Pos(6, 2), {
replacedWith: document.createTextNode("M"),
clearOnEnter: true
});
var cleared = 0;
CodeMirror.on(range, "clear", function(){++cleared;});
cm.setCursor(0, 3);
CodeMirror.commands.goLineDown(cm);
eqCharPos(cm.getCursor(), Pos(6, 2));
CodeMirror.commands.goCharLeft(cm);
eqCharPos(cm.getCursor(), Pos(6, 1));
eq(cleared, 1);
range.clear();
eq(cleared, 1);
range = cm.markText(Pos(1, 2), Pos(6, 2), {
replacedWith: document.createTextNode("Q"),
clearOnEnter: true
});
range.clear();
cm.setCursor(1, 2);
CodeMirror.commands.goCharRight(cm);
eqCharPos(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);
eqCharPos(cm.getCursor(), Pos(2, 0));
}, null);
testCM("nestedFold", function(cm) {
addDoc(cm, 10, 3);
function fold(ll, cl, lr, cr) {
return cm.markText(Pos(ll, cl), Pos(lr, cr), {collapsed: true});
}
var inner1 = fold(0, 6, 1, 3), inner2 = fold(0, 2, 1, 8), outer = fold(0, 1, 2, 3), inner0 = fold(0, 5, 0, 6);
cm.setCursor(0, 1);
CodeMirror.commands.goCharRight(cm);
eqCursorPos(cm.getCursor(), Pos(2, 3));
inner0.clear();
CodeMirror.commands.goCharLeft(cm);
eqCursorPos(cm.getCursor(), Pos(0, 1));
outer.clear();
CodeMirror.commands.goCharRight(cm);
eqCursorPos(cm.getCursor(), Pos(0, 2, "before"));
CodeMirror.commands.goCharRight(cm);
eqCursorPos(cm.getCursor(), Pos(1, 8));
inner2.clear();
CodeMirror.commands.goCharLeft(cm);
eqCursorPos(cm.getCursor(), Pos(1, 7, "after"));
cm.setCursor(0, 5);
CodeMirror.commands.goCharRight(cm);
eqCursorPos(cm.getCursor(), Pos(0, 6, "before"));
CodeMirror.commands.goCharRight(cm);
eqCursorPos(cm.getCursor(), Pos(1, 3));
});
testCM("badNestedFold", function(cm) {
addDoc(cm, 4, 4);
cm.markText(Pos(0, 2), Pos(3, 2), {collapsed: true});
var caught;
try {cm.markText(Pos(0, 1), Pos(0, 3), {collapsed: true});}
catch(e) {caught = e;}
is(caught instanceof Error, "no error");
is(/overlap/i.test(caught.message), "wrong error");
});
testCM("nestedFoldOnSide", function(cm) {
var m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true, inclusiveRight: true});
var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true});
cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true}).clear();
try { cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true, inclusiveLeft: true}); }
catch(e) { var caught = e; }
is(caught && /overlap/i.test(caught.message));
var m3 = cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true});
var m4 = cm.markText(Pos(2, 0), Pos(2, 1), {collapse: true, inclusiveRight: true});
m1.clear(); m4.clear();
m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true});
cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true}).clear();
try { cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true, inclusiveRight: true}); }
catch(e) { var caught = e; }
is(caught && /overlap/i.test(caught.message));
}, {value: "ab\ncd\ef"});
testCM("editInFold", function(cm) {
addDoc(cm, 4, 6);
var m = cm.markText(Pos(1, 2), Pos(3, 2), {collapsed: true});
cm.replaceRange("", Pos(0, 0), Pos(1, 3));
cm.replaceRange("", Pos(2, 1), Pos(3, 3));
cm.replaceRange("a\nb\nc\nd", Pos(0, 1), Pos(1, 0));
cm.cursorCoords(Pos(0, 0));
});
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("showEmptyWidgetSpan", function(cm) {
var marker = cm.markText(Pos(0, 2), Pos(0, 2), {
clearWhenEmpty: false,
replacedWith: document.createTextNode("X")
});
var text = cm.display.view[0].text;
eq(text.textContent || text.innerText, "abXc");
}, {value: "abc"});
testCM("changedInlineWidget", function(cm) {
cm.setSize("10em");
var w = document.createElement("span");
w.innerHTML = "x";
var m = cm.markText(Pos(0, 4), Pos(0, 5), {replacedWith: w});
w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
m.changed();
var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
is(hScroll.scrollWidth > hScroll.clientWidth);
}, {value: "hello there"});
testCM("changedBookmark", function(cm) {
cm.setSize("10em");
var w = document.createElement("span");
w.innerHTML = "x";
var m = cm.setBookmark(Pos(0, 4), {widget: w});
w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
m.changed();
var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
is(hScroll.scrollWidth > hScroll.clientWidth);
}, {value: "abcdefg"});
testCM("inlineWidget", function(cm) {
var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")});
cm.setCursor(0, 2);
CodeMirror.commands.goLineDown(cm);
eqCharPos(cm.getCursor(), Pos(1, 4));
cm.setCursor(0, 2);
cm.replaceSelection("hi");
eqCharPos(w.find(), Pos(0, 2));
cm.setCursor(0, 1);
cm.replaceSelection("ay");
eqCharPos(w.find(), Pos(0, 4));
eq(cm.getLine(0), "uayuhiuu");
}, {value: "uuuu\nuuuuuu"});
testCM("wrappingAndResizing", function(cm) {
cm.setSize(null, "auto");
cm.setOption("lineWrapping", true);
var wrap = cm.getWrapperElement(), h0 = wrap.offsetHeight;
var doc = "xxx xxx xxx xxx xxx";
cm.setValue(doc);
for (var step = 10, w = cm.charCoords(Pos(0, 18), "div").right;; w += step) {
cm.setSize(w);
if (wrap.offsetHeight <= h0 * (opera_lt10 ? 1.2 : 1.5)) {
if (step == 10) { w -= 10; step = 1; }
else break;
}
}
// Ensure that putting the cursor at the end of the maximally long
// line doesn't cause wrapping to happen.
cm.setCursor(Pos(0, doc.length));
eq(wrap.offsetHeight, h0);
cm.replaceSelection("x");
is(wrap.offsetHeight > h0, "wrapping happens");
// Now add a max-height and, in a document consisting of
// almost-wrapped lines, go over it so that a scrollbar appears.
cm.setValue(doc + "\n" + doc + "\n");
cm.getScrollerElement().style.maxHeight = "100px";
cm.replaceRange("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n!\n", Pos(2, 0));
forEach([Pos(0, doc.length), Pos(0, doc.length - 1),
Pos(0, 0), Pos(1, doc.length), Pos(1, doc.length - 1)],
function(pos) {
var coords = cm.charCoords(pos);
eqCharPos(pos, cm.coordsChar({left: coords.left + 2, top: coords.top + 5}));
});
}, null, ie_lt8);
testCM("measureEndOfLine", function(cm) {
cm.setSize(null, "auto");
var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild;
var lh = inner.offsetHeight;
for (var step = 10, w = cm.charCoords(Pos(0, 7), "div").right;; w += step) {
cm.setSize(w);
if (inner.offsetHeight < 2.5 * lh) {
if (step == 10) { w -= 10; step = 1; }
else break;
}
}
cm.setValue(cm.getValue() + "\n\n");
var endPos = cm.charCoords(Pos(0, 18), "local");
is(endPos.top > lh * .8, "not at top");
is(endPos.left > w - 20, "at right");
endPos = cm.charCoords(Pos(0, 18));
eqCursorPos(cm.coordsChar({left: endPos.left, top: endPos.top + 5}), Pos(0, 18, "before"));
var wrapPos = cm.cursorCoords(Pos(0, 9, "before"));
is(wrapPos.top < endPos.top, "wrapPos is actually in first line");
eqCursorPos(cm.coordsChar({left: wrapPos.left + 10, top: wrapPos.top}), Pos(0, 9, "before"));
}, {mode: "text/html", value: "<!-- foo barrr -->", lineWrapping: true}, ie_lt8 || opera_lt10);
testCM("measureWrappedEndOfLine", function(cm) {
cm.setSize(null, "auto");
var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild;
var lh = inner.offsetHeight;
for (var step = 10, w = cm.charCoords(Pos(0, 7), "div").right;; w += step) {
cm.setSize(w);
if (inner.offsetHeight < 2.5 * lh) {
if (step == 10) { w -= 10; step = 1; }
else break;
}
}
for (var i = 0; i < 3; ++i) {
var endPos = cm.charCoords(Pos(0, 12)); // Next-to-last since last would wrap (#1862)
endPos.left += w; // Add width of editor just to be sure that we are behind last character
eqCursorPos(cm.coordsChar(endPos), Pos(0, 13, "before"));
endPos.left += w * 100;
eqCursorPos(cm.coordsChar(endPos), Pos(0, 13, "before"));
cm.setValue("0123456789abcابجابجابجابج");
if (i == 1) {
var node = document.createElement("div");
node.innerHTML = "hi"; node.style.height = "30px";
cm.addLineWidget(0, node, {above: true});
}
}
}, {mode: "text/html", value: "0123456789abcde0123456789", lineWrapping: true}, ie_lt8 || opera_lt10);
testCM("measureEndOfLineBidi", function(cm) {
eqCursorPos(cm.coordsChar({left: 5000, top: cm.charCoords(Pos(0, 0)).top}), Pos(0, 8, "after"))
}, {value: "إإإإuuuuإإإإ"})
testCM("measureWrappedBidiLevel2", function(cm) {
cm.setSize(cm.charCoords(Pos(0, 6), "editor").right + 60)
var c9 = cm.charCoords(Pos(0, 9))
eqCharPos(cm.coordsChar({left: c9.right - 1, top: c9.top + 1}), Pos(0, 9))
}, {value: "foobar إإ إإ إإ إإ 555 بببببب", lineWrapping: true})
testCM("measureWrappedBeginOfLine", function(cm) {
cm.setSize(null, "auto");
var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild;
var lh = inner.offsetHeight;
for (var step = 10, w = cm.charCoords(Pos(0, 7), "div").right;; w += step) {
cm.setSize(w);
if (inner.offsetHeight < 2.5 * lh) {
if (step == 10) { w -= 10; step = 1; }
else break;
}
}
var beginOfSecondLine = Pos(0, 13, "after");
for (var i = 0; i < 2; ++i) {
var beginPos = cm.charCoords(Pos(0, 0));
beginPos.left -= w;
eqCursorPos(cm.coordsChar(beginPos), Pos(0, 0, "after"));
beginPos = cm.cursorCoords(beginOfSecondLine);
beginPos.left = 0;
eqCursorPos(cm.coordsChar(beginPos), beginOfSecondLine);
cm.setValue("0123456789abcابجابجابجابج");
beginOfSecondLine = Pos(0, 25, "before");
}
}, {mode: "text/html", value: "0123456789abcde0123456789", lineWrapping: true});
testCM("scrollVerticallyAndHorizontally", function(cm) {
if (cm.getOption("inputStyle") != "textarea") return;
cm.setSize(100, 100);
addDoc(cm, 40, 40);
cm.setCursor(39);
var wrap = cm.getWrapperElement(), bar = byClassName(wrap, "CodeMirror-vscrollbar")[0];
is(bar.offsetHeight < wrap.offsetHeight, "vertical scrollbar limited by horizontal one");
var cursorBox = byClassName(wrap, "CodeMirror-cursor")[0].getBoundingClientRect();
var editorBox = wrap.getBoundingClientRect();
is(cursorBox.bottom < editorBox.top + cm.getScrollerElement().clientHeight,
"bottom line visible");
}, {lineNumbers: true});
testCM("moveVstuck", function(cm) {
var lines = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild, h0 = lines.offsetHeight;
var val = "fooooooooooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\n";
cm.setValue(val);
for (var w = cm.charCoords(Pos(0, 26), "div").right * 2.8;; w += 5) {
cm.setSize(w);
if (lines.offsetHeight <= 3.5 * h0) break;
}
cm.setCursor(Pos(0, val.length - 1));
cm.moveV(-1, "line");
eqCursorPos(cm.getCursor(), Pos(0, 27, "before"));
is(cm.cursorCoords(null, "local").top < h0, "cursor is in first visual line");
}, {lineWrapping: true}, ie_lt8 || opera_lt10);
testCM("collapseOnMove", function(cm) {
cm.setSelection(Pos(0, 1), Pos(2, 4));
cm.execCommand("goLineUp");
is(!cm.somethingSelected());
eqCharPos(cm.getCursor(), Pos(0, 1));
cm.setSelection(Pos(0, 1), Pos(2, 4));
cm.execCommand("goPageDown");
is(!cm.somethingSelected());
eqCharPos(cm.getCursor(), Pos(2, 4));
cm.execCommand("goLineUp");
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(0, 4));
cm.setSelection(Pos(0, 1), Pos(2, 4));
cm.execCommand("goCharLeft");
is(!cm.somethingSelected());
eqCharPos(cm.getCursor(), Pos(0, 1));
}, {value: "aaaaa\nb\nccccc"});
testCM("clickTab", function(cm) {
var p0 = cm.charCoords(Pos(0, 0));
eqCharPos(cm.coordsChar({left: p0.left + 5, top: p0.top + 5}), Pos(0, 0));
eqCharPos(cm.coordsChar({left: p0.right - 5, top: p0.top + 5}), Pos(0, 1));
}, {value: "\t\n\n", lineWrapping: true, tabSize: 8});
testCM("verticalScroll", function(cm) {
cm.setSize(100, 200);
cm.setValue("foo\nbar\nbaz\n");
var sc = cm.getScrollerElement(), baseWidth = sc.scrollWidth;
cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
is(sc.scrollWidth > baseWidth, "scrollbar present");
cm.replaceRange("foo", Pos(0, 0), Pos(0));
eq(sc.scrollWidth, baseWidth, "scrollbar gone");
cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
cm.replaceRange("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh", Pos(1, 0), Pos(1));
is(sc.scrollWidth > baseWidth, "present again");
var curWidth = sc.scrollWidth;
cm.replaceRange("foo", Pos(0, 0), Pos(0));
is(sc.scrollWidth < curWidth, "scrollbar smaller");
is(sc.scrollWidth > baseWidth, "but still present");
});
testCM("extraKeys", function(cm) {
var outcome;
function fakeKey(expected, code, props) {
if (typeof code == "string") code = code.charCodeAt(0);
var e = {type: "keydown", keyCode: code, preventDefault: function(){}, stopPropagation: function(){}};
if (props) for (var n in props) e[n] = props[n];
outcome = null;
cm.triggerOnKeyDown(e);
eq(outcome, expected);
}
CodeMirror.commands.testCommand = function() {outcome = "tc";};
CodeMirror.commands.goTestCommand = function() {outcome = "gtc";};
cm.setOption("extraKeys", {"Shift-X": function() {outcome = "sx";},
"X": function() {outcome = "x";},
"Ctrl-Alt-U": function() {outcome = "cau";},
"End": "testCommand",
"Home": "goTestCommand",
"Tab": false});
fakeKey(null, "U");
fakeKey("cau", "U", {ctrlKey: true, altKey: true});
fakeKey(null, "U", {shiftKey: true, ctrlKey: true, altKey: true});
fakeKey("x", "X");
fakeKey("sx", "X", {shiftKey: true});
fakeKey("tc", 35);
fakeKey(null, 35, {shiftKey: true});
fakeKey("gtc", 36);
fakeKey("gtc", 36, {shiftKey: true});
fakeKey(null, 9);
}, null, window.opera && mac);
testCM("wordMovementCommands", function(cm) {
cm.execCommand("goWordLeft");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqCursorPos(cm.getCursor(), Pos(0, 7, "before"));
cm.execCommand("goWordLeft");
eqCursorPos(cm.getCursor(), Pos(0, 5, "after"));
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqCursorPos(cm.getCursor(), Pos(0, 12, "before"));
cm.execCommand("goWordLeft");
eqCursorPos(cm.getCursor(), Pos(0, 9, "after"));
cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqCursorPos(cm.getCursor(), Pos(0, 24, "before"));
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqCursorPos(cm.getCursor(), Pos(1, 9, "before"));
cm.execCommand("goWordRight");
eqCursorPos(cm.getCursor(), Pos(1, 13, "before"));
cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
eqCharPos(cm.getCursor(), Pos(2, 0));
}, {value: "this is (the) firstline.\na foo12\u00e9\u00f8\u00d7bar\n"});
testCM("groupMovementCommands", function(cm) {
cm.execCommand("goGroupLeft");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(0, 4, "before"));
cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(0, 7, "before"));
cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(0, 10, "before"));
cm.execCommand("goGroupLeft");
eqCursorPos(cm.getCursor(), Pos(0, 7, "after"));
cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(0, 15, "before"));
cm.setCursor(Pos(0, 17));
cm.execCommand("goGroupLeft");
eqCursorPos(cm.getCursor(), Pos(0, 16, "after"));
cm.execCommand("goGroupLeft");
eqCursorPos(cm.getCursor(), Pos(0, 14, "after"));
cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(0, 20, "before"));
cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(1, 0, "after"));
cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(1, 2, "before"));
cm.execCommand("goGroupRight");
eqCursorPos(cm.getCursor(), Pos(1, 5, "before"));
cm.execCommand("goGroupLeft"); cm.execCommand("goGroupLeft");
eqCursorPos(cm.getCursor(), Pos(1, 0, "after"));
cm.execCommand("goGroupLeft");
eqCursorPos(cm.getCursor(), Pos(0, 20, "after"));
cm.execCommand("goGroupLeft");
eqCursorPos(cm.getCursor(), Pos(0, 16, "after"));
}, {value: "booo ba---quux. ffff\n abc d"});
testCM("groupsAndWhitespace", function(cm) {
var positions = [Pos(0, 0), Pos(0, 2), Pos(0, 5), Pos(0, 9), Pos(0, 11),
Pos(1, 0), Pos(1, 2), Pos(1, 5)];
for (var i = 1; i < positions.length; i++) {
cm.execCommand("goGroupRight");
eqCharPos(cm.getCursor(), positions[i]);
}
for (var i = positions.length - 2; i >= 0; i--) {
cm.execCommand("goGroupLeft");
eqCharPos(cm.getCursor(), i == 2 ? Pos(0, 6, "before") : positions[i]);
}
}, {value: " foo +++ \n bar"});
testCM("charMovementCommands", function(cm) {
cm.execCommand("goCharLeft"); cm.execCommand("goColumnLeft");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goCharRight"); cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 2, "before"));
cm.setCursor(Pos(1, 0));
cm.execCommand("goColumnLeft");
eqCursorPos(cm.getCursor(), Pos(1, 0));
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 5, "before"));
cm.execCommand("goColumnRight");
eqCursorPos(cm.getCursor(), Pos(0, 5, "before"));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(1, 0, "after"));
cm.execCommand("goLineEnd");
eqCursorPos(cm.getCursor(), Pos(1, 5, "before"));
cm.execCommand("goLineStartSmart");
eqCursorPos(cm.getCursor(), Pos(1, 1, "after"));
cm.execCommand("goLineStartSmart");
eqCursorPos(cm.getCursor(), Pos(1, 0, "after"));
cm.setCursor(Pos(2, 0));
cm.execCommand("goCharRight"); cm.execCommand("goColumnRight");
eqCursorPos(cm.getCursor(), Pos(2, 0));
}, {value: "line1\n ine2\n"});
testCM("verticalMovementCommands", function(cm) {
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(1, 0));
cm.setCursor(Pos(1, 12));
cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(2, 5));
cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(3, 0));
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(2, 5));
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(1, 12));
cm.execCommand("goPageDown");
eqCharPos(cm.getCursor(), Pos(5, 0));
cm.execCommand("goPageDown"); cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(5, 0));
cm.execCommand("goPageUp");
eqCharPos(cm.getCursor(), Pos(0, 0));
}, {value: "line1\nlong long line2\nline3\n\nline5\n"});
testCM("verticalMovementCommandsWrapping", function(cm) {
cm.setSize(120);
cm.setCursor(Pos(0, 5));
cm.execCommand("goLineDown");
eq(cm.getCursor().line, 0);
is(cm.getCursor().ch > 5, "moved beyond wrap");
for (var i = 0; ; ++i) {
is(i < 20, "no endless loop");
cm.execCommand("goLineDown");
var cur = cm.getCursor();
if (cur.line == 1) eq(cur.ch, 5);
if (cur.line == 2) { eq(cur.ch, 1); break; }
}
}, {value: "a very long line that wraps around somehow so that we can test cursor movement\nshortone\nk",
lineWrapping: true});
testCM("verticalMovementCommandsSingleLine", function(cm) {
cm.display.wrapper.style.height = "auto";
cm.refresh();
cm.execCommand("goLineUp");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goLineDown");
eqCursorPos(cm.getCursor(), Pos(0, 11));
cm.setCursor(Pos(0, 5));
cm.execCommand("goLineDown");
eqCursorPos(cm.getCursor(), Pos(0, 11));
cm.execCommand("goLineDown");
eqCursorPos(cm.getCursor(), Pos(0, 11));
cm.execCommand("goLineUp");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goLineUp");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goPageDown");
eqCursorPos(cm.getCursor(), Pos(0, 11));
cm.execCommand("goPageDown"); cm.execCommand("goLineDown");
eqCursorPos(cm.getCursor(), Pos(0, 11));
cm.execCommand("goPageUp");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.setCursor(Pos(0, 5));
cm.execCommand("goPageUp");
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.setCursor(Pos(0, 5));
cm.execCommand("goPageDown");
eqCursorPos(cm.getCursor(), Pos(0, 11));
}, {value: "single line"});
testCM("rtlMovement", function(cm) {
if (cm.getOption("inputStyle") != "textarea") return;
forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج",
"خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", "خ ة ق",
"<img src=\"/בדיקה3.jpg\">", "يتم السحب في 05 فبراير 2014"], function(line) {
cm.setValue(line + "\n"); cm.execCommand("goLineStart");
var cursors = byClassName(cm.getWrapperElement(), "CodeMirror-cursors")[0];
var cursor = cursors.firstChild;
var prevX = cursor.offsetLeft, prevY = cursor.offsetTop;
for (var i = 0; i <= line.length; ++i) {
cm.execCommand("goCharRight");
cursor = cursors.firstChild;
if (i == line.length) is(cursor.offsetTop > prevY, "next line");
else is(cursor.offsetLeft > prevX, "moved right");
prevX = cursor.offsetLeft; prevY = cursor.offsetTop;
}
cm.setCursor(0, 0); cm.execCommand("goLineEnd");
prevX = cursors.firstChild.offsetLeft;
for (var i = 0; i < line.length; ++i) {
cm.execCommand("goCharLeft");
cursor = cursors.firstChild;
is(cursor.offsetLeft < prevX, "moved left");
prevX = cursor.offsetLeft;
}
});
}, null, ie_lt9);
// Verify that updating a line clears its bidi ordering
testCM("bidiUpdate", function(cm) {
cm.setCursor(Pos(0, 2, "before"));
cm.replaceSelection("خحج", "start");
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 6, "before"));
}, {value: "abcd\n"});
testCM("movebyTextUnit", function(cm) {
cm.setValue("בְּרֵאשִ\nééé́\n");
cm.execCommand("goLineStart");
for (var i = 0; i < 4; ++i) cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 0, "after"));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(1, 0, "after"));
cm.execCommand("goCharRight");
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(1, 4, "before"));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(1, 7, "before"));
});
testCM("lineChangeEvents", function(cm) {
addDoc(cm, 3, 5);
var log = [], want = ["ch 0", "ch 1", "del 2", "ch 0", "ch 0", "del 1", "del 3", "del 4"];
for (var i = 0; i < 5; ++i) {
CodeMirror.on(cm.getLineHandle(i), "delete", function(i) {
return function() {log.push("del " + i);};
}(i));
CodeMirror.on(cm.getLineHandle(i), "change", function(i) {
return function() {log.push("ch " + i);};
}(i));
}
cm.replaceRange("x", Pos(0, 1));
cm.replaceRange("xy", Pos(1, 1), Pos(2));
cm.replaceRange("foo\nbar", Pos(0, 1));
cm.replaceRange("", Pos(0, 0), Pos(cm.lineCount()));
eq(log.length, want.length, "same length");
for (var i = 0; i < log.length; ++i)
eq(log[i], want[i]);
});
testCM("scrollEntirelyToRight", function(cm) {
if (cm.getOption("inputStyle") != "textarea") return;
addDoc(cm, 500, 2);
cm.setCursor(Pos(0, 500));
var wrap = cm.getWrapperElement(), cur = byClassName(wrap, "CodeMirror-cursor")[0];
is(wrap.getBoundingClientRect().right > cur.getBoundingClientRect().left);
});
testCM("lineWidgets", function(cm) {
addDoc(cm, 500, 3);
var last = cm.charCoords(Pos(2, 0));
var node = document.createElement("div");
node.innerHTML = "hi";
var widget = cm.addLineWidget(1, node);
is(last.top < cm.charCoords(Pos(2, 0)).top, "took up space");
cm.setCursor(Pos(1, 1));
cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(2, 1));
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(1, 1));
});
testCM("lineWidgetFocus", function(cm) {
var place = document.getElementById("testground");
place.className = "offscreen";
try {
addDoc(cm, 500, 10);
var node = document.createElement("input");
var widget = cm.addLineWidget(1, node);
node.focus();
eq(document.activeElement, node);
cm.replaceRange("new stuff", Pos(1, 0));
eq(document.activeElement, node);
} finally {
place.className = "";
}
});
testCM("lineWidgetCautiousRedraw", function(cm) {
var node = document.createElement("div");
node.innerHTML = "hahah";
var w = cm.addLineWidget(0, node);
var redrawn = false;
w.on("redraw", function() { redrawn = true; });
cm.replaceSelection("0");
is(!redrawn);
}, {value: "123\n456"});
var knownScrollbarWidth;
function scrollbarWidth(measure) {
if (knownScrollbarWidth != null) return knownScrollbarWidth;
var div = document.createElement('div');
div.style.cssText = "width: 50px; height: 50px; overflow-x: scroll";
document.body.appendChild(div);
knownScrollbarWidth = div.offsetHeight - div.clientHeight;
document.body.removeChild(div);
return knownScrollbarWidth || 0;
}
testCM("lineWidgetChanged", function(cm) {
addDoc(cm, 2, 300);
var halfScrollbarWidth = scrollbarWidth(cm.display.measure)/2;
cm.setOption('lineNumbers', true);
cm.setSize(600, cm.defaultTextHeight() * 50);
cm.scrollTo(null, cm.heightAtLine(125, "local"));
var expectedWidgetHeight = 60;
var expectedLinesInWidget = 3;
function w() {
var node = document.createElement("div");
// we use these children with just under half width of the line to check measurements are made with correct width
// when placed in the measure div.
// If the widget is measured at a width much narrower than it is displayed at, the underHalf children will span two lines and break the test.
// If the widget is measured at a width much wider than it is displayed at, the overHalf children will combine and break the test.
// Note that this test only checks widgets where coverGutter is true, because these require extra styling to get the width right.
// It may also be worthwhile to check this for non-coverGutter widgets.
// Visually:
// Good:
// | ------------- display width ------------- |
// | ------- widget-width when measured ------ |
// | | -- under-half -- | | -- under-half -- | |
// | | --- over-half --- | |
// | | --- over-half --- | |
// Height: measured as 3 lines, same as it will be when actually displayed
// Bad (too narrow):
// | ------------- display width ------------- |
// | ------ widget-width when measured ----- | < -- uh oh
// | | -- under-half -- | |
// | | -- under-half -- | | < -- when measured, shoved to next line
// | | --- over-half --- | |
// | | --- over-half --- | |
// Height: measured as 4 lines, more than expected . Will be displayed as 3 lines!
// Bad (too wide):
// | ------------- display width ------------- |
// | -------- widget-width when measured ------- | < -- uh oh
// | | -- under-half -- | | -- under-half -- | |
// | | --- over-half --- | | --- over-half --- | | < -- when measured, combined on one line
// Height: measured as 2 lines, less than expected. Will be displayed as 3 lines!
var barelyUnderHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(285 - halfScrollbarWidth)+'px;"></div>';
var barelyOverHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(305 - halfScrollbarWidth)+'px;"></div>';
node.innerHTML = new Array(3).join(barelyUnderHalfWidthHtml) + new Array(3).join(barelyOverHalfWidthHtml);
node.style.cssText = "background: yellow;font-size:0;line-height: " + (expectedWidgetHeight/expectedLinesInWidget) + "px;";
return node;
}
var info0 = cm.getScrollInfo();
var w0 = cm.addLineWidget(0, w(), { coverGutter: true });
var w150 = cm.addLineWidget(150, w(), { coverGutter: true });
var w300 = cm.addLineWidget(300, w(), { coverGutter: true });
var info1 = cm.getScrollInfo();
eq(info0.height + (3 * expectedWidgetHeight), info1.height);
eq(info0.top + expectedWidgetHeight, info1.top);
expectedWidgetHeight = 12;
w0.node.style.lineHeight = w150.node.style.lineHeight = w300.node.style.lineHeight = (expectedWidgetHeight/expectedLinesInWidget) + "px";
w0.changed(); w150.changed(); w300.changed();
var info2 = cm.getScrollInfo();
eq(info0.height + (3 * expectedWidgetHeight), info2.height);
eq(info0.top + expectedWidgetHeight, info2.top);
});
testCM("lineWidgetIssue5486", function(cm) {
// [prepare]
// 2nd line is combined to 1st line due to markText
// 2nd line has a lineWidget below
cm.setValue("Lorem\nIpsue\nDollar")
var el = document.createElement('div')
el.style.height='50px'
el.textContent = '[[LINE WIDGET]]'
var lineWidget = cm.addLineWidget(1, el, {
above: false,
coverGutter: false,
noHScroll: false,
showIfHidden: false,
})
var marker = document.createElement('span')
marker.textContent = '[--]'
cm.markText({line:0, ch: 1}, {line:1, ch: 4}, {
replacedWith: marker
})
// before resizing the lineWidget, measure 3rd line position
var measure_1 = Math.round(cm.charCoords({line:2, ch:0}).top)
// resize lineWidget, height + 50 px
el.style.height='100px'
el.textContent += "\nlineWidget size changed.\nTry moving cursor to line 3?"
lineWidget.changed()
// re-measure 3rd line position
var measure_2 = Math.round(cm.charCoords({line:2, ch:0}).top)
eq(measure_2, measure_1 + 50)
// (extra test)
//
// add char to the right of the folded marker
// and re-measure 3rd line position
cm.replaceRange('-', {line:1, ch: 5})
var measure_3 = Math.round(cm.charCoords({line:2, ch:0}).top)
eq(measure_3, measure_2)
});
testCM("getLineNumber", function(cm) {
addDoc(cm, 2, 20);
var h1 = cm.getLineHandle(1);
eq(cm.getLineNumber(h1), 1);
cm.replaceRange("hi\nbye\n", Pos(0, 0));
eq(cm.getLineNumber(h1), 3);
cm.setValue("");
eq(cm.getLineNumber(h1), null);
});
testCM("jumpTheGap", function(cm) {
var longLine = "abcdef ghiklmnop qrstuvw xyz ";
longLine += longLine; longLine += longLine; longLine += longLine;
cm.replaceRange(longLine, Pos(2, 0), Pos(2));
cm.setSize("200px", null);
cm.getWrapperElement().style.lineHeight = 2;
cm.refresh();
cm.setCursor(Pos(0, 1));
cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(1, 1));
cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(2, 1));
cm.execCommand("goLineDown");
eq(cm.getCursor().line, 2);
is(cm.getCursor().ch > 1);
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(2, 1));
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(1, 1));
var node = document.createElement("div");
node.innerHTML = "hi"; node.style.height = "30px";
cm.addLineWidget(0, node);
cm.addLineWidget(1, node.cloneNode(true), {above: true});
cm.setCursor(Pos(0, 2));
cm.execCommand("goLineDown");
eqCharPos(cm.getCursor(), Pos(1, 2));
cm.execCommand("goLineUp");
eqCharPos(cm.getCursor(), Pos(0, 2));
}, {lineWrapping: true, value: "abc\ndef\nghi\njkl\n"});
testCM("addLineClass", function(cm) {
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");
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, 2); // Gutter classes are reflected in 2 nodes
cm.removeLineClass(0, "text", "foo");
cls(0, "bar", null, null, null);
cm.removeLineClass(0, "text", "foo");
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", "gutter-class");
cm.removeLineClass(1, "wrap");
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);
function atom(ll, cl, lr, cr, li, ri, ls, rs) {
var options = {
atomic: true,
inclusiveLeft: li,
inclusiveRight: ri
};
if (ls === true || ls === false) options["selectLeft"] = ls;
if (rs === true || rs === false) options["selectRight"] = rs;
return cm.markText(Pos(ll, cl), Pos(lr, cr), options);
}
// Can cursor to the left and right of a normal marker by jumping across it
var m = atom(0, 1, 0, 5);
cm.setCursor(Pos(0, 1));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 5));
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 1));
m.clear();
// Can't cursor to the left of a marker when inclusiveLeft=true
m = atom(0, 0, 0, 5, true);
eqCursorPos(cm.getCursor(), Pos(0, 5), "pushed out");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 5));
m.clear();
// Can't cursor to the left of a marker when inclusiveLeft=false and selectLeft=false
m = atom(0, 0, 0, 5, false, false, false);
cm.setCursor(Pos(0, 5));
eqCursorPos(cm.getCursor(), Pos(0, 5), "pushed out");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 5));
m.clear();
// Can cursor to the left of a marker when inclusiveLeft=false and selectLeft=True
m = atom(0, 0, 0, 5, false, false, true);
cm.setCursor(Pos(0, 5));
eqCursorPos(cm.getCursor(), Pos(0, 5), "pushed out");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 0));
m.clear();
// Can't cursor to the right of a marker when inclusiveRight=true
m = atom(0, 0, 0, 5, false, true);
cm.setCursor(Pos(0, 0));
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 6));
m.clear();
// Can't cursor to the right of a marker when inclusiveRight=false and selectRight=false
m = atom(0, 0, 0, 5, false, false, true, false);
cm.setCursor(Pos(0, 0));
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 6));
m.clear();
// Can cursor to the right of a marker when inclusiveRight=false and selectRight=True
m = atom(0, 0, 0, 5, false, false, true, true);
cm.setCursor(Pos(0, 0));
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 5));
m.clear();
// Can't cursor to the right of a multiline marker when inclusiveRight=true
m = atom(8, 4, 9, 10, false, true);
cm.setCursor(Pos(9, 8));
eqCursorPos(cm.getCursor(), Pos(8, 4), "set");
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(8, 4), "char right");
cm.execCommand("goLineDown");
eqCursorPos(cm.getCursor(), Pos(8, 4), "line down");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(8, 3, "after"));
m.clear();
// Cursor jumps across a multiline atomic marker,
// and backspace deletes the entire marker
m = atom(1, 1, 3, 8);
cm.setCursor(Pos(0, 0));
cm.setCursor(Pos(2, 0));
eqCursorPos(cm.getCursor(), Pos(3, 8));
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(1, 1));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(3, 8));
cm.execCommand("goLineUp");
eqCursorPos(cm.getCursor(), Pos(1, 1));
cm.execCommand("goLineDown");
eqCursorPos(cm.getCursor(), Pos(3, 8));
cm.execCommand("delCharBefore");
eq(cm.getValue().length, 80, "del chunk");
m.clear();
addDoc(cm, 10, 10);
// Delete before an atomic marker deletes the entire marker
m = atom(3, 0, 5, 5);
cm.setCursor(Pos(3, 0));
cm.execCommand("delWordAfter");
eq(cm.getValue().length, 82, "del chunk");
m.clear();
addDoc(cm, 10, 10);
});
testCM("selectionBias", function(cm) {
cm.markText(Pos(0, 1), Pos(0, 3), {atomic: true});
cm.setCursor(Pos(0, 2));
eqCursorPos(cm.getCursor(), Pos(0, 1));
cm.setCursor(Pos(0, 2));
eqCursorPos(cm.getCursor(), Pos(0, 3));
cm.setCursor(Pos(0, 2));
eqCursorPos(cm.getCursor(), Pos(0, 1));
cm.setCursor(Pos(0, 2), null, {bias: -1});
eqCursorPos(cm.getCursor(), Pos(0, 1));
cm.setCursor(Pos(0, 4));
cm.setCursor(Pos(0, 2), null, {bias: 1});
eqCursorPos(cm.getCursor(), Pos(0, 3));
}, {value: "12345"});
testCM("selectionHomeEnd", function(cm) {
cm.markText(Pos(1, 0), Pos(1, 1), {atomic: true, inclusiveLeft: true});
cm.markText(Pos(1, 3), Pos(1, 4), {atomic: true, inclusiveRight: true});
cm.setCursor(Pos(1, 2));
cm.execCommand("goLineStart");
eqCursorPos(cm.getCursor(), Pos(1, 1));
cm.execCommand("goLineEnd");
eqCursorPos(cm.getCursor(), Pos(1, 3));
}, {value: "ab\ncdef\ngh"});
testCM("readOnlyMarker", function(cm) {
function mark(ll, cl, lr, cr, at) {
return cm.markText(Pos(ll, cl), Pos(lr, cr),
{readOnly: true, atomic: at});
}
var m = mark(0, 1, 0, 4);
cm.setCursor(Pos(0, 2));
cm.replaceSelection("hi", "end");
eqCursorPos(cm.getCursor(), Pos(0, 2));
eq(cm.getLine(0), "abcde");
cm.execCommand("selectAll");
cm.replaceSelection("oops", "around");
eq(cm.getValue(), "oopsbcd");
cm.undo();
eqCursorPos(m.find().from, Pos(0, 1));
eqCursorPos(m.find().to, Pos(0, 4));
m.clear();
cm.setCursor(Pos(0, 2));
cm.replaceSelection("hi", "around");
eq(cm.getLine(0), "abhicde");
eqCursorPos(cm.getCursor(), Pos(0, 4));
m = mark(0, 2, 2, 2, true);
cm.setSelection(Pos(1, 1), Pos(2, 4));
cm.replaceSelection("t", "end");
eqCursorPos(cm.getCursor(), Pos(2, 3));
eq(cm.getLine(2), "klto");
cm.execCommand("goCharLeft");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 2));
cm.setSelection(Pos(0, 1), Pos(0, 3));
cm.replaceSelection("xx", "around");
eqCursorPos(cm.getCursor(), Pos(0, 3));
eq(cm.getLine(0), "axxhicde");
}, {value: "abcde\nfghij\nklmno\n"});
testCM("dirtyBit", function(cm) {
eq(cm.isClean(), true);
cm.replaceSelection("boo", null, "test");
eq(cm.isClean(), false);
cm.undo();
eq(cm.isClean(), true);
cm.replaceSelection("boo", null, "test");
cm.replaceSelection("baz", null, "test");
cm.undo();
eq(cm.isClean(), false);
cm.markClean();
eq(cm.isClean(), true);
cm.undo();
eq(cm.isClean(), false);
cm.redo();
eq(cm.isClean(), true);
});
testCM("changeGeneration", function(cm) {
cm.replaceSelection("x");
var softGen = cm.changeGeneration();
cm.replaceSelection("x");
cm.undo();
eq(cm.getValue(), "");
is(!cm.isClean(softGen));
cm.replaceSelection("x");
var hardGen = cm.changeGeneration(true);
cm.replaceSelection("x");
cm.undo();
eq(cm.getValue(), "x");
is(cm.isClean(hardGen));
});
testCM("addKeyMap", function(cm) {
function sendKey(code) {
cm.triggerOnKeyDown({type: "keydown", keyCode: code,
preventDefault: function(){}, stopPropagation: function(){}});
}
sendKey(39);
eqCursorPos(cm.getCursor(), Pos(0, 1, "before"));
var test = 0;
var map1 = {Right: function() { ++test; }}, map2 = {Right: function() { test += 10; }}
cm.addKeyMap(map1);
sendKey(39);
eqCursorPos(cm.getCursor(), Pos(0, 1, "before"));
eq(test, 1);
cm.addKeyMap(map2, true);
sendKey(39);
eq(test, 2);
cm.removeKeyMap(map1);
sendKey(39);
eq(test, 12);
cm.removeKeyMap(map2);
sendKey(39);
eq(test, 12);
eqCursorPos(cm.getCursor(), Pos(0, 2, "before"));
cm.addKeyMap({Right: function() { test = 55; }, name: "mymap"});
sendKey(39);
eq(test, 55);
cm.removeKeyMap("mymap");
sendKey(39);
eqCursorPos(cm.getCursor(), Pos(0, 3, "before"));
}, {value: "abc"});
function mouseDown(cm, button, pos, mods) {
var coords = cm.charCoords(pos, "window")
var event = {type: "mousedown",
preventDefault: Math.min,
which: button,
target: cm.display.lineDiv,
clientX: coords.left, clientY: coords.top}
if (mods) for (var prop in mods) event[prop] = mods[prop]
cm.triggerOnMouseDown(event)
}
testCM("mouseBinding", function(cm) {
var fired = []
cm.addKeyMap({
"Shift-LeftClick": function(_cm, pos) {
eqCharPos(pos, Pos(1, 2))
fired.push("a")
},
"Shift-LeftDoubleClick": function() { fired.push("b") },
"Shift-LeftTripleClick": function() { fired.push("c") }
})
function send(button, mods) { mouseDown(cm, button, Pos(1, 2), mods) }
send(1, {shiftKey: true})
send(1, {shiftKey: true})
send(1, {shiftKey: true})
send(1, {})
send(2, {ctrlKey: true})
send(2, {ctrlKey: true})
eq(fired.join(" "), "a b c")
}, {value: "foo\nbar\nbaz"})
testCM("configureMouse", function(cm) {
cm.setOption("configureMouse", function() { return {unit: "word"} })
mouseDown(cm, 1, Pos(0, 5))
eqCharPos(cm.getCursor("from"), Pos(0, 4))
eqCharPos(cm.getCursor("to"), Pos(0, 7))
cm.setOption("configureMouse", function() { return {extend: true} })
mouseDown(cm, 1, Pos(0, 0))
eqCharPos(cm.getCursor("from"), Pos(0, 0))
eqCharPos(cm.getCursor("to"), Pos(0, 4))
}, {value: "foo bar baz"})
testCM("findPosH", function(cm) {
forEach([{from: Pos(0, 0), to: Pos(0, 1, "before"), by: 1},
{from: Pos(0, 0), to: Pos(0, 0), by: -1, hitSide: true},
{from: Pos(0, 0), to: Pos(0, 4, "before"), by: 1, unit: "word"},
{from: Pos(0, 0), to: Pos(0, 8, "before"), by: 2, unit: "word"},
{from: Pos(0, 0), to: Pos(2, 0, "after"), by: 20, unit: "word", hitSide: true},
{from: Pos(0, 7), to: Pos(0, 5, "after"), by: -1, unit: "word"},
{from: Pos(0, 4), to: Pos(0, 8, "before"), by: 1, unit: "word"},
{from: Pos(1, 0), to: Pos(1, 18, "before"), by: 3, unit: "word"},
{from: Pos(1, 22), to: Pos(1, 5, "after"), by: -3, unit: "word"},
{from: Pos(1, 15), to: Pos(1, 10, "after"), by: -5},
{from: Pos(1, 15), to: Pos(1, 10, "after"), by: -5, unit: "column"},
{from: Pos(1, 15), to: Pos(1, 0, "after"), by: -50, unit: "column", hitSide: true},
{from: Pos(1, 15), to: Pos(1, 24, "before"), by: 50, unit: "column", hitSide: true},
{from: Pos(1, 15), to: Pos(2, 0, "after"), by: 50, hitSide: true}], function(t) {
var r = cm.findPosH(t.from, t.by, t.unit || "char");
eqCursorPos(r, t.to);
eq(!!r.hitSide, !!t.hitSide);
});
}, {value: "line one\nline two.something.other\n"});
testCM("beforeChange", function(cm) {
cm.on("beforeChange", function(cm, change) {
var text = [];
for (var i = 0; i < change.text.length; ++i)
text.push(change.text[i].replace(/\s/g, "_"));
change.update(null, null, text);
});
cm.setValue("hello, i am a\nnew document\n");
eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
CodeMirror.on(cm.getDoc(), "beforeChange", function(doc, change) {
if (change.from.line == 0) change.cancel();
});
cm.setValue("oops"); // Canceled
eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
cm.replaceRange("hey hey hey", Pos(1, 0), Pos(2, 0));
eq(cm.getValue(), "hello,_i_am_a\nhey_hey_hey");
}, {value: "abcdefghijk"});
testCM("beforeChangeUndo", function(cm) {
cm.replaceRange("hi", Pos(0, 0), Pos(0));
cm.replaceRange("bye", Pos(0, 0), Pos(0));
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;
if (!len || pos.ch == len) return Pos(pos.line, pos.ch - 1);
return pos;
}
cm.on("beforeSelectionChange", function(cm, obj) {
obj.update([{anchor: notAtEnd(cm, obj.ranges[0].anchor),
head: notAtEnd(cm, obj.ranges[0].head)}]);
});
addDoc(cm, 10, 10);
cm.execCommand("goLineEnd");
eqCursorPos(cm.getCursor(), Pos(0, 9));
cm.execCommand("selectAll");
eqCursorPos(cm.getCursor("start"), Pos(0, 0));
eqCursorPos(cm.getCursor("end"), Pos(9, 9));
});
testCM("change_removedText", function(cm) {
cm.setValue("abc\ndef");
var removedText = [];
cm.on("change", function(cm, change) {
removedText.push(change.removed);
});
cm.operation(function() {
cm.replaceRange("xyz", Pos(0, 0), Pos(1,1));
cm.replaceRange("123", Pos(0,0));
});
eq(removedText.length, 2);
eq(removedText[0].join("\n"), "abc\nd");
eq(removedText[1].join("\n"), "");
var removedText = [];
cm.undo();
eq(removedText.length, 2);
eq(removedText[0].join("\n"), "123");
eq(removedText[1].join("\n"), "xyz");
var removedText = [];
cm.redo();
eq(removedText.length, 2);
eq(removedText[0].join("\n"), "abc\nd");
eq(removedText[1].join("\n"), "");
});
testCM("lineStyleFromMode", function(cm) {
CodeMirror.defineMode("test_mode", function() {
return {token: function(stream) {
if (stream.match(/^\[[^\]]*\]/)) return " line-brackets ";
if (stream.match(/^\([^\)]*\)/)) return " line-background-parens ";
if (stream.match(/^<[^>]*>/)) return " span line-line line-background-bg ";
stream.match(/^\s+|^\S+/);
}};
});
cm.setOption("mode", "test_mode");
var bracketElts = byClassName(cm.getWrapperElement(), "brackets");
eq(bracketElts.length, 1, "brackets count");
eq(bracketElts[0].nodeName, "PRE");
is(!/brackets.*brackets/.test(bracketElts[0].className));
var parenElts = byClassName(cm.getWrapperElement(), "parens");
eq(parenElts.length, 1, "parens count");
eq(parenElts[0].nodeName, "DIV");
is(!/parens.*parens/.test(parenElts[0].className));
eq(parenElts[0].parentElement.nodeName, "DIV");
is(byClassName(cm.getWrapperElement(), "bg").length > 0);
is(byClassName(cm.getWrapperElement(), "line").length > 0);
var spanElts = byClassName(cm.getWrapperElement(), "cm-span");
eq(spanElts.length, 2);
is(/^\s*cm-span\s*$/.test(spanElts[0].className));
}, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: <tag> <tag>"});
testCM("lineStyleFromBlankLine", function(cm) {
CodeMirror.defineMode("lineStyleFromBlankLine_mode", function() {
return {token: function(stream) { stream.skipToEnd(); return "comment"; },
blankLine: function() { return "line-blank"; }};
});
cm.setOption("mode", "lineStyleFromBlankLine_mode");
var blankElts = byClassName(cm.getWrapperElement(), "blank");
eq(blankElts.length, 1);
eq(blankElts[0].nodeName, "PRE");
cm.replaceRange("x", Pos(1, 0));
blankElts = byClassName(cm.getWrapperElement(), "blank");
eq(blankElts.length, 0);
}, {value: "foo\n\nbar"});
CodeMirror.registerHelper("xxx", "a", "A");
CodeMirror.registerHelper("xxx", "b", "B");
CodeMirror.defineMode("yyy", function() {
return {
token: function(stream) { stream.skipToEnd(); },
xxx: ["a", "b", "q"]
};
});
CodeMirror.registerGlobalHelper("xxx", "c", function(m) { return m.enableC; }, "C");
testCM("helpers", function(cm) {
cm.setOption("mode", "yyy");
eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "A/B");
cm.setOption("mode", {name: "yyy", modeProps: {xxx: "b", enableC: true}});
eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "B/C");
cm.setOption("mode", "javascript");
eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "");
});
testCM("selectionHistory", function(cm) {
for (var i = 0; i < 3; i++) {
cm.setExtending(true);
cm.execCommand("goCharRight");
cm.setExtending(false);
cm.execCommand("goCharRight");
cm.execCommand("goCharRight");
}
cm.execCommand("undoSelection");
eq(cm.getSelection(), "c");
cm.execCommand("undoSelection");
eq(cm.getSelection(), "");
eqCursorPos(cm.getCursor(), Pos(0, 4, "before"));
cm.execCommand("undoSelection");
eq(cm.getSelection(), "b");
cm.execCommand("redoSelection");
eq(cm.getSelection(), "");
eqCursorPos(cm.getCursor(), Pos(0, 4, "before"));
cm.execCommand("redoSelection");
eq(cm.getSelection(), "c");
cm.execCommand("redoSelection");
eq(cm.getSelection(), "");
eqCursorPos(cm.getCursor(), Pos(0, 6, "before"));
}, {value: "a b c d"});
testCM("selectionChangeReducesRedo", function(cm) {
cm.replaceSelection("X");
cm.execCommand("goCharRight");
cm.undoSelection();
cm.execCommand("selectAll");
cm.undoSelection();
eq(cm.getValue(), "Xabc");
eqCursorPos(cm.getCursor(), Pos(0, 1));
cm.undoSelection();
eq(cm.getValue(), "abc");
}, {value: "abc"});
testCM("selectionHistoryNonOverlapping", function(cm) {
cm.setSelection(Pos(0, 0), Pos(0, 1));
cm.setSelection(Pos(0, 2), Pos(0, 3));
cm.execCommand("undoSelection");
eqCursorPos(cm.getCursor("anchor"), Pos(0, 0));
eqCursorPos(cm.getCursor("head"), Pos(0, 1));
}, {value: "1234"});
testCM("cursorMotionSplitsHistory", function(cm) {
cm.replaceSelection("a");
cm.execCommand("goCharRight");
cm.replaceSelection("b");
cm.replaceSelection("c");
cm.undo();
eq(cm.getValue(), "a1234");
eqCursorPos(cm.getCursor(), Pos(0, 2, "before"));
cm.undo();
eq(cm.getValue(), "1234");
eqCursorPos(cm.getCursor(), Pos(0, 0));
}, {value: "1234"});
testCM("selChangeInOperationDoesNotSplit", function(cm) {
for (var i = 0; i < 4; i++) {
cm.operation(function() {
cm.replaceSelection("x");
cm.setCursor(Pos(0, cm.getCursor().ch - 1));
});
}
eqCursorPos(cm.getCursor(), Pos(0, 0));
eq(cm.getValue(), "xxxxa");
cm.undo();
eq(cm.getValue(), "a");
}, {value: "a"});
testCM("alwaysMergeSelEventWithChangeOrigin", function(cm) {
cm.replaceSelection("U", null, "foo");
cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "foo"});
cm.undoSelection();
eq(cm.getValue(), "a");
cm.replaceSelection("V", null, "foo");
cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "bar"});
cm.undoSelection();
eq(cm.getValue(), "Va");
}, {value: "a"});
testCM("getTokenAt", function(cm) {
var tokPlus = cm.getTokenAt(Pos(0, 2));
eq(tokPlus.type, "operator");
eq(tokPlus.string, "+");
var toks = cm.getLineTokens(0);
eq(toks.length, 3);
forEach([["number", "1"], ["operator", "+"], ["number", "2"]], function(expect, i) {
eq(toks[i].type, expect[0]);
eq(toks[i].string, expect[1]);
});
}, {value: "1+2", mode: "javascript"});
testCM("getTokenTypeAt", function(cm) {
eq(cm.getTokenTypeAt(Pos(0, 0)), "number");
eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
cm.addOverlay({
token: function(stream) {
if (stream.match("foo")) return "foo";
else stream.next();
}
});
eq(byClassName(cm.getWrapperElement(), "cm-foo").length, 1);
eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
}, {value: "1 + 'foo'", mode: "javascript"});
testCM("addOverlay", function(cm) {
cm.addOverlay({
token: function(stream) {
var base = stream.baseToken()
if (!/comment/.test(base.type) && stream.match(/\d+/)) return "x"
stream.next()
}
})
var x = byClassName(cm.getWrapperElement(), "cm-x")
is(x.length, 1)
is(x[0].textContent, "233")
cm.replaceRange("", Pos(0, 4), Pos(0, 6))
is(byClassName(cm.getWrapperElement(), "cm-x").length, 2)
}, {value: "foo /* 100 */\nbar + 233;\nbaz", mode: "javascript"})
testCM("resizeLineWidget", function(cm) {
addDoc(cm, 200, 3);
var widget = document.createElement("pre");
widget.innerHTML = "imwidget";
widget.style.background = "yellow";
cm.addLineWidget(1, widget, {noHScroll: true});
cm.setSize(40);
is(widget.parentNode.offsetWidth < 42);
});
testCM("combinedOperations", function(cm) {
var place = document.getElementById("testground");
var other = CodeMirror(place, {value: "123"});
try {
cm.operation(function() {
cm.addLineClass(0, "wrap", "foo");
other.addLineClass(0, "wrap", "foo");
});
eq(byClassName(cm.getWrapperElement(), "foo").length, 1);
eq(byClassName(other.getWrapperElement(), "foo").length, 1);
cm.operation(function() {
cm.removeLineClass(0, "wrap", "foo");
other.removeLineClass(0, "wrap", "foo");
});
eq(byClassName(cm.getWrapperElement(), "foo").length, 0);
eq(byClassName(other.getWrapperElement(), "foo").length, 0);
} finally {
place.removeChild(other.getWrapperElement());
}
}, {value: "abc"});
testCM("eventOrder", function(cm) {
var seen = [];
cm.on("change", function() {
if (!seen.length) cm.replaceSelection(".");
seen.push("change");
});
cm.on("cursorActivity", function() {
cm.replaceSelection("!");
seen.push("activity");
});
cm.replaceSelection("/");
eq(seen.join(","), "change,change,activity,change");
});
testCM("splitSpaces_nonspecial", function(cm) {
eq(byClassName(cm.getWrapperElement(), "cm-invalidchar").length, 0);
}, {
specialChars: /[\u00a0]/,
value: "spaces -> <- between"
});
test("core_rmClass", function() {
var node = document.createElement("div");
node.className = "foo-bar baz-quux yadda";
CodeMirror.rmClass(node, "quux");
eq(node.className, "foo-bar baz-quux yadda");
CodeMirror.rmClass(node, "baz-quux");
eq(node.className, "foo-bar yadda");
CodeMirror.rmClass(node, "yadda");
eq(node.className, "foo-bar");
CodeMirror.rmClass(node, "foo-bar");
eq(node.className, "");
node.className = " foo ";
CodeMirror.rmClass(node, "foo");
eq(node.className, "");
});
test("core_addClass", function() {
var node = document.createElement("div");
CodeMirror.addClass(node, "a");
eq(node.className, "a");
CodeMirror.addClass(node, "a");
eq(node.className, "a");
CodeMirror.addClass(node, "b");
eq(node.className, "a b");
CodeMirror.addClass(node, "a");
CodeMirror.addClass(node, "b");
eq(node.className, "a b");
});
testCM("lineSeparator", function(cm) {
eq(cm.lineCount(), 3);
eq(cm.getLine(1), "bar\r");
eq(cm.getLine(2), "baz\rquux");
cm.setOption("lineSeparator", "\r");
eq(cm.lineCount(), 5);
eq(cm.getLine(4), "quux");
eq(cm.getValue(), "foo\rbar\r\rbaz\rquux");
eq(cm.getValue("\n"), "foo\nbar\n\nbaz\nquux");
cm.setOption("lineSeparator", null);
cm.setValue("foo\nbar\r\nbaz\rquux");
eq(cm.lineCount(), 4);
}, {value: "foo\nbar\r\nbaz\rquux",
lineSeparator: "\n"});
var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/
var getChar = function (noExtending) { var res; do {res = String.fromCharCode(Math.floor(Math.random()*0x8ac)); } while ([0x90].indexOf(res.charCodeAt(0)) != -1 || (noExtending && extendingChars.test(res))); return res }
var getString = function (n) { var res = getChar(true); while (--n > 0) res += getChar(); return res }
function makeItWrapAfter(cm, pos) {
var firstLineTop = cm.cursorCoords(Pos(0, 0)).top;
for(var w = 0, posTop; posTop != firstLineTop; ++w) {
cm.setSize(w);
posTop = cm.charCoords(pos).top;
}
}
function countIf(arr, f) {
var result = 0
for (var i = 0; i < arr.length; i++) if (f[arr[i]]) result++
return result
}
function testMoveBidi(str) {
testCM("move_bidi_" + str, function(cm) {
if (cm.getOption("inputStyle") != "textarea" || !cm.getOption("rtlMoveVisually")) return;
cm.getScrollerElement().style.fontFamily = "monospace";
makeItWrapAfter(cm, Pos(0, 5));
var steps = str.length - countIf(str.split(""), function(ch) { return extendingChars.test(ch) });
var lineBreaks = {}
lineBreaks[6 - countIf(str.substr(0, 5).split(""), function(ch) { return extendingChars.test(ch) })] = 'w';
if (str.indexOf("\n") != -1) {
lineBreaks[steps - 2] = 'n';
}
// Make sure we are at the visual beginning of the first line
cm.execCommand("goLineStart");
var prevCoords = cm.cursorCoords(), coords;
for(var i = 0; i < steps; ++i) {
cm.execCommand("goCharRight");
coords = cm.cursorCoords();
if ((i >= 10 && i <= 12) && !lineBreaks[i] && coords.left < prevCoords.left && coords.top > prevCoords.top) {
// The first line wraps twice
lineBreaks[i] = 'w';
}
if (!lineBreaks[i]) {
is(coords.left > prevCoords.left, "In step " + i + ", cursor didn't move right");
eq(coords.top, prevCoords.top, "In step " + i + ", cursor moved out of line");
} else {
is(coords.left < prevCoords.left, i);
is(coords.top > prevCoords.top, i);
}
prevCoords = coords;
}
cm.execCommand("goCharRight");
coords = cm.cursorCoords();
eq(coords.left, prevCoords.left, "Moving " + steps + " steps right didn't reach the end");
eq(coords.top, prevCoords.top, "Moving " + steps + " steps right didn't reach the end");
for(i = steps - 1; i >= 0; --i) {
cm.execCommand("goCharLeft");
coords = cm.cursorCoords();
if (!(lineBreaks[i] == 'n' || lineBreaks[i + 1] == 'w')) {
is(coords.left < prevCoords.left, "In step " + i + ", cursor didn't move left");
eq(coords.top, prevCoords.top, "In step " + i + ", cursor is not at the same line anymore");
} else {
is(coords.left > prevCoords.left, i);
is(coords.top < prevCoords.top, i);
}
prevCoords = coords;
}
cm.execCommand("goCharLeft");
coords = cm.cursorCoords();
eq(coords.left, prevCoords.left, "Moving " + steps + " steps left didn't reach the beginning");
eq(coords.top, prevCoords.top, "Moving " + steps + " steps left didn't reach the beginning");
}, {value: str, lineWrapping: true})
};
function testMoveEndBidi(str) {
testCM("move_end_bidi_" + str, function(cm) {
cm.getScrollerElement().style.fontFamily = "monospace";
makeItWrapAfter(cm, Pos(0, 5));
cm.execCommand("goLineStart");
var pos = cm.doc.getCursor();
cm.execCommand("goCharLeft");
eqCursorPos(pos, cm.doc.getCursor());
cm.execCommand("goLineEnd");
pos = cm.doc.getCursor();
cm.execCommand("goColumnRight");
eqCursorPos(pos, cm.doc.getCursor());
}, {value: str, lineWrapping: true})
};
var bidiTests = [];
// We don't correctly implement L1 UBA
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1331501
// and https://bugs.chromium.org/p/chromium/issues/detail?id=673405
/*
bidiTests.push("Say ا ب جabj\nS");
bidiTests.push("Sayyy ا ا ب ج");
*/
bidiTests.push("Όȝǝڪȉۥ״ۺ׆ɀҩۏ\nҳ");
if (!window.automatedTests) bidiTests.push("ŌӰтقȤ؁ƥ؅٣ĎȺ١\nϚ");
bidiTests.push("ٻоҤѕѽΩ־؉ïίքdz\nٵ");
bidiTests.push("؅؁ĆՕƿɁǞϮؠȩóć\nď");
bidiTests.push("RŨďңŪzϢŎƏԖڇڦ\nӈ");
bidiTests.push("ό׊۷٢ԜһОצЉيčǟ\nѩ");
bidiTests.push("ۑÚҳҕڬġڹհяųKV\nr");
bidiTests.push("źڻғúہ4ם1Ƞc1a\nԁ");
bidiTests.push("ҒȨҟփƞ٦ԓȦڰғâƥ\nڤ");
bidiTests.push("ϖسՉȏŧΔԛdžĎӟیڡ\nέ");
bidiTests.push("۹ؼL۵ĺȧКԙػא7״\nم");
bidiTests.push("ن (ي)\u2009أقواس"); // thin space to throw off Firefox 51's broken white-space compressing behavior
bidiTests.push("քմѧǮßپüŢҍҞўڳ\nӧ");
//bidiTests.push("Count ١ ٢ ٣ ٤");
//bidiTests.push("ӣאƦϰ؊ȓېÛوը٬ز\nϪ");
//bidiTests.push("ҾճٳџIՖӻ٥׭֐؜ڏ\nێ");
//bidiTests.push("ҬÓФ؜ڂį٦Ͽɓڐͳٵ\nՈ");
//bidiTests.push("aѴNijȻهˇ҃ڱӧǻֵ\na");
//bidiTests.push(" a٧ا٢ ب جa\nS");
for (var i = 0; i < bidiTests.length; ++i) {
testMoveBidi(bidiTests[i]);
testMoveEndBidi(bidiTests[i]);
}
/*
for (var i = 0; i < 5; ++i) {
testMoveBidi(getString(12) + "\n" + getString(1));
}
*/
function testCoordsWrappedBidi(str) {
testCM("coords_wrapped_bidi_" + str, function(cm) {
cm.getScrollerElement().style.fontFamily = "monospace";
makeItWrapAfter(cm, Pos(0, 5));
// Make sure we are at the visual beginning of the first line
var pos = Pos(0, 0), lastPos;
cm.doc.setCursor(pos);
do {
lastPos = pos;
cm.execCommand("goCharLeft");
pos = cm.doc.getCursor();
} while (pos != lastPos)
var top = cm.charCoords(Pos(0, 0)).top, lastTop;
for (var i = 1; i < str.length; ++i) {
lastTop = top;
top = cm.charCoords(Pos(0, i)).top;
is(top >= lastTop);
}
}, {value: str, lineWrapping: true})
};
testCoordsWrappedBidi("Count ١ ٢ ٣ ٤");
/*
for (var i = 0; i < 5; ++i) {
testCoordsWrappedBidi(getString(50));
}
*/
testCM("rtl_wrapped_selection", function(cm) {
cm.setSelection(Pos(0, 10), Pos(0, 190))
is(byClassName(cm.getWrapperElement(), "CodeMirror-selected").length >= 3)
}, {value: new Array(10).join(" فتي تم تضمينها فتي تم"), lineWrapping: true})
testCM("bidi_wrapped_selection", function(cm) {
cm.setSize(cm.charCoords(Pos(0, 10), "editor").left)
cm.setSelection(Pos(0, 37), Pos(0, 80))
var blocks = byClassName(cm.getWrapperElement(), "CodeMirror-selected")
is(blocks.length >= 2)
is(blocks.length <= 3)
var boxTop = blocks[0].getBoundingClientRect(), boxBot = blocks[blocks.length - 1].getBoundingClientRect()
is(boxTop.left > cm.charCoords(Pos(0, 1)).right)
is(boxBot.right < cm.charCoords(Pos(0, cm.getLine(0).length - 2)).left)
}, {value: "<p>مفتي11 تم تضمينهفتي تم تضمينها فتي تفتي تم تضمينها فتي تفتي تم تضمينها فتي تفتي تم تضمينها فتي تا فت10ي ت</p>", lineWrapping: true})
testCM("delete_wrapped", function(cm) {
makeItWrapAfter(cm, Pos(0, 2));
cm.doc.setCursor(Pos(0, 3, "after"));
cm.deleteH(-1, "char");
eq(cm.getLine(0), "1245");
}, {value: "12345", lineWrapping: true})
testCM("issue_4878", function(cm) {
if (window.automatedTests) return
cm.setCursor(Pos(1, 12, "after"));
cm.moveH(-1, "char");
eqCursorPos(cm.getCursor(), Pos(0, 113, "before"));
}, {value: " في تطبيق السمات مرة واحدة https://github.com/codemirror/CodeMirror/issues/4878#issuecomment-330550964على سبيل المثال <code>\"foo bar\"</code>\n" +
" سيتم تعيين", direction: "rtl", lineWrapping: true});
CodeMirror.defineMode("lookahead_mode", function() {
// Colors text as atom if the line two lines down has an x in it
return {
token: function(stream) {
stream.skipToEnd()
return /x/.test(stream.lookAhead(2)) ? "atom" : null
}
}
})
testCM("mode_lookahead", function(cm) {
eq(cm.getTokenAt(Pos(0, 1)).type, "atom")
eq(cm.getTokenAt(Pos(1, 1)).type, "atom")
eq(cm.getTokenAt(Pos(2, 1)).type, null)
cm.replaceRange("\n", Pos(2, 0))
eq(cm.getTokenAt(Pos(0, 1)).type, null)
eq(cm.getTokenAt(Pos(1, 1)).type, "atom")
}, {value: "foo\na\nx\nx\n", mode: "lookahead_mode"})