test.js 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034
  1. var Pos = CodeMirror.Pos;
  2. CodeMirror.defaults.rtlMoveVisually = true;
  3. function forEach(arr, f) {
  4. for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
  5. }
  6. function addDoc(cm, width, height) {
  7. var content = [], line = "";
  8. for (var i = 0; i < width; ++i) line += "x";
  9. for (var i = 0; i < height; ++i) content.push(line);
  10. cm.setValue(content.join("\n"));
  11. }
  12. function byClassName(elt, cls) {
  13. if (elt.getElementsByClassName) return elt.getElementsByClassName(cls);
  14. var found = [], re = new RegExp("\\b" + cls + "\\b");
  15. function search(elt) {
  16. if (elt.nodeType == 3) return;
  17. if (re.test(elt.className)) found.push(elt);
  18. for (var i = 0, e = elt.childNodes.length; i < e; ++i)
  19. search(elt.childNodes[i]);
  20. }
  21. search(elt);
  22. return found;
  23. }
  24. var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
  25. var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
  26. var mac = /Mac/.test(navigator.platform);
  27. var phantom = /PhantomJS/.test(navigator.userAgent);
  28. var opera = /Opera\/\./.test(navigator.userAgent);
  29. var opera_version = opera && navigator.userAgent.match(/Version\/(\d+\.\d+)/);
  30. if (opera_version) opera_version = Number(opera_version);
  31. var opera_lt10 = opera && (!opera_version || opera_version < 10);
  32. namespace = "core_";
  33. test("core_fromTextArea", function() {
  34. var te = document.getElementById("code");
  35. te.value = "CONTENT";
  36. var cm = CodeMirror.fromTextArea(te);
  37. is(!te.offsetHeight);
  38. eq(cm.getValue(), "CONTENT");
  39. cm.setValue("foo\nbar");
  40. eq(cm.getValue(), "foo\nbar");
  41. cm.save();
  42. is(/^foo\r?\nbar$/.test(te.value));
  43. cm.setValue("xxx");
  44. cm.toTextArea();
  45. is(te.offsetHeight);
  46. eq(te.value, "xxx");
  47. });
  48. testCM("getRange", function(cm) {
  49. eq(cm.getLine(0), "1234");
  50. eq(cm.getLine(1), "5678");
  51. eq(cm.getLine(2), null);
  52. eq(cm.getLine(-1), null);
  53. eq(cm.getRange(Pos(0, 0), Pos(0, 3)), "123");
  54. eq(cm.getRange(Pos(0, -1), Pos(0, 200)), "1234");
  55. eq(cm.getRange(Pos(0, 2), Pos(1, 2)), "34\n56");
  56. eq(cm.getRange(Pos(1, 2), Pos(100, 0)), "78");
  57. }, {value: "1234\n5678"});
  58. testCM("replaceRange", function(cm) {
  59. eq(cm.getValue(), "");
  60. cm.replaceRange("foo\n", Pos(0, 0));
  61. eq(cm.getValue(), "foo\n");
  62. cm.replaceRange("a\nb", Pos(0, 1));
  63. eq(cm.getValue(), "fa\nboo\n");
  64. eq(cm.lineCount(), 3);
  65. cm.replaceRange("xyzzy", Pos(0, 0), Pos(1, 1));
  66. eq(cm.getValue(), "xyzzyoo\n");
  67. cm.replaceRange("abc", Pos(0, 0), Pos(10, 0));
  68. eq(cm.getValue(), "abc");
  69. eq(cm.lineCount(), 1);
  70. });
  71. testCM("selection", function(cm) {
  72. cm.setSelection(Pos(0, 4), Pos(2, 2));
  73. is(cm.somethingSelected());
  74. eq(cm.getSelection(), "11\n222222\n33");
  75. eqPos(cm.getCursor(false), Pos(2, 2));
  76. eqPos(cm.getCursor(true), Pos(0, 4));
  77. cm.setSelection(Pos(1, 0));
  78. is(!cm.somethingSelected());
  79. eq(cm.getSelection(), "");
  80. eqPos(cm.getCursor(true), Pos(1, 0));
  81. cm.replaceSelection("abc", "around");
  82. eq(cm.getSelection(), "abc");
  83. eq(cm.getValue(), "111111\nabc222222\n333333");
  84. cm.replaceSelection("def", "end");
  85. eq(cm.getSelection(), "");
  86. eqPos(cm.getCursor(true), Pos(1, 3));
  87. cm.setCursor(Pos(2, 1));
  88. eqPos(cm.getCursor(true), Pos(2, 1));
  89. cm.setCursor(1, 2);
  90. eqPos(cm.getCursor(true), Pos(1, 2));
  91. }, {value: "111111\n222222\n333333"});
  92. testCM("extendSelection", function(cm) {
  93. cm.setExtending(true);
  94. addDoc(cm, 10, 10);
  95. cm.setSelection(Pos(3, 5));
  96. eqPos(cm.getCursor("head"), Pos(3, 5));
  97. eqPos(cm.getCursor("anchor"), Pos(3, 5));
  98. cm.setSelection(Pos(2, 5), Pos(5, 5));
  99. eqPos(cm.getCursor("head"), Pos(5, 5));
  100. eqPos(cm.getCursor("anchor"), Pos(2, 5));
  101. eqPos(cm.getCursor("start"), Pos(2, 5));
  102. eqPos(cm.getCursor("end"), Pos(5, 5));
  103. cm.setSelection(Pos(5, 5), Pos(2, 5));
  104. eqPos(cm.getCursor("head"), Pos(2, 5));
  105. eqPos(cm.getCursor("anchor"), Pos(5, 5));
  106. eqPos(cm.getCursor("start"), Pos(2, 5));
  107. eqPos(cm.getCursor("end"), Pos(5, 5));
  108. cm.extendSelection(Pos(3, 2));
  109. eqPos(cm.getCursor("head"), Pos(3, 2));
  110. eqPos(cm.getCursor("anchor"), Pos(5, 5));
  111. cm.extendSelection(Pos(6, 2));
  112. eqPos(cm.getCursor("head"), Pos(6, 2));
  113. eqPos(cm.getCursor("anchor"), Pos(5, 5));
  114. cm.extendSelection(Pos(6, 3), Pos(6, 4));
  115. eqPos(cm.getCursor("head"), Pos(6, 4));
  116. eqPos(cm.getCursor("anchor"), Pos(5, 5));
  117. cm.extendSelection(Pos(0, 3), Pos(0, 4));
  118. eqPos(cm.getCursor("head"), Pos(0, 3));
  119. eqPos(cm.getCursor("anchor"), Pos(5, 5));
  120. cm.extendSelection(Pos(4, 5), Pos(6, 5));
  121. eqPos(cm.getCursor("head"), Pos(6, 5));
  122. eqPos(cm.getCursor("anchor"), Pos(4, 5));
  123. cm.setExtending(false);
  124. cm.extendSelection(Pos(0, 3), Pos(0, 4));
  125. eqPos(cm.getCursor("head"), Pos(0, 3));
  126. eqPos(cm.getCursor("anchor"), Pos(0, 4));
  127. });
  128. testCM("lines", function(cm) {
  129. eq(cm.getLine(0), "111111");
  130. eq(cm.getLine(1), "222222");
  131. eq(cm.getLine(-1), null);
  132. cm.replaceRange("", Pos(1, 0), Pos(2, 0))
  133. cm.replaceRange("abc", Pos(1, 0), Pos(1));
  134. eq(cm.getValue(), "111111\nabc");
  135. }, {value: "111111\n222222\n333333"});
  136. testCM("indent", function(cm) {
  137. cm.indentLine(1);
  138. eq(cm.getLine(1), " blah();");
  139. cm.setOption("indentUnit", 8);
  140. cm.indentLine(1);
  141. eq(cm.getLine(1), "\tblah();");
  142. cm.setOption("indentUnit", 10);
  143. cm.setOption("tabSize", 4);
  144. cm.indentLine(1);
  145. eq(cm.getLine(1), "\t\t blah();");
  146. }, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8});
  147. testCM("indentByNumber", function(cm) {
  148. cm.indentLine(0, 2);
  149. eq(cm.getLine(0), " foo");
  150. cm.indentLine(0, -200);
  151. eq(cm.getLine(0), "foo");
  152. cm.setSelection(Pos(0, 0), Pos(1, 2));
  153. cm.indentSelection(3);
  154. eq(cm.getValue(), " foo\n bar\nbaz");
  155. }, {value: "foo\nbar\nbaz"});
  156. test("core_defaults", function() {
  157. var defsCopy = {}, defs = CodeMirror.defaults;
  158. for (var opt in defs) defsCopy[opt] = defs[opt];
  159. defs.indentUnit = 5;
  160. defs.value = "uu";
  161. defs.indentWithTabs = true;
  162. defs.tabindex = 55;
  163. var place = document.getElementById("testground"), cm = CodeMirror(place);
  164. try {
  165. eq(cm.getOption("indentUnit"), 5);
  166. cm.setOption("indentUnit", 10);
  167. eq(defs.indentUnit, 5);
  168. eq(cm.getValue(), "uu");
  169. eq(cm.getOption("indentWithTabs"), true);
  170. eq(cm.getInputField().tabIndex, 55);
  171. }
  172. finally {
  173. for (var opt in defsCopy) defs[opt] = defsCopy[opt];
  174. place.removeChild(cm.getWrapperElement());
  175. }
  176. });
  177. testCM("lineInfo", function(cm) {
  178. eq(cm.lineInfo(-1), null);
  179. var mark = document.createElement("span");
  180. var lh = cm.setGutterMarker(1, "FOO", mark);
  181. var info = cm.lineInfo(1);
  182. eq(info.text, "222222");
  183. eq(info.gutterMarkers.FOO, mark);
  184. eq(info.line, 1);
  185. eq(cm.lineInfo(2).gutterMarkers, null);
  186. cm.setGutterMarker(lh, "FOO", null);
  187. eq(cm.lineInfo(1).gutterMarkers, null);
  188. cm.setGutterMarker(1, "FOO", mark);
  189. cm.setGutterMarker(0, "FOO", mark);
  190. cm.clearGutter("FOO");
  191. eq(cm.lineInfo(0).gutterMarkers, null);
  192. eq(cm.lineInfo(1).gutterMarkers, null);
  193. }, {value: "111111\n222222\n333333"});
  194. testCM("coords", function(cm) {
  195. cm.setSize(null, 100);
  196. addDoc(cm, 32, 200);
  197. var top = cm.charCoords(Pos(0, 0));
  198. var bot = cm.charCoords(Pos(200, 30));
  199. is(top.left < bot.left);
  200. is(top.top < bot.top);
  201. is(top.top < top.bottom);
  202. cm.scrollTo(null, 100);
  203. var top2 = cm.charCoords(Pos(0, 0));
  204. is(top.top > top2.top);
  205. eq(top.left, top2.left);
  206. });
  207. testCM("coordsChar", function(cm) {
  208. addDoc(cm, 35, 70);
  209. for (var i = 0; i < 2; ++i) {
  210. var sys = i ? "local" : "page";
  211. for (var ch = 0; ch <= 35; ch += 5) {
  212. for (var line = 0; line < 70; line += 5) {
  213. cm.setCursor(line, ch);
  214. var coords = cm.charCoords(Pos(line, ch), sys);
  215. var pos = cm.coordsChar({left: coords.left + 1, top: coords.top + 1}, sys);
  216. eqPos(pos, Pos(line, ch));
  217. }
  218. }
  219. }
  220. }, {lineNumbers: true});
  221. testCM("posFromIndex", function(cm) {
  222. cm.setValue(
  223. "This function should\n" +
  224. "convert a zero based index\n" +
  225. "to line and ch."
  226. );
  227. var examples = [
  228. { index: -1, line: 0, ch: 0 }, // <- Tests clipping
  229. { index: 0, line: 0, ch: 0 },
  230. { index: 10, line: 0, ch: 10 },
  231. { index: 39, line: 1, ch: 18 },
  232. { index: 55, line: 2, ch: 7 },
  233. { index: 63, line: 2, ch: 15 },
  234. { index: 64, line: 2, ch: 15 } // <- Tests clipping
  235. ];
  236. for (var i = 0; i < examples.length; i++) {
  237. var example = examples[i];
  238. var pos = cm.posFromIndex(example.index);
  239. eq(pos.line, example.line);
  240. eq(pos.ch, example.ch);
  241. if (example.index >= 0 && example.index < 64)
  242. eq(cm.indexFromPos(pos), example.index);
  243. }
  244. });
  245. testCM("undo", function(cm) {
  246. cm.replaceRange("def", Pos(0, 0), Pos(0));
  247. eq(cm.historySize().undo, 1);
  248. cm.undo();
  249. eq(cm.getValue(), "abc");
  250. eq(cm.historySize().undo, 0);
  251. eq(cm.historySize().redo, 1);
  252. cm.redo();
  253. eq(cm.getValue(), "def");
  254. eq(cm.historySize().undo, 1);
  255. eq(cm.historySize().redo, 0);
  256. cm.setValue("1\n\n\n2");
  257. cm.clearHistory();
  258. eq(cm.historySize().undo, 0);
  259. for (var i = 0; i < 20; ++i) {
  260. cm.replaceRange("a", Pos(0, 0));
  261. cm.replaceRange("b", Pos(3, 0));
  262. }
  263. eq(cm.historySize().undo, 40);
  264. for (var i = 0; i < 40; ++i)
  265. cm.undo();
  266. eq(cm.historySize().redo, 40);
  267. eq(cm.getValue(), "1\n\n\n2");
  268. }, {value: "abc"});
  269. testCM("undoDepth", function(cm) {
  270. cm.replaceRange("d", Pos(0));
  271. cm.replaceRange("e", Pos(0));
  272. cm.replaceRange("f", Pos(0));
  273. cm.undo(); cm.undo(); cm.undo();
  274. eq(cm.getValue(), "abcd");
  275. }, {value: "abc", undoDepth: 4});
  276. testCM("undoDoesntClearValue", function(cm) {
  277. cm.undo();
  278. eq(cm.getValue(), "x");
  279. }, {value: "x"});
  280. testCM("undoMultiLine", function(cm) {
  281. cm.operation(function() {
  282. cm.replaceRange("x", Pos(0, 0));
  283. cm.replaceRange("y", Pos(1, 0));
  284. });
  285. cm.undo();
  286. eq(cm.getValue(), "abc\ndef\nghi");
  287. cm.operation(function() {
  288. cm.replaceRange("y", Pos(1, 0));
  289. cm.replaceRange("x", Pos(0, 0));
  290. });
  291. cm.undo();
  292. eq(cm.getValue(), "abc\ndef\nghi");
  293. cm.operation(function() {
  294. cm.replaceRange("y", Pos(2, 0));
  295. cm.replaceRange("x", Pos(1, 0));
  296. cm.replaceRange("z", Pos(2, 0));
  297. });
  298. cm.undo();
  299. eq(cm.getValue(), "abc\ndef\nghi", 3);
  300. }, {value: "abc\ndef\nghi"});
  301. testCM("undoComposite", function(cm) {
  302. cm.replaceRange("y", Pos(1));
  303. cm.operation(function() {
  304. cm.replaceRange("x", Pos(0));
  305. cm.replaceRange("z", Pos(2));
  306. });
  307. eq(cm.getValue(), "ax\nby\ncz\n");
  308. cm.undo();
  309. eq(cm.getValue(), "a\nby\nc\n");
  310. cm.undo();
  311. eq(cm.getValue(), "a\nb\nc\n");
  312. cm.redo(); cm.redo();
  313. eq(cm.getValue(), "ax\nby\ncz\n");
  314. }, {value: "a\nb\nc\n"});
  315. testCM("undoSelection", function(cm) {
  316. cm.setSelection(Pos(0, 2), Pos(0, 4));
  317. cm.replaceSelection("");
  318. cm.setCursor(Pos(1, 0));
  319. cm.undo();
  320. eqPos(cm.getCursor(true), Pos(0, 2));
  321. eqPos(cm.getCursor(false), Pos(0, 4));
  322. cm.setCursor(Pos(1, 0));
  323. cm.redo();
  324. eqPos(cm.getCursor(true), Pos(0, 2));
  325. eqPos(cm.getCursor(false), Pos(0, 2));
  326. }, {value: "abcdefgh\n"});
  327. testCM("undoSelectionAsBefore", function(cm) {
  328. cm.replaceSelection("abc", "around");
  329. cm.undo();
  330. cm.redo();
  331. eq(cm.getSelection(), "abc");
  332. });
  333. testCM("selectionChangeConfusesHistory", function(cm) {
  334. cm.replaceSelection("abc", null, "dontmerge");
  335. cm.operation(function() {
  336. cm.setCursor(Pos(0, 0));
  337. cm.replaceSelection("abc", null, "dontmerge");
  338. });
  339. eq(cm.historySize().undo, 2);
  340. });
  341. testCM("markTextSingleLine", function(cm) {
  342. forEach([{a: 0, b: 1, c: "", f: 2, t: 5},
  343. {a: 0, b: 4, c: "", f: 0, t: 2},
  344. {a: 1, b: 2, c: "x", f: 3, t: 6},
  345. {a: 4, b: 5, c: "", f: 3, t: 5},
  346. {a: 4, b: 5, c: "xx", f: 3, t: 7},
  347. {a: 2, b: 5, c: "", f: 2, t: 3},
  348. {a: 2, b: 5, c: "abcd", f: 6, t: 7},
  349. {a: 2, b: 6, c: "x", f: null, t: null},
  350. {a: 3, b: 6, c: "", f: null, t: null},
  351. {a: 0, b: 9, c: "hallo", f: null, t: null},
  352. {a: 4, b: 6, c: "x", f: 3, t: 4},
  353. {a: 4, b: 8, c: "", f: 3, t: 4},
  354. {a: 6, b: 6, c: "a", f: 3, t: 6},
  355. {a: 8, b: 9, c: "", f: 3, t: 6}], function(test) {
  356. cm.setValue("1234567890");
  357. var r = cm.markText(Pos(0, 3), Pos(0, 6), {className: "foo"});
  358. cm.replaceRange(test.c, Pos(0, test.a), Pos(0, test.b));
  359. var f = r.find();
  360. eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t);
  361. });
  362. });
  363. testCM("markTextMultiLine", function(cm) {
  364. function p(v) { return v && Pos(v[0], v[1]); }
  365. forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]},
  366. {a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]},
  367. {a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]},
  368. {a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]},
  369. {a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]},
  370. {a: [0, 6], b: [2, 4], c: "", f: [0, 5], t: [0, 7]},
  371. {a: [0, 6], b: [2, 4], c: "aa", f: [0, 5], t: [0, 9]},
  372. {a: [1, 2], b: [1, 8], c: "", f: [0, 5], t: [2, 5]},
  373. {a: [0, 5], b: [2, 5], c: "xx", f: null, t: null},
  374. {a: [0, 0], b: [2, 10], c: "x", f: null, t: null},
  375. {a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]},
  376. {a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]},
  377. {a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]},
  378. {a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]},
  379. {a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) {
  380. cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n");
  381. var r = cm.markText(Pos(0, 5), Pos(2, 5),
  382. {className: "CodeMirror-matchingbracket"});
  383. cm.replaceRange(test.c, p(test.a), p(test.b));
  384. var f = r.find();
  385. eqPos(f && f.from, p(test.f)); eqPos(f && f.to, p(test.t));
  386. });
  387. });
  388. testCM("markTextUndo", function(cm) {
  389. var marker1, marker2, bookmark;
  390. marker1 = cm.markText(Pos(0, 1), Pos(0, 3),
  391. {className: "CodeMirror-matchingbracket"});
  392. marker2 = cm.markText(Pos(0, 0), Pos(2, 1),
  393. {className: "CodeMirror-matchingbracket"});
  394. bookmark = cm.setBookmark(Pos(1, 5));
  395. cm.operation(function(){
  396. cm.replaceRange("foo", Pos(0, 2));
  397. cm.replaceRange("bar\nbaz\nbug\n", Pos(2, 0), Pos(3, 0));
  398. });
  399. var v1 = cm.getValue();
  400. cm.setValue("");
  401. eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null);
  402. cm.undo();
  403. eqPos(bookmark.find(), Pos(1, 5), "still there");
  404. cm.undo();
  405. var m1Pos = marker1.find(), m2Pos = marker2.find();
  406. eqPos(m1Pos.from, Pos(0, 1)); eqPos(m1Pos.to, Pos(0, 3));
  407. eqPos(m2Pos.from, Pos(0, 0)); eqPos(m2Pos.to, Pos(2, 1));
  408. eqPos(bookmark.find(), Pos(1, 5));
  409. cm.redo(); cm.redo();
  410. eq(bookmark.find(), null);
  411. cm.undo();
  412. eqPos(bookmark.find(), Pos(1, 5));
  413. eq(cm.getValue(), v1);
  414. }, {value: "1234\n56789\n00\n"});
  415. testCM("markTextStayGone", function(cm) {
  416. var m1 = cm.markText(Pos(0, 0), Pos(0, 1));
  417. cm.replaceRange("hi", Pos(0, 2));
  418. m1.clear();
  419. cm.undo();
  420. eq(m1.find(), null);
  421. }, {value: "hello"});
  422. testCM("markTextAllowEmpty", function(cm) {
  423. var m1 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false});
  424. is(m1.find());
  425. cm.replaceRange("x", Pos(0, 0));
  426. is(m1.find());
  427. cm.replaceRange("y", Pos(0, 2));
  428. is(m1.find());
  429. cm.replaceRange("z", Pos(0, 3), Pos(0, 4));
  430. is(!m1.find());
  431. var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false,
  432. inclusiveLeft: true,
  433. inclusiveRight: true});
  434. cm.replaceRange("q", Pos(0, 1), Pos(0, 2));
  435. is(m2.find());
  436. cm.replaceRange("", Pos(0, 0), Pos(0, 3));
  437. is(!m2.find());
  438. var m3 = cm.markText(Pos(0, 1), Pos(0, 1), {clearWhenEmpty: false});
  439. cm.replaceRange("a", Pos(0, 3));
  440. is(m3.find());
  441. cm.replaceRange("b", Pos(0, 1));
  442. is(!m3.find());
  443. }, {value: "abcde"});
  444. testCM("markTextStacked", function(cm) {
  445. var m1 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
  446. var m2 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
  447. cm.replaceRange("B", Pos(0, 1));
  448. is(m1.find() && m2.find());
  449. }, {value: "A"});
  450. testCM("undoPreservesNewMarks", function(cm) {
  451. cm.markText(Pos(0, 3), Pos(0, 4));
  452. cm.markText(Pos(1, 1), Pos(1, 3));
  453. cm.replaceRange("", Pos(0, 3), Pos(3, 1));
  454. var mBefore = cm.markText(Pos(0, 0), Pos(0, 1));
  455. var mAfter = cm.markText(Pos(0, 5), Pos(0, 6));
  456. var mAround = cm.markText(Pos(0, 2), Pos(0, 4));
  457. cm.undo();
  458. eqPos(mBefore.find().from, Pos(0, 0));
  459. eqPos(mBefore.find().to, Pos(0, 1));
  460. eqPos(mAfter.find().from, Pos(3, 3));
  461. eqPos(mAfter.find().to, Pos(3, 4));
  462. eqPos(mAround.find().from, Pos(0, 2));
  463. eqPos(mAround.find().to, Pos(3, 2));
  464. var found = cm.findMarksAt(Pos(2, 2));
  465. eq(found.length, 1);
  466. eq(found[0], mAround);
  467. }, {value: "aaaa\nbbbb\ncccc\ndddd"});
  468. testCM("markClearBetween", function(cm) {
  469. cm.setValue("aaa\nbbb\nccc\nddd\n");
  470. cm.markText(Pos(0, 0), Pos(2));
  471. cm.replaceRange("aaa\nbbb\nccc", Pos(0, 0), Pos(2));
  472. eq(cm.findMarksAt(Pos(1, 1)).length, 0);
  473. });
  474. testCM("deleteSpanCollapsedInclusiveLeft", function(cm) {
  475. var from = Pos(1, 0), to = Pos(1, 1);
  476. var m = cm.markText(from, to, {collapsed: true, inclusiveLeft: true});
  477. // Delete collapsed span.
  478. cm.replaceRange("", from, to);
  479. }, {value: "abc\nX\ndef"});
  480. testCM("bookmark", function(cm) {
  481. function p(v) { return v && Pos(v[0], v[1]); }
  482. forEach([{a: [1, 0], b: [1, 1], c: "", d: [1, 4]},
  483. {a: [1, 1], b: [1, 1], c: "xx", d: [1, 7]},
  484. {a: [1, 4], b: [1, 5], c: "ab", d: [1, 6]},
  485. {a: [1, 4], b: [1, 6], c: "", d: null},
  486. {a: [1, 5], b: [1, 6], c: "abc", d: [1, 5]},
  487. {a: [1, 6], b: [1, 8], c: "", d: [1, 5]},
  488. {a: [1, 4], b: [1, 4], c: "\n\n", d: [3, 1]},
  489. {bm: [1, 9], a: [1, 1], b: [1, 1], c: "\n", d: [2, 8]}], function(test) {
  490. cm.setValue("1234567890\n1234567890\n1234567890");
  491. var b = cm.setBookmark(p(test.bm) || Pos(1, 5));
  492. cm.replaceRange(test.c, p(test.a), p(test.b));
  493. eqPos(b.find(), p(test.d));
  494. });
  495. });
  496. testCM("bookmarkInsertLeft", function(cm) {
  497. var br = cm.setBookmark(Pos(0, 2), {insertLeft: false});
  498. var bl = cm.setBookmark(Pos(0, 2), {insertLeft: true});
  499. cm.setCursor(Pos(0, 2));
  500. cm.replaceSelection("hi");
  501. eqPos(br.find(), Pos(0, 2));
  502. eqPos(bl.find(), Pos(0, 4));
  503. cm.replaceRange("", Pos(0, 4), Pos(0, 5));
  504. cm.replaceRange("", Pos(0, 2), Pos(0, 4));
  505. cm.replaceRange("", Pos(0, 1), Pos(0, 2));
  506. // Verify that deleting next to bookmarks doesn't kill them
  507. eqPos(br.find(), Pos(0, 1));
  508. eqPos(bl.find(), Pos(0, 1));
  509. }, {value: "abcdef"});
  510. testCM("bookmarkCursor", function(cm) {
  511. var pos01 = cm.cursorCoords(Pos(0, 1)), pos11 = cm.cursorCoords(Pos(1, 1)),
  512. pos20 = cm.cursorCoords(Pos(2, 0)), pos30 = cm.cursorCoords(Pos(3, 0)),
  513. pos41 = cm.cursorCoords(Pos(4, 1));
  514. cm.setBookmark(Pos(0, 1), {widget: document.createTextNode("←"), insertLeft: true});
  515. cm.setBookmark(Pos(2, 0), {widget: document.createTextNode("←"), insertLeft: true});
  516. cm.setBookmark(Pos(1, 1), {widget: document.createTextNode("→")});
  517. cm.setBookmark(Pos(3, 0), {widget: document.createTextNode("→")});
  518. var new01 = cm.cursorCoords(Pos(0, 1)), new11 = cm.cursorCoords(Pos(1, 1)),
  519. new20 = cm.cursorCoords(Pos(2, 0)), new30 = cm.cursorCoords(Pos(3, 0));
  520. near(new01.left, pos01.left, 1);
  521. near(new01.top, pos01.top, 1);
  522. is(new11.left > pos11.left, "at right, middle of line");
  523. near(new11.top == pos11.top, 1);
  524. near(new20.left, pos20.left, 1);
  525. near(new20.top, pos20.top, 1);
  526. is(new30.left > pos30.left, "at right, empty line");
  527. near(new30.top, pos30, 1);
  528. cm.setBookmark(Pos(4, 0), {widget: document.createTextNode("→")});
  529. is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug");
  530. }, {value: "foo\nbar\n\n\nx\ny"});
  531. testCM("multiBookmarkCursor", function(cm) {
  532. if (phantom) return;
  533. var ms = [], m;
  534. function add(insertLeft) {
  535. for (var i = 0; i < 3; ++i) {
  536. var node = document.createElement("span");
  537. node.innerHTML = "X";
  538. ms.push(cm.setBookmark(Pos(0, 1), {widget: node, insertLeft: insertLeft}));
  539. }
  540. }
  541. var base1 = cm.cursorCoords(Pos(0, 1)).left, base4 = cm.cursorCoords(Pos(0, 4)).left;
  542. add(true);
  543. near(base1, cm.cursorCoords(Pos(0, 1)).left, 1);
  544. while (m = ms.pop()) m.clear();
  545. add(false);
  546. near(base4, cm.cursorCoords(Pos(0, 1)).left, 1);
  547. }, {value: "abcdefg"});
  548. testCM("getAllMarks", function(cm) {
  549. addDoc(cm, 10, 10);
  550. var m1 = cm.setBookmark(Pos(0, 2));
  551. var m2 = cm.markText(Pos(0, 2), Pos(3, 2));
  552. var m3 = cm.markText(Pos(1, 2), Pos(1, 8));
  553. var m4 = cm.markText(Pos(8, 0), Pos(9, 0));
  554. eq(cm.getAllMarks().length, 4);
  555. m1.clear();
  556. m3.clear();
  557. eq(cm.getAllMarks().length, 2);
  558. });
  559. testCM("bug577", function(cm) {
  560. cm.setValue("a\nb");
  561. cm.clearHistory();
  562. cm.setValue("fooooo");
  563. cm.undo();
  564. });
  565. testCM("scrollSnap", function(cm) {
  566. cm.setSize(100, 100);
  567. addDoc(cm, 200, 200);
  568. cm.setCursor(Pos(100, 180));
  569. var info = cm.getScrollInfo();
  570. is(info.left > 0 && info.top > 0);
  571. cm.setCursor(Pos(0, 0));
  572. info = cm.getScrollInfo();
  573. is(info.left == 0 && info.top == 0, "scrolled clean to top");
  574. cm.setCursor(Pos(100, 180));
  575. cm.setCursor(Pos(199, 0));
  576. info = cm.getScrollInfo();
  577. is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom");
  578. });
  579. testCM("scrollIntoView", function(cm) {
  580. if (phantom) return;
  581. var outer = cm.getWrapperElement().getBoundingClientRect();
  582. function test(line, ch, msg) {
  583. var pos = Pos(line, ch);
  584. cm.scrollIntoView(pos);
  585. var box = cm.charCoords(pos, "window");
  586. is(box.left >= outer.left, msg + " (left)");
  587. is(box.right <= outer.right, msg + " (right)");
  588. is(box.top >= outer.top, msg + " (top)");
  589. is(box.bottom <= outer.bottom, msg + " (bottom)");
  590. }
  591. addDoc(cm, 200, 200);
  592. test(199, 199, "bottom right");
  593. test(0, 0, "top left");
  594. test(100, 100, "center");
  595. test(199, 0, "bottom left");
  596. test(0, 199, "top right");
  597. test(100, 100, "center again");
  598. });
  599. testCM("scrollBackAndForth", function(cm) {
  600. addDoc(cm, 1, 200);
  601. cm.operation(function() {
  602. cm.scrollIntoView(Pos(199, 0));
  603. cm.scrollIntoView(Pos(4, 0));
  604. });
  605. is(cm.getScrollInfo().top > 0);
  606. });
  607. testCM("selectAllNoScroll", function(cm) {
  608. addDoc(cm, 1, 200);
  609. cm.execCommand("selectAll");
  610. eq(cm.getScrollInfo().top, 0);
  611. cm.setCursor(199);
  612. cm.execCommand("selectAll");
  613. is(cm.getScrollInfo().top > 0);
  614. });
  615. testCM("selectionPos", function(cm) {
  616. if (phantom) return;
  617. cm.setSize(100, 100);
  618. addDoc(cm, 200, 100);
  619. cm.setSelection(Pos(1, 100), Pos(98, 100));
  620. var lineWidth = cm.charCoords(Pos(0, 200), "local").left;
  621. var lineHeight = (cm.charCoords(Pos(99)).top - cm.charCoords(Pos(0)).top) / 100;
  622. cm.scrollTo(0, 0);
  623. var selElt = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
  624. var outer = cm.getWrapperElement().getBoundingClientRect();
  625. var sawMiddle, sawTop, sawBottom;
  626. for (var i = 0, e = selElt.length; i < e; ++i) {
  627. var box = selElt[i].getBoundingClientRect();
  628. var atLeft = box.left - outer.left < 30;
  629. var width = box.right - box.left;
  630. var atRight = box.right - outer.left > .8 * lineWidth;
  631. if (atLeft && atRight) {
  632. sawMiddle = true;
  633. is(box.bottom - box.top > 90 * lineHeight, "middle high");
  634. is(width > .9 * lineWidth, "middle wide");
  635. } else {
  636. is(width > .4 * lineWidth, "top/bot wide enough");
  637. is(width < .6 * lineWidth, "top/bot slim enough");
  638. if (atLeft) {
  639. sawBottom = true;
  640. is(box.top - outer.top > 96 * lineHeight, "bot below");
  641. } else if (atRight) {
  642. sawTop = true;
  643. is(box.top - outer.top < 2.1 * lineHeight, "top above");
  644. }
  645. }
  646. }
  647. is(sawTop && sawBottom && sawMiddle, "all parts");
  648. }, null);
  649. testCM("restoreHistory", function(cm) {
  650. cm.setValue("abc\ndef");
  651. cm.replaceRange("hello", Pos(1, 0), Pos(1));
  652. cm.replaceRange("goop", Pos(0, 0), Pos(0));
  653. cm.undo();
  654. var storedVal = cm.getValue(), storedHist = cm.getHistory();
  655. if (window.JSON) storedHist = JSON.parse(JSON.stringify(storedHist));
  656. eq(storedVal, "abc\nhello");
  657. cm.setValue("");
  658. cm.clearHistory();
  659. eq(cm.historySize().undo, 0);
  660. cm.setValue(storedVal);
  661. cm.setHistory(storedHist);
  662. cm.redo();
  663. eq(cm.getValue(), "goop\nhello");
  664. cm.undo(); cm.undo();
  665. eq(cm.getValue(), "abc\ndef");
  666. });
  667. testCM("doubleScrollbar", function(cm) {
  668. var dummy = document.body.appendChild(document.createElement("p"));
  669. dummy.style.cssText = "height: 50px; overflow: scroll; width: 50px";
  670. var scrollbarWidth = dummy.offsetWidth + 1 - dummy.clientWidth;
  671. document.body.removeChild(dummy);
  672. if (scrollbarWidth < 2) return;
  673. cm.setSize(null, 100);
  674. addDoc(cm, 1, 300);
  675. var wrap = cm.getWrapperElement();
  676. is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth * 1.5);
  677. });
  678. testCM("weirdLinebreaks", function(cm) {
  679. cm.setValue("foo\nbar\rbaz\r\nquux\n\rplop");
  680. is(cm.getValue(), "foo\nbar\nbaz\nquux\n\nplop");
  681. is(cm.lineCount(), 6);
  682. cm.setValue("\n\n");
  683. is(cm.lineCount(), 3);
  684. });
  685. testCM("setSize", function(cm) {
  686. cm.setSize(100, 100);
  687. var wrap = cm.getWrapperElement();
  688. is(wrap.offsetWidth, 100);
  689. is(wrap.offsetHeight, 100);
  690. cm.setSize("100%", "3em");
  691. is(wrap.style.width, "100%");
  692. is(wrap.style.height, "3em");
  693. cm.setSize(null, 40);
  694. is(wrap.style.width, "100%");
  695. is(wrap.style.height, "40px");
  696. });
  697. function foldLines(cm, start, end, autoClear) {
  698. return cm.markText(Pos(start, 0), Pos(end - 1), {
  699. inclusiveLeft: true,
  700. inclusiveRight: true,
  701. collapsed: true,
  702. clearOnEnter: autoClear
  703. });
  704. }
  705. testCM("collapsedLines", function(cm) {
  706. addDoc(cm, 4, 10);
  707. var range = foldLines(cm, 4, 5), cleared = 0;
  708. CodeMirror.on(range, "clear", function() {cleared++;});
  709. cm.setCursor(Pos(3, 0));
  710. CodeMirror.commands.goLineDown(cm);
  711. eqPos(cm.getCursor(), Pos(5, 0));
  712. cm.replaceRange("abcdefg", Pos(3, 0), Pos(3));
  713. cm.setCursor(Pos(3, 6));
  714. CodeMirror.commands.goLineDown(cm);
  715. eqPos(cm.getCursor(), Pos(5, 4));
  716. cm.replaceRange("ab", Pos(3, 0), Pos(3));
  717. cm.setCursor(Pos(3, 2));
  718. CodeMirror.commands.goLineDown(cm);
  719. eqPos(cm.getCursor(), Pos(5, 2));
  720. cm.operation(function() {range.clear(); range.clear();});
  721. eq(cleared, 1);
  722. });
  723. testCM("collapsedRangeCoordsChar", function(cm) {
  724. var pos_1_3 = cm.charCoords(Pos(1, 3));
  725. pos_1_3.left += 2; pos_1_3.top += 2;
  726. var opts = {collapsed: true, inclusiveLeft: true, inclusiveRight: true};
  727. var m1 = cm.markText(Pos(0, 0), Pos(2, 0), opts);
  728. eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
  729. m1.clear();
  730. var m1 = cm.markText(Pos(0, 0), Pos(1, 1), {collapsed: true, inclusiveLeft: true});
  731. var m2 = cm.markText(Pos(1, 1), Pos(2, 0), {collapsed: true, inclusiveRight: true});
  732. eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
  733. m1.clear(); m2.clear();
  734. var m1 = cm.markText(Pos(0, 0), Pos(1, 6), opts);
  735. eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
  736. }, {value: "123456\nabcdef\nghijkl\nmnopqr\n"});
  737. testCM("collapsedRangeBetweenLinesSelected", function(cm) {
  738. var widget = document.createElement("span");
  739. widget.textContent = "\u2194";
  740. cm.markText(Pos(0, 3), Pos(1, 0), {replacedWith: widget});
  741. cm.setSelection(Pos(0, 3), Pos(1, 0));
  742. var selElts = byClassName(cm.getWrapperElement(), "CodeMirror-selected");
  743. for (var i = 0, w = 0; i < selElts.length; i++)
  744. w += selElts[i].offsetWidth;
  745. is(w > 0);
  746. }, {value: "one\ntwo"});
  747. testCM("randomCollapsedRanges", function(cm) {
  748. addDoc(cm, 20, 500);
  749. cm.operation(function() {
  750. for (var i = 0; i < 200; i++) {
  751. var start = Pos(Math.floor(Math.random() * 500), Math.floor(Math.random() * 20));
  752. if (i % 4)
  753. try { cm.markText(start, Pos(start.line + 2, 1), {collapsed: true}); }
  754. catch(e) { if (!/overlapping/.test(String(e))) throw e; }
  755. else
  756. cm.markText(start, Pos(start.line, start.ch + 4), {"className": "foo"});
  757. }
  758. });
  759. });
  760. testCM("hiddenLinesAutoUnfold", function(cm) {
  761. var range = foldLines(cm, 1, 3, true), cleared = 0;
  762. CodeMirror.on(range, "clear", function() {cleared++;});
  763. cm.setCursor(Pos(3, 0));
  764. eq(cleared, 0);
  765. cm.execCommand("goCharLeft");
  766. eq(cleared, 1);
  767. range = foldLines(cm, 1, 3, true);
  768. CodeMirror.on(range, "clear", function() {cleared++;});
  769. eqPos(cm.getCursor(), Pos(3, 0));
  770. cm.setCursor(Pos(0, 3));
  771. cm.execCommand("goCharRight");
  772. eq(cleared, 2);
  773. }, {value: "abc\ndef\nghi\njkl"});
  774. testCM("hiddenLinesSelectAll", function(cm) { // Issue #484
  775. addDoc(cm, 4, 20);
  776. foldLines(cm, 0, 10);
  777. foldLines(cm, 11, 20);
  778. CodeMirror.commands.selectAll(cm);
  779. eqPos(cm.getCursor(true), Pos(10, 0));
  780. eqPos(cm.getCursor(false), Pos(10, 4));
  781. });
  782. testCM("everythingFolded", function(cm) {
  783. addDoc(cm, 2, 2);
  784. function enterPress() {
  785. cm.triggerOnKeyDown({type: "keydown", keyCode: 13, preventDefault: function(){}, stopPropagation: function(){}});
  786. }
  787. var fold = foldLines(cm, 0, 2);
  788. enterPress();
  789. eq(cm.getValue(), "xx\nxx");
  790. fold.clear();
  791. fold = foldLines(cm, 0, 2, true);
  792. eq(fold.find(), null);
  793. enterPress();
  794. eq(cm.getValue(), "\nxx\nxx");
  795. });
  796. testCM("structuredFold", function(cm) {
  797. if (phantom) return;
  798. addDoc(cm, 4, 8);
  799. var range = cm.markText(Pos(1, 2), Pos(6, 2), {
  800. replacedWith: document.createTextNode("Q")
  801. });
  802. cm.setCursor(0, 3);
  803. CodeMirror.commands.goLineDown(cm);
  804. eqPos(cm.getCursor(), Pos(6, 2));
  805. CodeMirror.commands.goCharLeft(cm);
  806. eqPos(cm.getCursor(), Pos(1, 2));
  807. CodeMirror.commands.delCharAfter(cm);
  808. eq(cm.getValue(), "xxxx\nxxxx\nxxxx");
  809. addDoc(cm, 4, 8);
  810. range = cm.markText(Pos(1, 2), Pos(6, 2), {
  811. replacedWith: document.createTextNode("M"),
  812. clearOnEnter: true
  813. });
  814. var cleared = 0;
  815. CodeMirror.on(range, "clear", function(){++cleared;});
  816. cm.setCursor(0, 3);
  817. CodeMirror.commands.goLineDown(cm);
  818. eqPos(cm.getCursor(), Pos(6, 2));
  819. CodeMirror.commands.goCharLeft(cm);
  820. eqPos(cm.getCursor(), Pos(6, 1));
  821. eq(cleared, 1);
  822. range.clear();
  823. eq(cleared, 1);
  824. range = cm.markText(Pos(1, 2), Pos(6, 2), {
  825. replacedWith: document.createTextNode("Q"),
  826. clearOnEnter: true
  827. });
  828. range.clear();
  829. cm.setCursor(1, 2);
  830. CodeMirror.commands.goCharRight(cm);
  831. eqPos(cm.getCursor(), Pos(1, 3));
  832. range = cm.markText(Pos(2, 0), Pos(4, 4), {
  833. replacedWith: document.createTextNode("M")
  834. });
  835. cm.setCursor(1, 0);
  836. CodeMirror.commands.goLineDown(cm);
  837. eqPos(cm.getCursor(), Pos(2, 0));
  838. }, null);
  839. testCM("nestedFold", function(cm) {
  840. addDoc(cm, 10, 3);
  841. function fold(ll, cl, lr, cr) {
  842. return cm.markText(Pos(ll, cl), Pos(lr, cr), {collapsed: true});
  843. }
  844. 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);
  845. cm.setCursor(0, 1);
  846. CodeMirror.commands.goCharRight(cm);
  847. eqPos(cm.getCursor(), Pos(2, 3));
  848. inner0.clear();
  849. CodeMirror.commands.goCharLeft(cm);
  850. eqPos(cm.getCursor(), Pos(0, 1));
  851. outer.clear();
  852. CodeMirror.commands.goCharRight(cm);
  853. eqPos(cm.getCursor(), Pos(0, 2));
  854. CodeMirror.commands.goCharRight(cm);
  855. eqPos(cm.getCursor(), Pos(1, 8));
  856. inner2.clear();
  857. CodeMirror.commands.goCharLeft(cm);
  858. eqPos(cm.getCursor(), Pos(1, 7));
  859. cm.setCursor(0, 5);
  860. CodeMirror.commands.goCharRight(cm);
  861. eqPos(cm.getCursor(), Pos(0, 6));
  862. CodeMirror.commands.goCharRight(cm);
  863. eqPos(cm.getCursor(), Pos(1, 3));
  864. });
  865. testCM("badNestedFold", function(cm) {
  866. addDoc(cm, 4, 4);
  867. cm.markText(Pos(0, 2), Pos(3, 2), {collapsed: true});
  868. var caught;
  869. try {cm.markText(Pos(0, 1), Pos(0, 3), {collapsed: true});}
  870. catch(e) {caught = e;}
  871. is(caught instanceof Error, "no error");
  872. is(/overlap/i.test(caught.message), "wrong error");
  873. });
  874. testCM("nestedFoldOnSide", function(cm) {
  875. var m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true, inclusiveRight: true});
  876. var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true});
  877. cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true}).clear();
  878. try { cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true, inclusiveLeft: true}); }
  879. catch(e) { var caught = e; }
  880. is(caught && /overlap/i.test(caught.message));
  881. var m3 = cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true});
  882. var m4 = cm.markText(Pos(2, 0), Pos(2, 1), {collapse: true, inclusiveRight: true});
  883. m1.clear(); m4.clear();
  884. m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true});
  885. cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true}).clear();
  886. try { cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true, inclusiveRight: true}); }
  887. catch(e) { var caught = e; }
  888. is(caught && /overlap/i.test(caught.message));
  889. }, {value: "ab\ncd\ef"});
  890. testCM("editInFold", function(cm) {
  891. addDoc(cm, 4, 6);
  892. var m = cm.markText(Pos(1, 2), Pos(3, 2), {collapsed: true});
  893. cm.replaceRange("", Pos(0, 0), Pos(1, 3));
  894. cm.replaceRange("", Pos(2, 1), Pos(3, 3));
  895. cm.replaceRange("a\nb\nc\nd", Pos(0, 1), Pos(1, 0));
  896. cm.cursorCoords(Pos(0, 0));
  897. });
  898. testCM("wrappingInlineWidget", function(cm) {
  899. cm.setSize("11em");
  900. var w = document.createElement("span");
  901. w.style.color = "red";
  902. w.innerHTML = "one two three four";
  903. cm.markText(Pos(0, 6), Pos(0, 9), {replacedWith: w});
  904. var cur0 = cm.cursorCoords(Pos(0, 0)), cur1 = cm.cursorCoords(Pos(0, 10));
  905. is(cur0.top < cur1.top);
  906. is(cur0.bottom < cur1.bottom);
  907. var curL = cm.cursorCoords(Pos(0, 6)), curR = cm.cursorCoords(Pos(0, 9));
  908. eq(curL.top, cur0.top);
  909. eq(curL.bottom, cur0.bottom);
  910. eq(curR.top, cur1.top);
  911. eq(curR.bottom, cur1.bottom);
  912. cm.replaceRange("", Pos(0, 9), Pos(0));
  913. curR = cm.cursorCoords(Pos(0, 9));
  914. if (phantom) return;
  915. eq(curR.top, cur1.top);
  916. eq(curR.bottom, cur1.bottom);
  917. }, {value: "1 2 3 xxx 4", lineWrapping: true});
  918. testCM("changedInlineWidget", function(cm) {
  919. cm.setSize("10em");
  920. var w = document.createElement("span");
  921. w.innerHTML = "x";
  922. var m = cm.markText(Pos(0, 4), Pos(0, 5), {replacedWith: w});
  923. w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
  924. m.changed();
  925. var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
  926. is(hScroll.scrollWidth > hScroll.clientWidth);
  927. }, {value: "hello there"});
  928. testCM("changedBookmark", function(cm) {
  929. cm.setSize("10em");
  930. var w = document.createElement("span");
  931. w.innerHTML = "x";
  932. var m = cm.setBookmark(Pos(0, 4), {widget: w});
  933. w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed";
  934. m.changed();
  935. var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0];
  936. is(hScroll.scrollWidth > hScroll.clientWidth);
  937. }, {value: "abcdefg"});
  938. testCM("inlineWidget", function(cm) {
  939. var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")});
  940. cm.setCursor(0, 2);
  941. CodeMirror.commands.goLineDown(cm);
  942. eqPos(cm.getCursor(), Pos(1, 4));
  943. cm.setCursor(0, 2);
  944. cm.replaceSelection("hi");
  945. eqPos(w.find(), Pos(0, 2));
  946. cm.setCursor(0, 1);
  947. cm.replaceSelection("ay");
  948. eqPos(w.find(), Pos(0, 4));
  949. eq(cm.getLine(0), "uayuhiuu");
  950. }, {value: "uuuu\nuuuuuu"});
  951. testCM("wrappingAndResizing", function(cm) {
  952. cm.setSize(null, "auto");
  953. cm.setOption("lineWrapping", true);
  954. var wrap = cm.getWrapperElement(), h0 = wrap.offsetHeight;
  955. var doc = "xxx xxx xxx xxx xxx";
  956. cm.setValue(doc);
  957. for (var step = 10, w = cm.charCoords(Pos(0, 18), "div").right;; w += step) {
  958. cm.setSize(w);
  959. if (wrap.offsetHeight <= h0 * (opera_lt10 ? 1.2 : 1.5)) {
  960. if (step == 10) { w -= 10; step = 1; }
  961. else break;
  962. }
  963. }
  964. // Ensure that putting the cursor at the end of the maximally long
  965. // line doesn't cause wrapping to happen.
  966. cm.setCursor(Pos(0, doc.length));
  967. eq(wrap.offsetHeight, h0);
  968. cm.replaceSelection("x");
  969. is(wrap.offsetHeight > h0, "wrapping happens");
  970. // Now add a max-height and, in a document consisting of
  971. // almost-wrapped lines, go over it so that a scrollbar appears.
  972. cm.setValue(doc + "\n" + doc + "\n");
  973. cm.getScrollerElement().style.maxHeight = "100px";
  974. 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));
  975. forEach([Pos(0, doc.length), Pos(0, doc.length - 1),
  976. Pos(0, 0), Pos(1, doc.length), Pos(1, doc.length - 1)],
  977. function(pos) {
  978. var coords = cm.charCoords(pos);
  979. eqPos(pos, cm.coordsChar({left: coords.left + 2, top: coords.top + 5}));
  980. });
  981. }, null, ie_lt8);
  982. testCM("measureEndOfLine", function(cm) {
  983. cm.setSize(null, "auto");
  984. var inner = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild;
  985. var lh = inner.offsetHeight;
  986. for (var step = 10, w = cm.charCoords(Pos(0, 7), "div").right;; w += step) {
  987. cm.setSize(w);
  988. if (inner.offsetHeight < 2.5 * lh) {
  989. if (step == 10) { w -= 10; step = 1; }
  990. else break;
  991. }
  992. }
  993. cm.setValue(cm.getValue() + "\n\n");
  994. var endPos = cm.charCoords(Pos(0, 18), "local");
  995. is(endPos.top > lh * .8, "not at top");
  996. is(endPos.left > w - 20, "not at right");
  997. endPos = cm.charCoords(Pos(0, 18));
  998. eqPos(cm.coordsChar({left: endPos.left, top: endPos.top + 5}), Pos(0, 18));
  999. }, {mode: "text/html", value: "<!-- foo barrr -->", lineWrapping: true}, ie_lt8 || opera_lt10);
  1000. testCM("scrollVerticallyAndHorizontally", function(cm) {
  1001. cm.setSize(100, 100);
  1002. addDoc(cm, 40, 40);
  1003. cm.setCursor(39);
  1004. var wrap = cm.getWrapperElement(), bar = byClassName(wrap, "CodeMirror-vscrollbar")[0];
  1005. is(bar.offsetHeight < wrap.offsetHeight, "vertical scrollbar limited by horizontal one");
  1006. var cursorBox = byClassName(wrap, "CodeMirror-cursor")[0].getBoundingClientRect();
  1007. var editorBox = wrap.getBoundingClientRect();
  1008. is(cursorBox.bottom < editorBox.top + cm.getScrollerElement().clientHeight,
  1009. "bottom line visible");
  1010. }, {lineNumbers: true});
  1011. testCM("moveVstuck", function(cm) {
  1012. var lines = byClassName(cm.getWrapperElement(), "CodeMirror-lines")[0].firstChild, h0 = lines.offsetHeight;
  1013. var val = "fooooooooooooooooooooooooo baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\n";
  1014. cm.setValue(val);
  1015. for (var w = cm.charCoords(Pos(0, 26), "div").right * 2.8;; w += 5) {
  1016. cm.setSize(w);
  1017. if (lines.offsetHeight <= 3.5 * h0) break;
  1018. }
  1019. cm.setCursor(Pos(0, val.length - 1));
  1020. cm.moveV(-1, "line");
  1021. eqPos(cm.getCursor(), Pos(0, 26));
  1022. }, {lineWrapping: true}, ie_lt8 || opera_lt10);
  1023. testCM("collapseOnMove", function(cm) {
  1024. cm.setSelection(Pos(0, 1), Pos(2, 4));
  1025. cm.execCommand("goLineUp");
  1026. is(!cm.somethingSelected());
  1027. eqPos(cm.getCursor(), Pos(0, 1));
  1028. cm.setSelection(Pos(0, 1), Pos(2, 4));
  1029. cm.execCommand("goPageDown");
  1030. is(!cm.somethingSelected());
  1031. eqPos(cm.getCursor(), Pos(2, 4));
  1032. cm.execCommand("goLineUp");
  1033. cm.execCommand("goLineUp");
  1034. eqPos(cm.getCursor(), Pos(0, 4));
  1035. cm.setSelection(Pos(0, 1), Pos(2, 4));
  1036. cm.execCommand("goCharLeft");
  1037. is(!cm.somethingSelected());
  1038. eqPos(cm.getCursor(), Pos(0, 1));
  1039. }, {value: "aaaaa\nb\nccccc"});
  1040. testCM("clickTab", function(cm) {
  1041. var p0 = cm.charCoords(Pos(0, 0));
  1042. eqPos(cm.coordsChar({left: p0.left + 5, top: p0.top + 5}), Pos(0, 0));
  1043. eqPos(cm.coordsChar({left: p0.right - 5, top: p0.top + 5}), Pos(0, 1));
  1044. }, {value: "\t\n\n", lineWrapping: true, tabSize: 8});
  1045. testCM("verticalScroll", function(cm) {
  1046. cm.setSize(100, 200);
  1047. cm.setValue("foo\nbar\nbaz\n");
  1048. var sc = cm.getScrollerElement(), baseWidth = sc.scrollWidth;
  1049. cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
  1050. is(sc.scrollWidth > baseWidth, "scrollbar present");
  1051. cm.replaceRange("foo", Pos(0, 0), Pos(0));
  1052. if (!phantom) eq(sc.scrollWidth, baseWidth, "scrollbar gone");
  1053. cm.replaceRange("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah", Pos(0, 0), Pos(0));
  1054. cm.replaceRange("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbh", Pos(1, 0), Pos(1));
  1055. is(sc.scrollWidth > baseWidth, "present again");
  1056. var curWidth = sc.scrollWidth;
  1057. cm.replaceRange("foo", Pos(0, 0), Pos(0));
  1058. is(sc.scrollWidth < curWidth, "scrollbar smaller");
  1059. is(sc.scrollWidth > baseWidth, "but still present");
  1060. });
  1061. testCM("extraKeys", function(cm) {
  1062. var outcome;
  1063. function fakeKey(expected, code, props) {
  1064. if (typeof code == "string") code = code.charCodeAt(0);
  1065. var e = {type: "keydown", keyCode: code, preventDefault: function(){}, stopPropagation: function(){}};
  1066. if (props) for (var n in props) e[n] = props[n];
  1067. outcome = null;
  1068. cm.triggerOnKeyDown(e);
  1069. eq(outcome, expected);
  1070. }
  1071. CodeMirror.commands.testCommand = function() {outcome = "tc";};
  1072. CodeMirror.commands.goTestCommand = function() {outcome = "gtc";};
  1073. cm.setOption("extraKeys", {"Shift-X": function() {outcome = "sx";},
  1074. "X": function() {outcome = "x";},
  1075. "Ctrl-Alt-U": function() {outcome = "cau";},
  1076. "End": "testCommand",
  1077. "Home": "goTestCommand",
  1078. "Tab": false});
  1079. fakeKey(null, "U");
  1080. fakeKey("cau", "U", {ctrlKey: true, altKey: true});
  1081. fakeKey(null, "U", {shiftKey: true, ctrlKey: true, altKey: true});
  1082. fakeKey("x", "X");
  1083. fakeKey("sx", "X", {shiftKey: true});
  1084. fakeKey("tc", 35);
  1085. fakeKey(null, 35, {shiftKey: true});
  1086. fakeKey("gtc", 36);
  1087. fakeKey("gtc", 36, {shiftKey: true});
  1088. fakeKey(null, 9);
  1089. }, null, window.opera && mac);
  1090. testCM("wordMovementCommands", function(cm) {
  1091. cm.execCommand("goWordLeft");
  1092. eqPos(cm.getCursor(), Pos(0, 0));
  1093. cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
  1094. eqPos(cm.getCursor(), Pos(0, 7));
  1095. cm.execCommand("goWordLeft");
  1096. eqPos(cm.getCursor(), Pos(0, 5));
  1097. cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
  1098. eqPos(cm.getCursor(), Pos(0, 12));
  1099. cm.execCommand("goWordLeft");
  1100. eqPos(cm.getCursor(), Pos(0, 9));
  1101. cm.execCommand("goWordRight"); cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
  1102. eqPos(cm.getCursor(), Pos(0, 24));
  1103. cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
  1104. eqPos(cm.getCursor(), Pos(1, 9));
  1105. cm.execCommand("goWordRight");
  1106. eqPos(cm.getCursor(), Pos(1, 13));
  1107. cm.execCommand("goWordRight"); cm.execCommand("goWordRight");
  1108. eqPos(cm.getCursor(), Pos(2, 0));
  1109. }, {value: "this is (the) firstline.\na foo12\u00e9\u00f8\u00d7bar\n"});
  1110. testCM("groupMovementCommands", function(cm) {
  1111. cm.execCommand("goGroupLeft");
  1112. eqPos(cm.getCursor(), Pos(0, 0));
  1113. cm.execCommand("goGroupRight");
  1114. eqPos(cm.getCursor(), Pos(0, 4));
  1115. cm.execCommand("goGroupRight");
  1116. eqPos(cm.getCursor(), Pos(0, 7));
  1117. cm.execCommand("goGroupRight");
  1118. eqPos(cm.getCursor(), Pos(0, 10));
  1119. cm.execCommand("goGroupLeft");
  1120. eqPos(cm.getCursor(), Pos(0, 7));
  1121. cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
  1122. eqPos(cm.getCursor(), Pos(0, 15));
  1123. cm.setCursor(Pos(0, 17));
  1124. cm.execCommand("goGroupLeft");
  1125. eqPos(cm.getCursor(), Pos(0, 16));
  1126. cm.execCommand("goGroupLeft");
  1127. eqPos(cm.getCursor(), Pos(0, 14));
  1128. cm.execCommand("goGroupRight"); cm.execCommand("goGroupRight");
  1129. eqPos(cm.getCursor(), Pos(0, 20));
  1130. cm.execCommand("goGroupRight");
  1131. eqPos(cm.getCursor(), Pos(1, 0));
  1132. cm.execCommand("goGroupRight");
  1133. eqPos(cm.getCursor(), Pos(1, 2));
  1134. cm.execCommand("goGroupRight");
  1135. eqPos(cm.getCursor(), Pos(1, 5));
  1136. cm.execCommand("goGroupLeft"); cm.execCommand("goGroupLeft");
  1137. eqPos(cm.getCursor(), Pos(1, 0));
  1138. cm.execCommand("goGroupLeft");
  1139. eqPos(cm.getCursor(), Pos(0, 20));
  1140. cm.execCommand("goGroupLeft");
  1141. eqPos(cm.getCursor(), Pos(0, 16));
  1142. }, {value: "booo ba---quux. ffff\n abc d"});
  1143. testCM("groupsAndWhitespace", function(cm) {
  1144. var positions = [Pos(0, 0), Pos(0, 2), Pos(0, 5), Pos(0, 9), Pos(0, 11),
  1145. Pos(1, 0), Pos(1, 2), Pos(1, 5)];
  1146. for (var i = 1; i < positions.length; i++) {
  1147. cm.execCommand("goGroupRight");
  1148. eqPos(cm.getCursor(), positions[i]);
  1149. }
  1150. for (var i = positions.length - 2; i >= 0; i--) {
  1151. cm.execCommand("goGroupLeft");
  1152. eqPos(cm.getCursor(), i == 2 ? Pos(0, 6) : positions[i]);
  1153. }
  1154. }, {value: " foo +++ \n bar"});
  1155. testCM("charMovementCommands", function(cm) {
  1156. cm.execCommand("goCharLeft"); cm.execCommand("goColumnLeft");
  1157. eqPos(cm.getCursor(), Pos(0, 0));
  1158. cm.execCommand("goCharRight"); cm.execCommand("goCharRight");
  1159. eqPos(cm.getCursor(), Pos(0, 2));
  1160. cm.setCursor(Pos(1, 0));
  1161. cm.execCommand("goColumnLeft");
  1162. eqPos(cm.getCursor(), Pos(1, 0));
  1163. cm.execCommand("goCharLeft");
  1164. eqPos(cm.getCursor(), Pos(0, 5));
  1165. cm.execCommand("goColumnRight");
  1166. eqPos(cm.getCursor(), Pos(0, 5));
  1167. cm.execCommand("goCharRight");
  1168. eqPos(cm.getCursor(), Pos(1, 0));
  1169. cm.execCommand("goLineEnd");
  1170. eqPos(cm.getCursor(), Pos(1, 5));
  1171. cm.execCommand("goLineStartSmart");
  1172. eqPos(cm.getCursor(), Pos(1, 1));
  1173. cm.execCommand("goLineStartSmart");
  1174. eqPos(cm.getCursor(), Pos(1, 0));
  1175. cm.setCursor(Pos(2, 0));
  1176. cm.execCommand("goCharRight"); cm.execCommand("goColumnRight");
  1177. eqPos(cm.getCursor(), Pos(2, 0));
  1178. }, {value: "line1\n ine2\n"});
  1179. testCM("verticalMovementCommands", function(cm) {
  1180. cm.execCommand("goLineUp");
  1181. eqPos(cm.getCursor(), Pos(0, 0));
  1182. cm.execCommand("goLineDown");
  1183. if (!phantom) // This fails in PhantomJS, though not in a real Webkit
  1184. eqPos(cm.getCursor(), Pos(1, 0));
  1185. cm.setCursor(Pos(1, 12));
  1186. cm.execCommand("goLineDown");
  1187. eqPos(cm.getCursor(), Pos(2, 5));
  1188. cm.execCommand("goLineDown");
  1189. eqPos(cm.getCursor(), Pos(3, 0));
  1190. cm.execCommand("goLineUp");
  1191. eqPos(cm.getCursor(), Pos(2, 5));
  1192. cm.execCommand("goLineUp");
  1193. eqPos(cm.getCursor(), Pos(1, 12));
  1194. cm.execCommand("goPageDown");
  1195. eqPos(cm.getCursor(), Pos(5, 0));
  1196. cm.execCommand("goPageDown"); cm.execCommand("goLineDown");
  1197. eqPos(cm.getCursor(), Pos(5, 0));
  1198. cm.execCommand("goPageUp");
  1199. eqPos(cm.getCursor(), Pos(0, 0));
  1200. }, {value: "line1\nlong long line2\nline3\n\nline5\n"});
  1201. testCM("verticalMovementCommandsWrapping", function(cm) {
  1202. cm.setSize(120);
  1203. cm.setCursor(Pos(0, 5));
  1204. cm.execCommand("goLineDown");
  1205. eq(cm.getCursor().line, 0);
  1206. is(cm.getCursor().ch > 5, "moved beyond wrap");
  1207. for (var i = 0; ; ++i) {
  1208. is(i < 20, "no endless loop");
  1209. cm.execCommand("goLineDown");
  1210. var cur = cm.getCursor();
  1211. if (cur.line == 1) eq(cur.ch, 5);
  1212. if (cur.line == 2) { eq(cur.ch, 1); break; }
  1213. }
  1214. }, {value: "a very long line that wraps around somehow so that we can test cursor movement\nshortone\nk",
  1215. lineWrapping: true});
  1216. testCM("rtlMovement", function(cm) {
  1217. forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج",
  1218. "خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", "خ ة ق",
  1219. "<img src=\"/בדיקה3.jpg\">"], function(line) {
  1220. var inv = line.charAt(0) == "خ";
  1221. cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart");
  1222. var cursors = byClassName(cm.getWrapperElement(), "CodeMirror-cursors")[0];
  1223. var cursor = cursors.firstChild;
  1224. var prevX = cursor.offsetLeft, prevY = cursor.offsetTop;
  1225. for (var i = 0; i <= line.length; ++i) {
  1226. cm.execCommand("goCharRight");
  1227. cursor = cursors.firstChild;
  1228. if (i == line.length) is(cursor.offsetTop > prevY, "next line");
  1229. else is(cursor.offsetLeft > prevX, "moved right");
  1230. prevX = cursor.offsetLeft; prevY = cursor.offsetTop;
  1231. }
  1232. cm.setCursor(0, 0); cm.execCommand(inv ? "goLineStart" : "goLineEnd");
  1233. prevX = cursors.firstChild.offsetLeft;
  1234. for (var i = 0; i < line.length; ++i) {
  1235. cm.execCommand("goCharLeft");
  1236. cursor = cursors.firstChild;
  1237. is(cursor.offsetLeft < prevX, "moved left");
  1238. prevX = cursor.offsetLeft;
  1239. }
  1240. });
  1241. }, null, ie_lt9);
  1242. // Verify that updating a line clears its bidi ordering
  1243. testCM("bidiUpdate", function(cm) {
  1244. cm.setCursor(Pos(0, 2));
  1245. cm.replaceSelection("خحج", "start");
  1246. cm.execCommand("goCharRight");
  1247. eqPos(cm.getCursor(), Pos(0, 4));
  1248. }, {value: "abcd\n"});
  1249. testCM("movebyTextUnit", function(cm) {
  1250. cm.setValue("בְּרֵאשִ\nééé́\n");
  1251. cm.execCommand("goLineEnd");
  1252. for (var i = 0; i < 4; ++i) cm.execCommand("goCharRight");
  1253. eqPos(cm.getCursor(), Pos(0, 0));
  1254. cm.execCommand("goCharRight");
  1255. eqPos(cm.getCursor(), Pos(1, 0));
  1256. cm.execCommand("goCharRight");
  1257. cm.execCommand("goCharRight");
  1258. eqPos(cm.getCursor(), Pos(1, 4));
  1259. cm.execCommand("goCharRight");
  1260. eqPos(cm.getCursor(), Pos(1, 7));
  1261. });
  1262. testCM("lineChangeEvents", function(cm) {
  1263. addDoc(cm, 3, 5);
  1264. var log = [], want = ["ch 0", "ch 1", "del 2", "ch 0", "ch 0", "del 1", "del 3", "del 4"];
  1265. for (var i = 0; i < 5; ++i) {
  1266. CodeMirror.on(cm.getLineHandle(i), "delete", function(i) {
  1267. return function() {log.push("del " + i);};
  1268. }(i));
  1269. CodeMirror.on(cm.getLineHandle(i), "change", function(i) {
  1270. return function() {log.push("ch " + i);};
  1271. }(i));
  1272. }
  1273. cm.replaceRange("x", Pos(0, 1));
  1274. cm.replaceRange("xy", Pos(1, 1), Pos(2));
  1275. cm.replaceRange("foo\nbar", Pos(0, 1));
  1276. cm.replaceRange("", Pos(0, 0), Pos(cm.lineCount()));
  1277. eq(log.length, want.length, "same length");
  1278. for (var i = 0; i < log.length; ++i)
  1279. eq(log[i], want[i]);
  1280. });
  1281. testCM("scrollEntirelyToRight", function(cm) {
  1282. if (phantom) return;
  1283. addDoc(cm, 500, 2);
  1284. cm.setCursor(Pos(0, 500));
  1285. var wrap = cm.getWrapperElement(), cur = byClassName(wrap, "CodeMirror-cursor")[0];
  1286. is(wrap.getBoundingClientRect().right > cur.getBoundingClientRect().left);
  1287. });
  1288. testCM("lineWidgets", function(cm) {
  1289. addDoc(cm, 500, 3);
  1290. var last = cm.charCoords(Pos(2, 0));
  1291. var node = document.createElement("div");
  1292. node.innerHTML = "hi";
  1293. var widget = cm.addLineWidget(1, node);
  1294. is(last.top < cm.charCoords(Pos(2, 0)).top, "took up space");
  1295. cm.setCursor(Pos(1, 1));
  1296. cm.execCommand("goLineDown");
  1297. eqPos(cm.getCursor(), Pos(2, 1));
  1298. cm.execCommand("goLineUp");
  1299. eqPos(cm.getCursor(), Pos(1, 1));
  1300. });
  1301. testCM("lineWidgetFocus", function(cm) {
  1302. var place = document.getElementById("testground");
  1303. place.className = "offscreen";
  1304. try {
  1305. addDoc(cm, 500, 10);
  1306. var node = document.createElement("input");
  1307. var widget = cm.addLineWidget(1, node);
  1308. node.focus();
  1309. eq(document.activeElement, node);
  1310. cm.replaceRange("new stuff", Pos(1, 0));
  1311. eq(document.activeElement, node);
  1312. } finally {
  1313. place.className = "";
  1314. }
  1315. });
  1316. testCM("lineWidgetCautiousRedraw", function(cm) {
  1317. var node = document.createElement("div");
  1318. node.innerHTML = "hahah";
  1319. var w = cm.addLineWidget(0, node);
  1320. var redrawn = false;
  1321. w.on("redraw", function() { redrawn = true; });
  1322. cm.replaceSelection("0");
  1323. is(!redrawn);
  1324. }, {value: "123\n456"});
  1325. var knownScrollbarWidth;
  1326. function scrollbarWidth(measure) {
  1327. if (knownScrollbarWidth != null) return knownScrollbarWidth;
  1328. var div = document.createElement('div');
  1329. div.style.cssText = "width: 50px; height: 50px; overflow-x: scroll";
  1330. document.body.appendChild(div);
  1331. knownScrollbarWidth = div.offsetHeight - div.clientHeight;
  1332. document.body.removeChild(div);
  1333. return knownScrollbarWidth || 0;
  1334. }
  1335. testCM("lineWidgetChanged", function(cm) {
  1336. addDoc(cm, 2, 300);
  1337. var halfScrollbarWidth = scrollbarWidth(cm.display.measure)/2;
  1338. cm.setOption('lineNumbers', true);
  1339. cm.setSize(600, cm.defaultTextHeight() * 50);
  1340. cm.scrollTo(null, cm.heightAtLine(125, "local"));
  1341. var expectedWidgetHeight = 60;
  1342. var expectedLinesInWidget = 3;
  1343. function w() {
  1344. var node = document.createElement("div");
  1345. // we use these children with just under half width of the line to check measurements are made with correct width
  1346. // when placed in the measure div.
  1347. // 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.
  1348. // If the widget is measured at a width much wider than it is displayed at, the overHalf children will combine and break the test.
  1349. // Note that this test only checks widgets where coverGutter is true, because these require extra styling to get the width right.
  1350. // It may also be worthwhile to check this for non-coverGutter widgets.
  1351. // Visually:
  1352. // Good:
  1353. // | ------------- display width ------------- |
  1354. // | ------- widget-width when measured ------ |
  1355. // | | -- under-half -- | | -- under-half -- | |
  1356. // | | --- over-half --- | |
  1357. // | | --- over-half --- | |
  1358. // Height: measured as 3 lines, same as it will be when actually displayed
  1359. // Bad (too narrow):
  1360. // | ------------- display width ------------- |
  1361. // | ------ widget-width when measured ----- | < -- uh oh
  1362. // | | -- under-half -- | |
  1363. // | | -- under-half -- | | < -- when measured, shoved to next line
  1364. // | | --- over-half --- | |
  1365. // | | --- over-half --- | |
  1366. // Height: measured as 4 lines, more than expected . Will be displayed as 3 lines!
  1367. // Bad (too wide):
  1368. // | ------------- display width ------------- |
  1369. // | -------- widget-width when measured ------- | < -- uh oh
  1370. // | | -- under-half -- | | -- under-half -- | |
  1371. // | | --- over-half --- | | --- over-half --- | | < -- when measured, combined on one line
  1372. // Height: measured as 2 lines, less than expected. Will be displayed as 3 lines!
  1373. var barelyUnderHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(285 - halfScrollbarWidth)+'px;"></div>';
  1374. var barelyOverHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(305 - halfScrollbarWidth)+'px;"></div>';
  1375. node.innerHTML = new Array(3).join(barelyUnderHalfWidthHtml) + new Array(3).join(barelyOverHalfWidthHtml);
  1376. node.style.cssText = "background: yellow;font-size:0;line-height: " + (expectedWidgetHeight/expectedLinesInWidget) + "px;";
  1377. return node;
  1378. }
  1379. var info0 = cm.getScrollInfo();
  1380. var w0 = cm.addLineWidget(0, w(), { coverGutter: true });
  1381. var w150 = cm.addLineWidget(150, w(), { coverGutter: true });
  1382. var w300 = cm.addLineWidget(300, w(), { coverGutter: true });
  1383. var info1 = cm.getScrollInfo();
  1384. eq(info0.height + (3 * expectedWidgetHeight), info1.height);
  1385. eq(info0.top + expectedWidgetHeight, info1.top);
  1386. expectedWidgetHeight = 12;
  1387. w0.node.style.lineHeight = w150.node.style.lineHeight = w300.node.style.lineHeight = (expectedWidgetHeight/expectedLinesInWidget) + "px";
  1388. w0.changed(); w150.changed(); w300.changed();
  1389. var info2 = cm.getScrollInfo();
  1390. eq(info0.height + (3 * expectedWidgetHeight), info2.height);
  1391. eq(info0.top + expectedWidgetHeight, info2.top);
  1392. });
  1393. testCM("getLineNumber", function(cm) {
  1394. addDoc(cm, 2, 20);
  1395. var h1 = cm.getLineHandle(1);
  1396. eq(cm.getLineNumber(h1), 1);
  1397. cm.replaceRange("hi\nbye\n", Pos(0, 0));
  1398. eq(cm.getLineNumber(h1), 3);
  1399. cm.setValue("");
  1400. eq(cm.getLineNumber(h1), null);
  1401. });
  1402. testCM("jumpTheGap", function(cm) {
  1403. if (phantom) return;
  1404. var longLine = "abcdef ghiklmnop qrstuvw xyz ";
  1405. longLine += longLine; longLine += longLine; longLine += longLine;
  1406. cm.replaceRange(longLine, Pos(2, 0), Pos(2));
  1407. cm.setSize("200px", null);
  1408. cm.getWrapperElement().style.lineHeight = 2;
  1409. cm.refresh();
  1410. cm.setCursor(Pos(0, 1));
  1411. cm.execCommand("goLineDown");
  1412. eqPos(cm.getCursor(), Pos(1, 1));
  1413. cm.execCommand("goLineDown");
  1414. eqPos(cm.getCursor(), Pos(2, 1));
  1415. cm.execCommand("goLineDown");
  1416. eq(cm.getCursor().line, 2);
  1417. is(cm.getCursor().ch > 1);
  1418. cm.execCommand("goLineUp");
  1419. eqPos(cm.getCursor(), Pos(2, 1));
  1420. cm.execCommand("goLineUp");
  1421. eqPos(cm.getCursor(), Pos(1, 1));
  1422. var node = document.createElement("div");
  1423. node.innerHTML = "hi"; node.style.height = "30px";
  1424. cm.addLineWidget(0, node);
  1425. cm.addLineWidget(1, node.cloneNode(true), {above: true});
  1426. cm.setCursor(Pos(0, 2));
  1427. cm.execCommand("goLineDown");
  1428. eqPos(cm.getCursor(), Pos(1, 2));
  1429. cm.execCommand("goLineUp");
  1430. eqPos(cm.getCursor(), Pos(0, 2));
  1431. }, {lineWrapping: true, value: "abc\ndef\nghi\njkl\n"});
  1432. testCM("addLineClass", function(cm) {
  1433. function cls(line, text, bg, wrap) {
  1434. var i = cm.lineInfo(line);
  1435. eq(i.textClass, text);
  1436. eq(i.bgClass, bg);
  1437. eq(i.wrapClass, wrap);
  1438. }
  1439. cm.addLineClass(0, "text", "foo");
  1440. cm.addLineClass(0, "text", "bar");
  1441. cm.addLineClass(1, "background", "baz");
  1442. cm.addLineClass(1, "wrap", "foo");
  1443. cls(0, "foo bar", null, null);
  1444. cls(1, null, "baz", "foo");
  1445. var lines = cm.display.lineDiv;
  1446. eq(byClassName(lines, "foo").length, 2);
  1447. eq(byClassName(lines, "bar").length, 1);
  1448. eq(byClassName(lines, "baz").length, 1);
  1449. cm.removeLineClass(0, "text", "foo");
  1450. cls(0, "bar", null, null);
  1451. cm.removeLineClass(0, "text", "foo");
  1452. cls(0, "bar", null, null);
  1453. cm.removeLineClass(0, "text", "bar");
  1454. cls(0, null, null, null);
  1455. cm.addLineClass(1, "wrap", "quux");
  1456. cls(1, null, "baz", "foo quux");
  1457. cm.removeLineClass(1, "wrap");
  1458. cls(1, null, "baz", null);
  1459. }, {value: "hohoho\n"});
  1460. testCM("atomicMarker", function(cm) {
  1461. addDoc(cm, 10, 10);
  1462. function atom(ll, cl, lr, cr, li, ri) {
  1463. return cm.markText(Pos(ll, cl), Pos(lr, cr),
  1464. {atomic: true, inclusiveLeft: li, inclusiveRight: ri});
  1465. }
  1466. var m = atom(0, 1, 0, 5);
  1467. cm.setCursor(Pos(0, 1));
  1468. cm.execCommand("goCharRight");
  1469. eqPos(cm.getCursor(), Pos(0, 5));
  1470. cm.execCommand("goCharLeft");
  1471. eqPos(cm.getCursor(), Pos(0, 1));
  1472. m.clear();
  1473. m = atom(0, 0, 0, 5, true);
  1474. eqPos(cm.getCursor(), Pos(0, 5), "pushed out");
  1475. cm.execCommand("goCharLeft");
  1476. eqPos(cm.getCursor(), Pos(0, 5));
  1477. m.clear();
  1478. m = atom(8, 4, 9, 10, false, true);
  1479. cm.setCursor(Pos(9, 8));
  1480. eqPos(cm.getCursor(), Pos(8, 4), "set");
  1481. cm.execCommand("goCharRight");
  1482. eqPos(cm.getCursor(), Pos(8, 4), "char right");
  1483. cm.execCommand("goLineDown");
  1484. eqPos(cm.getCursor(), Pos(8, 4), "line down");
  1485. cm.execCommand("goCharLeft");
  1486. eqPos(cm.getCursor(), Pos(8, 3));
  1487. m.clear();
  1488. m = atom(1, 1, 3, 8);
  1489. cm.setCursor(Pos(0, 0));
  1490. cm.setCursor(Pos(2, 0));
  1491. eqPos(cm.getCursor(), Pos(3, 8));
  1492. cm.execCommand("goCharLeft");
  1493. eqPos(cm.getCursor(), Pos(1, 1));
  1494. cm.execCommand("goCharRight");
  1495. eqPos(cm.getCursor(), Pos(3, 8));
  1496. cm.execCommand("goLineUp");
  1497. eqPos(cm.getCursor(), Pos(1, 1));
  1498. cm.execCommand("goLineDown");
  1499. eqPos(cm.getCursor(), Pos(3, 8));
  1500. cm.execCommand("delCharBefore");
  1501. eq(cm.getValue().length, 80, "del chunk");
  1502. m = atom(3, 0, 5, 5);
  1503. cm.setCursor(Pos(3, 0));
  1504. cm.execCommand("delWordAfter");
  1505. eq(cm.getValue().length, 53, "del chunk");
  1506. });
  1507. testCM("selectionBias", function(cm) {
  1508. cm.markText(Pos(0, 1), Pos(0, 3), {atomic: true});
  1509. cm.setCursor(Pos(0, 2));
  1510. eqPos(cm.getCursor(), Pos(0, 3));
  1511. cm.setCursor(Pos(0, 2));
  1512. eqPos(cm.getCursor(), Pos(0, 1));
  1513. cm.setCursor(Pos(0, 2), null, {bias: -1});
  1514. eqPos(cm.getCursor(), Pos(0, 1));
  1515. cm.setCursor(Pos(0, 4));
  1516. cm.setCursor(Pos(0, 2), null, {bias: 1});
  1517. eqPos(cm.getCursor(), Pos(0, 3));
  1518. }, {value: "12345"});
  1519. testCM("selectionHomeEnd", function(cm) {
  1520. cm.markText(Pos(1, 0), Pos(1, 1), {atomic: true, inclusiveLeft: true});
  1521. cm.markText(Pos(1, 3), Pos(1, 4), {atomic: true, inclusiveRight: true});
  1522. cm.setCursor(Pos(1, 2));
  1523. cm.execCommand("goLineStart");
  1524. eqPos(cm.getCursor(), Pos(1, 1));
  1525. cm.execCommand("goLineEnd");
  1526. eqPos(cm.getCursor(), Pos(1, 3));
  1527. }, {value: "ab\ncdef\ngh"});
  1528. testCM("readOnlyMarker", function(cm) {
  1529. function mark(ll, cl, lr, cr, at) {
  1530. return cm.markText(Pos(ll, cl), Pos(lr, cr),
  1531. {readOnly: true, atomic: at});
  1532. }
  1533. var m = mark(0, 1, 0, 4);
  1534. cm.setCursor(Pos(0, 2));
  1535. cm.replaceSelection("hi", "end");
  1536. eqPos(cm.getCursor(), Pos(0, 2));
  1537. eq(cm.getLine(0), "abcde");
  1538. cm.execCommand("selectAll");
  1539. cm.replaceSelection("oops", "around");
  1540. eq(cm.getValue(), "oopsbcd");
  1541. cm.undo();
  1542. eqPos(m.find().from, Pos(0, 1));
  1543. eqPos(m.find().to, Pos(0, 4));
  1544. m.clear();
  1545. cm.setCursor(Pos(0, 2));
  1546. cm.replaceSelection("hi", "around");
  1547. eq(cm.getLine(0), "abhicde");
  1548. eqPos(cm.getCursor(), Pos(0, 4));
  1549. m = mark(0, 2, 2, 2, true);
  1550. cm.setSelection(Pos(1, 1), Pos(2, 4));
  1551. cm.replaceSelection("t", "end");
  1552. eqPos(cm.getCursor(), Pos(2, 3));
  1553. eq(cm.getLine(2), "klto");
  1554. cm.execCommand("goCharLeft");
  1555. cm.execCommand("goCharLeft");
  1556. eqPos(cm.getCursor(), Pos(0, 2));
  1557. cm.setSelection(Pos(0, 1), Pos(0, 3));
  1558. cm.replaceSelection("xx", "around");
  1559. eqPos(cm.getCursor(), Pos(0, 3));
  1560. eq(cm.getLine(0), "axxhicde");
  1561. }, {value: "abcde\nfghij\nklmno\n"});
  1562. testCM("dirtyBit", function(cm) {
  1563. eq(cm.isClean(), true);
  1564. cm.replaceSelection("boo", null, "test");
  1565. eq(cm.isClean(), false);
  1566. cm.undo();
  1567. eq(cm.isClean(), true);
  1568. cm.replaceSelection("boo", null, "test");
  1569. cm.replaceSelection("baz", null, "test");
  1570. cm.undo();
  1571. eq(cm.isClean(), false);
  1572. cm.markClean();
  1573. eq(cm.isClean(), true);
  1574. cm.undo();
  1575. eq(cm.isClean(), false);
  1576. cm.redo();
  1577. eq(cm.isClean(), true);
  1578. });
  1579. testCM("changeGeneration", function(cm) {
  1580. cm.replaceSelection("x");
  1581. var softGen = cm.changeGeneration();
  1582. cm.replaceSelection("x");
  1583. cm.undo();
  1584. eq(cm.getValue(), "");
  1585. is(!cm.isClean(softGen));
  1586. cm.replaceSelection("x");
  1587. var hardGen = cm.changeGeneration(true);
  1588. cm.replaceSelection("x");
  1589. cm.undo();
  1590. eq(cm.getValue(), "x");
  1591. is(cm.isClean(hardGen));
  1592. });
  1593. testCM("addKeyMap", function(cm) {
  1594. function sendKey(code) {
  1595. cm.triggerOnKeyDown({type: "keydown", keyCode: code,
  1596. preventDefault: function(){}, stopPropagation: function(){}});
  1597. }
  1598. sendKey(39);
  1599. eqPos(cm.getCursor(), Pos(0, 1));
  1600. var test = 0;
  1601. var map1 = {Right: function() { ++test; }}, map2 = {Right: function() { test += 10; }}
  1602. cm.addKeyMap(map1);
  1603. sendKey(39);
  1604. eqPos(cm.getCursor(), Pos(0, 1));
  1605. eq(test, 1);
  1606. cm.addKeyMap(map2, true);
  1607. sendKey(39);
  1608. eq(test, 2);
  1609. cm.removeKeyMap(map1);
  1610. sendKey(39);
  1611. eq(test, 12);
  1612. cm.removeKeyMap(map2);
  1613. sendKey(39);
  1614. eq(test, 12);
  1615. eqPos(cm.getCursor(), Pos(0, 2));
  1616. cm.addKeyMap({Right: function() { test = 55; }, name: "mymap"});
  1617. sendKey(39);
  1618. eq(test, 55);
  1619. cm.removeKeyMap("mymap");
  1620. sendKey(39);
  1621. eqPos(cm.getCursor(), Pos(0, 3));
  1622. }, {value: "abc"});
  1623. testCM("findPosH", function(cm) {
  1624. forEach([{from: Pos(0, 0), to: Pos(0, 1), by: 1},
  1625. {from: Pos(0, 0), to: Pos(0, 0), by: -1, hitSide: true},
  1626. {from: Pos(0, 0), to: Pos(0, 4), by: 1, unit: "word"},
  1627. {from: Pos(0, 0), to: Pos(0, 8), by: 2, unit: "word"},
  1628. {from: Pos(0, 0), to: Pos(2, 0), by: 20, unit: "word", hitSide: true},
  1629. {from: Pos(0, 7), to: Pos(0, 5), by: -1, unit: "word"},
  1630. {from: Pos(0, 4), to: Pos(0, 8), by: 1, unit: "word"},
  1631. {from: Pos(1, 0), to: Pos(1, 18), by: 3, unit: "word"},
  1632. {from: Pos(1, 22), to: Pos(1, 5), by: -3, unit: "word"},
  1633. {from: Pos(1, 15), to: Pos(1, 10), by: -5},
  1634. {from: Pos(1, 15), to: Pos(1, 10), by: -5, unit: "column"},
  1635. {from: Pos(1, 15), to: Pos(1, 0), by: -50, unit: "column", hitSide: true},
  1636. {from: Pos(1, 15), to: Pos(1, 24), by: 50, unit: "column", hitSide: true},
  1637. {from: Pos(1, 15), to: Pos(2, 0), by: 50, hitSide: true}], function(t) {
  1638. var r = cm.findPosH(t.from, t.by, t.unit || "char");
  1639. eqPos(r, t.to);
  1640. eq(!!r.hitSide, !!t.hitSide);
  1641. });
  1642. }, {value: "line one\nline two.something.other\n"});
  1643. testCM("beforeChange", function(cm) {
  1644. cm.on("beforeChange", function(cm, change) {
  1645. var text = [];
  1646. for (var i = 0; i < change.text.length; ++i)
  1647. text.push(change.text[i].replace(/\s/g, "_"));
  1648. change.update(null, null, text);
  1649. });
  1650. cm.setValue("hello, i am a\nnew document\n");
  1651. eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
  1652. CodeMirror.on(cm.getDoc(), "beforeChange", function(doc, change) {
  1653. if (change.from.line == 0) change.cancel();
  1654. });
  1655. cm.setValue("oops"); // Canceled
  1656. eq(cm.getValue(), "hello,_i_am_a\nnew_document\n");
  1657. cm.replaceRange("hey hey hey", Pos(1, 0), Pos(2, 0));
  1658. eq(cm.getValue(), "hello,_i_am_a\nhey_hey_hey");
  1659. }, {value: "abcdefghijk"});
  1660. testCM("beforeChangeUndo", function(cm) {
  1661. cm.replaceRange("hi", Pos(0, 0), Pos(0));
  1662. cm.replaceRange("bye", Pos(0, 0), Pos(0));
  1663. eq(cm.historySize().undo, 2);
  1664. cm.on("beforeChange", function(cm, change) {
  1665. is(!change.update);
  1666. change.cancel();
  1667. });
  1668. cm.undo();
  1669. eq(cm.historySize().undo, 0);
  1670. eq(cm.getValue(), "bye\ntwo");
  1671. }, {value: "one\ntwo"});
  1672. testCM("beforeSelectionChange", function(cm) {
  1673. function notAtEnd(cm, pos) {
  1674. var len = cm.getLine(pos.line).length;
  1675. if (!len || pos.ch == len) return Pos(pos.line, pos.ch - 1);
  1676. return pos;
  1677. }
  1678. cm.on("beforeSelectionChange", function(cm, obj) {
  1679. obj.update([{anchor: notAtEnd(cm, obj.ranges[0].anchor),
  1680. head: notAtEnd(cm, obj.ranges[0].head)}]);
  1681. });
  1682. addDoc(cm, 10, 10);
  1683. cm.execCommand("goLineEnd");
  1684. eqPos(cm.getCursor(), Pos(0, 9));
  1685. cm.execCommand("selectAll");
  1686. eqPos(cm.getCursor("start"), Pos(0, 0));
  1687. eqPos(cm.getCursor("end"), Pos(9, 9));
  1688. });
  1689. testCM("change_removedText", function(cm) {
  1690. cm.setValue("abc\ndef");
  1691. var removedText = [];
  1692. cm.on("change", function(cm, change) {
  1693. removedText.push(change.removed);
  1694. });
  1695. cm.operation(function() {
  1696. cm.replaceRange("xyz", Pos(0, 0), Pos(1,1));
  1697. cm.replaceRange("123", Pos(0,0));
  1698. });
  1699. eq(removedText.length, 2);
  1700. eq(removedText[0].join("\n"), "abc\nd");
  1701. eq(removedText[1].join("\n"), "");
  1702. var removedText = [];
  1703. cm.undo();
  1704. eq(removedText.length, 2);
  1705. eq(removedText[0].join("\n"), "123");
  1706. eq(removedText[1].join("\n"), "xyz");
  1707. var removedText = [];
  1708. cm.redo();
  1709. eq(removedText.length, 2);
  1710. eq(removedText[0].join("\n"), "abc\nd");
  1711. eq(removedText[1].join("\n"), "");
  1712. });
  1713. testCM("lineStyleFromMode", function(cm) {
  1714. CodeMirror.defineMode("test_mode", function() {
  1715. return {token: function(stream) {
  1716. if (stream.match(/^\[[^\]]*\]/)) return " line-brackets ";
  1717. if (stream.match(/^\([^\)]*\)/)) return " line-background-parens ";
  1718. if (stream.match(/^<[^>]*>/)) return " span line-line line-background-bg ";
  1719. stream.match(/^\s+|^\S+/);
  1720. }};
  1721. });
  1722. cm.setOption("mode", "test_mode");
  1723. var bracketElts = byClassName(cm.getWrapperElement(), "brackets");
  1724. eq(bracketElts.length, 1, "brackets count");
  1725. eq(bracketElts[0].nodeName, "PRE");
  1726. is(!/brackets.*brackets/.test(bracketElts[0].className));
  1727. var parenElts = byClassName(cm.getWrapperElement(), "parens");
  1728. eq(parenElts.length, 1, "parens count");
  1729. eq(parenElts[0].nodeName, "DIV");
  1730. is(!/parens.*parens/.test(parenElts[0].className));
  1731. eq(parenElts[0].parentElement.nodeName, "DIV");
  1732. eq(byClassName(cm.getWrapperElement(), "bg").length, 1);
  1733. eq(byClassName(cm.getWrapperElement(), "line").length, 1);
  1734. var spanElts = byClassName(cm.getWrapperElement(), "cm-span");
  1735. eq(spanElts.length, 2);
  1736. is(/^\s*cm-span\s*$/.test(spanElts[0].className));
  1737. }, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: <tag> <tag>"});
  1738. testCM("lineStyleFromBlankLine", function(cm) {
  1739. CodeMirror.defineMode("lineStyleFromBlankLine_mode", function() {
  1740. return {token: function(stream) { stream.skipToEnd(); return "comment"; },
  1741. blankLine: function() { return "line-blank"; }};
  1742. });
  1743. cm.setOption("mode", "lineStyleFromBlankLine_mode");
  1744. var blankElts = byClassName(cm.getWrapperElement(), "blank");
  1745. eq(blankElts.length, 1);
  1746. eq(blankElts[0].nodeName, "PRE");
  1747. cm.replaceRange("x", Pos(1, 0));
  1748. blankElts = byClassName(cm.getWrapperElement(), "blank");
  1749. eq(blankElts.length, 0);
  1750. }, {value: "foo\n\nbar"});
  1751. CodeMirror.registerHelper("xxx", "a", "A");
  1752. CodeMirror.registerHelper("xxx", "b", "B");
  1753. CodeMirror.defineMode("yyy", function() {
  1754. return {
  1755. token: function(stream) { stream.skipToEnd(); },
  1756. xxx: ["a", "b", "q"]
  1757. };
  1758. });
  1759. CodeMirror.registerGlobalHelper("xxx", "c", function(m) { return m.enableC; }, "C");
  1760. testCM("helpers", function(cm) {
  1761. cm.setOption("mode", "yyy");
  1762. eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "A/B");
  1763. cm.setOption("mode", {name: "yyy", modeProps: {xxx: "b", enableC: true}});
  1764. eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "B/C");
  1765. cm.setOption("mode", "javascript");
  1766. eq(cm.getHelpers(Pos(0, 0), "xxx").join("/"), "");
  1767. });
  1768. testCM("selectionHistory", function(cm) {
  1769. for (var i = 0; i < 3; i++) {
  1770. cm.setExtending(true);
  1771. cm.execCommand("goCharRight");
  1772. cm.setExtending(false);
  1773. cm.execCommand("goCharRight");
  1774. cm.execCommand("goCharRight");
  1775. }
  1776. cm.execCommand("undoSelection");
  1777. eq(cm.getSelection(), "c");
  1778. cm.execCommand("undoSelection");
  1779. eq(cm.getSelection(), "");
  1780. eqPos(cm.getCursor(), Pos(0, 4));
  1781. cm.execCommand("undoSelection");
  1782. eq(cm.getSelection(), "b");
  1783. cm.execCommand("redoSelection");
  1784. eq(cm.getSelection(), "");
  1785. eqPos(cm.getCursor(), Pos(0, 4));
  1786. cm.execCommand("redoSelection");
  1787. eq(cm.getSelection(), "c");
  1788. cm.execCommand("redoSelection");
  1789. eq(cm.getSelection(), "");
  1790. eqPos(cm.getCursor(), Pos(0, 6));
  1791. }, {value: "a b c d"});
  1792. testCM("selectionChangeReducesRedo", function(cm) {
  1793. cm.replaceSelection("X");
  1794. cm.execCommand("goCharRight");
  1795. cm.undoSelection();
  1796. cm.execCommand("selectAll");
  1797. cm.undoSelection();
  1798. eq(cm.getValue(), "Xabc");
  1799. eqPos(cm.getCursor(), Pos(0, 1));
  1800. cm.undoSelection();
  1801. eq(cm.getValue(), "abc");
  1802. }, {value: "abc"});
  1803. testCM("selectionHistoryNonOverlapping", function(cm) {
  1804. cm.setSelection(Pos(0, 0), Pos(0, 1));
  1805. cm.setSelection(Pos(0, 2), Pos(0, 3));
  1806. cm.execCommand("undoSelection");
  1807. eqPos(cm.getCursor("anchor"), Pos(0, 0));
  1808. eqPos(cm.getCursor("head"), Pos(0, 1));
  1809. }, {value: "1234"});
  1810. testCM("cursorMotionSplitsHistory", function(cm) {
  1811. cm.replaceSelection("a");
  1812. cm.execCommand("goCharRight");
  1813. cm.replaceSelection("b");
  1814. cm.replaceSelection("c");
  1815. cm.undo();
  1816. eq(cm.getValue(), "a1234");
  1817. eqPos(cm.getCursor(), Pos(0, 2));
  1818. cm.undo();
  1819. eq(cm.getValue(), "1234");
  1820. eqPos(cm.getCursor(), Pos(0, 0));
  1821. }, {value: "1234"});
  1822. testCM("selChangeInOperationDoesNotSplit", function(cm) {
  1823. for (var i = 0; i < 4; i++) {
  1824. cm.operation(function() {
  1825. cm.replaceSelection("x");
  1826. cm.setCursor(Pos(0, cm.getCursor().ch - 1));
  1827. });
  1828. }
  1829. eqPos(cm.getCursor(), Pos(0, 0));
  1830. eq(cm.getValue(), "xxxxa");
  1831. cm.undo();
  1832. eq(cm.getValue(), "a");
  1833. }, {value: "a"});
  1834. testCM("alwaysMergeSelEventWithChangeOrigin", function(cm) {
  1835. cm.replaceSelection("U", null, "foo");
  1836. cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "foo"});
  1837. cm.undoSelection();
  1838. eq(cm.getValue(), "a");
  1839. cm.replaceSelection("V", null, "foo");
  1840. cm.setSelection(Pos(0, 0), Pos(0, 1), {origin: "bar"});
  1841. cm.undoSelection();
  1842. eq(cm.getValue(), "Va");
  1843. }, {value: "a"});
  1844. testCM("getTokenTypeAt", function(cm) {
  1845. eq(cm.getTokenTypeAt(Pos(0, 0)), "number");
  1846. eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
  1847. cm.addOverlay({
  1848. token: function(stream) {
  1849. if (stream.match("foo")) return "foo";
  1850. else stream.next();
  1851. }
  1852. });
  1853. eq(byClassName(cm.getWrapperElement(), "cm-foo").length, 1);
  1854. eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
  1855. }, {value: "1 + 'foo'", mode: "javascript"});
  1856. testCM("resizeLineWidget", function(cm) {
  1857. addDoc(cm, 200, 3);
  1858. var widget = document.createElement("pre");
  1859. widget.innerHTML = "imwidget";
  1860. widget.style.background = "yellow";
  1861. cm.addLineWidget(1, widget, {noHScroll: true});
  1862. cm.setSize(40);
  1863. is(widget.parentNode.offsetWidth < 42);
  1864. });
  1865. testCM("combinedOperations", function(cm) {
  1866. var place = document.getElementById("testground");
  1867. var other = CodeMirror(place, {value: "123"});
  1868. try {
  1869. cm.operation(function() {
  1870. cm.addLineClass(0, "wrap", "foo");
  1871. other.addLineClass(0, "wrap", "foo");
  1872. });
  1873. eq(byClassName(cm.getWrapperElement(), "foo").length, 1);
  1874. eq(byClassName(other.getWrapperElement(), "foo").length, 1);
  1875. cm.operation(function() {
  1876. cm.removeLineClass(0, "wrap", "foo");
  1877. other.removeLineClass(0, "wrap", "foo");
  1878. });
  1879. eq(byClassName(cm.getWrapperElement(), "foo").length, 0);
  1880. eq(byClassName(other.getWrapperElement(), "foo").length, 0);
  1881. } finally {
  1882. place.removeChild(other.getWrapperElement());
  1883. }
  1884. }, {value: "abc"});
  1885. testCM("eventOrder", function(cm) {
  1886. var seen = [];
  1887. cm.on("change", function() {
  1888. if (!seen.length) cm.replaceSelection(".");
  1889. seen.push("change");
  1890. });
  1891. cm.on("cursorActivity", function() {
  1892. cm.replaceSelection("!");
  1893. seen.push("activity");
  1894. });
  1895. cm.replaceSelection("/");
  1896. eq(seen.join(","), "change,change,activity,change");
  1897. });