From 1f7f154ab251344eb1075e6651c5f27f93de8cca Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 21 Nov 2013 13:35:50 +0100 Subject: [PATCH 001/155] Bump version number post-3.20 --- lib/codemirror.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 7b4fd00141..5fec83a7f7 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5936,7 +5936,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.20.0"; + CodeMirror.version = "3.20.1"; return CodeMirror; })(); diff --git a/package.json b/package.json index 6c5a122ea2..ab323b0110 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"3.20.0", + "version":"3.20.1", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", From bdd887155c535ec7a88481071f98299fa2bed8e4 Mon Sep 17 00:00:00 2001 From: Nikolay Kostov Date: Thu, 21 Nov 2013 17:40:08 +0200 Subject: [PATCH 002/155] [the-matrix theme] Fix broken CSS selector, tune down selection contrast. --- theme/the-matrix.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/the-matrix.css b/theme/the-matrix.css index 9e60b7b04b..0c3704a62f 100644 --- a/theme/the-matrix.css +++ b/theme/the-matrix.css @@ -1,5 +1,5 @@ .cm-s-the-matrix.CodeMirror { background: #000000; color: #00FF00; } -.cm-s-the-matrix span.CodeMirror-selected { background: #a8f !important; } +.cm-s-the-matrix div.CodeMirror-selected { background: #2D2D2D !important; } .cm-s-the-matrix .CodeMirror-gutters { background: #060; border-right: 2px solid #00FF00; } .cm-s-the-matrix .CodeMirror-linenumber { color: #FFFFFF; } .cm-s-the-matrix .CodeMirror-cursor { border-left: 1px solid #00FF00 !important; } From 410bf3dc309105bb8dcf10e40f1678957314dea3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 21 Nov 2013 22:27:18 +0100 Subject: [PATCH 003/155] Fix bug in patching lines with widgets The gutter element would end up before any 'above' widgets, causing the widgets to hide it. Closes #1971 --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 5fec83a7f7..cc5f689bcb 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -719,7 +719,7 @@ window.CodeMirror = (function() { if (cm.options.lineNumbers || markers) { var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), - wrap.firstChild); + lineElement); if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap); if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) wrap.lineNumber = gutterWrap.appendChild( From 24cbf32ee0332f84d71675b2ac1af40e7ee8b3ae Mon Sep 17 00:00:00 2001 From: Beni Cherniavsky-Paskin Date: Thu, 21 Nov 2013 14:13:06 -0800 Subject: [PATCH 004/155] [changemode demo] Really rate-limit looksLikeScheme() calls --- demo/changemode.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/changemode.html b/demo/changemode.html index 61c1786074..9ea99b2d04 100644 --- a/demo/changemode.html +++ b/demo/changemode.html @@ -44,11 +44,11 @@ lineNumbers: true, tabMode: "indent" }); + var pending; editor.on("change", function() { clearTimeout(pending); - setTimeout(update, 400); + pending = setTimeout(update, 400); }); - var pending; function looksLikeScheme(code) { return !/^\s*\(\s*function\b/.test(code) && /^\s*[;\(]/.test(code); } From 771c60cfe1b5ec776f10f469c5956ad0c0abcace Mon Sep 17 00:00:00 2001 From: John Van Der Loo Date: Fri, 22 Nov 2013 15:40:21 +1100 Subject: [PATCH 005/155] Add a wrapper class to gutters to offer more control using CSS. --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index cc5f689bcb..bb3c565bda 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -717,7 +717,7 @@ window.CodeMirror = (function() { if (bgClass) wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild); if (cm.options.lineNumbers || markers) { - var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + + var gutterWrap = wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), lineElement); if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap); From 3e84b9e1f7c642021bdd0ed1fb76d497405959b8 Mon Sep 17 00:00:00 2001 From: Daniel KJ Date: Fri, 22 Nov 2013 15:28:43 +0000 Subject: [PATCH 006/155] [show-hint addon] Emit "pick" event when a completion is picked --- addon/hint/show-hint.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index 46103b719b..324d4e7ddb 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -45,6 +45,7 @@ var completion = data.list[i]; if (completion.hint) completion.hint(this.cm, data, completion); else this.cm.replaceRange(getText(completion), data.from, data.to); + CodeMirror.signal(data, "pick", completion); this.close(); }, From 28a638a984c021696d3ae9bbedb4103e0f4db4b1 Mon Sep 17 00:00:00 2001 From: ForbesLindesay Date: Fri, 22 Nov 2013 23:33:34 +0000 Subject: [PATCH 007/155] [pegjs mode] Quotes can appear in character classes when they do so, they don't indicate the start of a string. --- mode/pegjs/pegjs.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/mode/pegjs/pegjs.js b/mode/pegjs/pegjs.js index 6cdcc61f30..6b2e273791 100644 --- a/mode/pegjs/pegjs.js +++ b/mode/pegjs/pegjs.js @@ -54,15 +54,11 @@ CodeMirror.defineMode("pegjs", function (config) { } return "comment"; } else if (state.inChracterClass) { - if (stream.match(/^[^\]\\]+/)) { - return; - } else if (stream.match(/^\\./)) { - return; - } else { - stream.next(); - state.inChracterClass = false; - return 'bracket'; - } + while (state.inChracterClass && !stream.eol()) { + if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) { + state.inChracterClass = false; + } + } } else if (stream.peek() === '[') { stream.next(); state.inChracterClass = true; From c3b38d016f990c16e126b0fb024fbd2c92656c22 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 23 Nov 2013 19:43:27 +0100 Subject: [PATCH 008/155] Make textarea contextmenu shim have background: transparent To prevent it from being visible on dark backgrounds. --- lib/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index bb3c565bda..9ea863bd70 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2199,7 +2199,7 @@ window.CodeMirror = (function() { var oldCSS = display.input.style.cssText; display.inputDiv.style.position = "absolute"; display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: transparent; outline: none;" + "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);"; focusInput(cm); resetInput(cm, true); @@ -2224,7 +2224,7 @@ window.CodeMirror = (function() { if (!ie || ie_lt9) prepareSelectAllHack(); clearTimeout(detectingSelectAll); var i = 0, poll = function(){ - if (display.prevInput == " " && display.input.selectionStart == 0) + if (display.prevInput == "\u200b" && display.input.selectionStart == 0) operation(cm, commands.selectAll)(cm); else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); else resetInput(cm); From 52cbe9b13cab1a2e1d3ec127c9b66b6e0412e2fd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 25 Nov 2013 11:50:53 +0100 Subject: [PATCH 009/155] [commend addon] Prevent bogus uncomment when markers not at line start Closes #1975 --- addon/comment/comment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/comment/comment.js b/addon/comment/comment.js index cd2123e175..b8a86a3f33 100644 --- a/addon/comment/comment.js +++ b/addon/comment/comment.js @@ -97,7 +97,7 @@ var line = self.getLine(i); var found = line.indexOf(lineString); if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment; - if (i != start && found > -1 && nonWS.test(line.slice(0, found))) break lineComment; + if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; lines.push(line); } self.operation(function() { From fd746e250f7a07f70a1e4e565c29f454da44c10a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 25 Nov 2013 15:54:22 +0100 Subject: [PATCH 010/155] Prevent infinite recursion in lineIsHiddenInner Closes #1984 --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 9ea863bd70..70d8091db7 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4168,7 +4168,7 @@ window.CodeMirror = (function() { return true; for (var sp, i = 0; i < line.markedSpans.length; ++i) { sp = line.markedSpans[i]; - if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && + if (sp != span && sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && lineIsHiddenInner(doc, line, sp)) return true; } From ed26d5aa3bbe6df2223ab04c19f2a0efaeeb0403 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 25 Nov 2013 16:12:22 +0100 Subject: [PATCH 011/155] [website] Make things more or less run on IE8 HTML5 tag parsing hack, and invert css media query. --- doc/activebookmark.js | 10 ++++++++-- doc/docs.css | 13 +++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/doc/activebookmark.js b/doc/activebookmark.js index 69a126e8ce..88c879cf42 100644 --- a/doc/activebookmark.js +++ b/doc/activebookmark.js @@ -1,3 +1,7 @@ +// Kludge in HTML5 tag recognition in IE8 +document.createElement("section"); +document.createElement("article"); + (function() { var pending = false, prevVal = null; @@ -37,6 +41,8 @@ } } - window.addEventListener("scroll", updateSoon); - window.addEventListener("load", updateSoon); + if (window.addEventListener) { + window.addEventListener("scroll", updateSoon); + window.addEventListener("load", updateSoon); + } })(); diff --git a/doc/docs.css b/doc/docs.css index d2b0522e9f..c7fb6327ed 100644 --- a/doc/docs.css +++ b/doc/docs.css @@ -39,7 +39,7 @@ em { article { max-width: 700px; - margin: 0 auto; + margin: 0 0 0 160px; border-left: 2px solid #E30808; border-right: 1px solid #ddd; padding: 30px 50px 100px 50px; @@ -54,19 +54,20 @@ article { #nav { position: fixed; top: 30px; - right: 50%; + left: 0; right: none; + width: 160px; padding-right: 350px; text-align: right; z-index: 1; } -@media screen and (max-width: 1000px) { +@media screen and (min-width: 1000px) { article { - margin: 0 0 0 160px; + margin: 0 auto; } #nav { - left: 0; right: none; - width: 160px; + right: 50%; + width: auto; } } From ef8042e68aac996fab3823cf0b7ebfdb17eca0e8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 25 Nov 2013 16:41:15 +0100 Subject: [PATCH 012/155] [php mode] Make double-quoted string hack also work for single-quoted Closes #1992 --- mode/php/php.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mode/php/php.js b/mode/php/php.js index 3555b8b145..09d2ab6122 100755 --- a/mode/php/php.js +++ b/mode/php/php.js @@ -56,15 +56,15 @@ function dispatch(stream, state) { var isPHP = state.curMode == phpMode; - if (stream.sol() && state.pending != '"') state.pending = null; + if (stream.sol() && state.pending && state.pending != '"' && state.pending != "'") state.pending = null; if (!isPHP) { if (stream.match(/^<\?\w*/)) { state.curMode = phpMode; state.curState = state.php; return "meta"; } - if (state.pending == '"') { - while (!stream.eol() && stream.next() != '"') {} + if (state.pending == '"' || state.pending == "'") { + while (!stream.eol() && stream.next() != state.pending) {} var style = "string"; } else if (state.pending && stream.pos < state.pending.end) { stream.pos = state.pending.end; @@ -72,10 +72,10 @@ } else { var style = htmlMode.token(stream, state.curState); } - state.pending = null; - var cur = stream.current(), openPHP = cur.search(/<\?/); + if (state.pending) state.pending = null; + var cur = stream.current(), openPHP = cur.search(/<\?/), m; if (openPHP != -1) { - if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"'; + if (style == "string" && (m = cur.match(/[\'\"]$/)) && !/\?>/.test(cur)) state.pending = m[0]; else state.pending = {end: stream.pos, style: style}; stream.backUp(cur.length - openPHP); } From a9f5bf1010c2bd1065d97abfa0b1e885b6c657d5 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 25 Nov 2013 16:46:11 +0100 Subject: [PATCH 013/155] [dialog addon] Add value option --- addon/dialog/dialog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/dialog/dialog.js b/addon/dialog/dialog.js index 499964fdb2..41d7bf8663 100644 --- a/addon/dialog/dialog.js +++ b/addon/dialog/dialog.js @@ -35,6 +35,7 @@ } var inp = dialog.getElementsByTagName("input")[0], button; if (inp) { + if (options && options.value) inp.value = options.value; CodeMirror.on(inp, "keydown", function(e) { if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } if (e.keyCode == 13 || e.keyCode == 27) { From 9aa4d41c0ba97286c23cdc4f99d565fd04fbfd3a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 25 Nov 2013 16:46:33 +0100 Subject: [PATCH 014/155] [search addon] Default search query to current selection Closes #1993 --- addon/search/search.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addon/search/search.js b/addon/search/search.js index 71ed75b500..ddb6eeea90 100644 --- a/addon/search/search.js +++ b/addon/search/search.js @@ -33,9 +33,9 @@ // Heuristic: if the query string is all lowercase, do a case insensitive search. return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase()); } - function dialog(cm, text, shortText, f) { - if (cm.openDialog) cm.openDialog(text, f); - else f(prompt(shortText, "")); + function dialog(cm, text, shortText, deflt, f) { + if (cm.openDialog) cm.openDialog(text, f, {value: deflt}); + else f(prompt(shortText, deflt)); } function confirmDialog(cm, text, shortText, fs) { if (cm.openConfirm) cm.openConfirm(text, fs); @@ -50,7 +50,7 @@ function doSearch(cm, rev) { var state = getSearchState(cm); if (state.query) return findNext(cm, rev); - dialog(cm, queryDialog, "Search for:", function(query) { + dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) { cm.operation(function() { if (!query || state.query) return; state.query = parseQuery(query); @@ -85,10 +85,10 @@ var replacementQueryDialog = 'With: '; var doReplaceConfirm = "Replace? "; function replace(cm, all) { - dialog(cm, replaceQueryDialog, "Replace:", function(query) { + dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) { if (!query) return; query = parseQuery(query); - dialog(cm, replacementQueryDialog, "Replace with:", function(text) { + dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { if (all) { cm.operation(function() { for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { From 5d752bbd7bbccf33c986dd5bf7f19ab752c6a4aa Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Mon, 25 Nov 2013 10:58:53 +0100 Subject: [PATCH 015/155] [rpm mode] Add 'preinstall' and 'postinstall' sections --- mode/rpm/spec/spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/rpm/spec/spec.js b/mode/rpm/spec/spec.js index 9f339c21b1..0fab6c4891 100644 --- a/mode/rpm/spec/spec.js +++ b/mode/rpm/spec/spec.js @@ -4,7 +4,7 @@ CodeMirror.defineMode("spec", function() { var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/; - var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preun|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/; + var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/; var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros var control_flow_simple = /^%(else|endif)/; // rpm control flow macros var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros From c84f676ebc6302102bc8e9ac1317c59b656ee74f Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Mon, 25 Nov 2013 11:52:34 +0000 Subject: [PATCH 016/155] Use the github url for `repository` This makes it easy to find the GitHub repo from https://npmjs.org/package/codemirror --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab323b0110..27bb735827 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,5 @@ "email": "marijnh@gmail.com", "web": "http://marijnhaverbeke.nl"}], "repository": {"type": "git", - "url": "http://marijnhaverbeke.nl/git/codemirror"} + "url": "https://github.com/marijnh/CodeMirror.git"} } From f5bcce05dceb4f60c9685733d2f074d75fb96118 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 25 Nov 2013 17:36:19 +0100 Subject: [PATCH 017/155] Fix a bidi cursor drawing bug --- lib/codemirror.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 70d8091db7..fc2e971e7b 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5712,22 +5712,22 @@ window.CodeMirror = (function() { } var bidiOther; function getBidiPartAt(order, pos) { + bidiOther = null; for (var i = 0, found; i < order.length; ++i) { var cur = order[i]; - if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; } - if (cur.from == pos || cur.to == pos) { + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { if (found == null) { found = i; } else if (compareBidiLevel(order, cur.level, order[found].level)) { - bidiOther = found; + if (cur.from != cur.to) bidiOther = found; return i; } else { - bidiOther = i; + if (cur.from != cur.to) bidiOther = i; return found; } } } - bidiOther = null; return found; } From 3e614e48f31ed20364ffdde764049fa733f7b10f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 26 Nov 2013 10:08:59 +0100 Subject: [PATCH 018/155] [closetag addon] Prevent action in another corner case (When slicing the part before the cursor from the tagname causes it to be empty.) Closes #1998 --- addon/edit/closetag.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 1da89ba6dc..bbd1c6e10c 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -53,7 +53,8 @@ if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch); var lowerTagName = tagName.toLowerCase(); // Don't process the '>' at the end of an end-tag or self-closing tag - if (tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) || + if (!tagName || + tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) || tok.type == "tag" && state.type == "closeTag" || tok.string.indexOf("/") == (tok.string.length - 1) || // match something like dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1) From dafd825598f12dfe193ac85b17cc10372e2e4705 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 26 Nov 2013 10:23:35 +0100 Subject: [PATCH 019/155] Fix a bug in the bidi algorithm --- lib/codemirror.js | 4 ++-- test/test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index fc2e971e7b..d1e82bccbf 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5861,7 +5861,7 @@ window.CodeMirror = (function() { if (type == ",") types[i] = "N"; else if (type == "%") { for (var end = i + 1; end < len && types[end] == "%"; ++end) {} - var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; for (var j = i; j < end; ++j) types[j] = replace; i = end - 1; } @@ -5886,7 +5886,7 @@ window.CodeMirror = (function() { if (isNeutral.test(types[i])) { for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} var before = (i ? types[i-1] : outerType) == "L"; - var after = (end < len - 1 ? types[end] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; var replace = before || after ? "L" : "R"; for (var j = i; j < end; ++j) types[j] = replace; i = end - 1; diff --git a/test/test.js b/test/test.js index e5d9afdedd..dad089fa46 100644 --- a/test/test.js +++ b/test/test.js @@ -1141,7 +1141,7 @@ testCM("verticalMovementCommandsWrapping", function(cm) { testCM("rtlMovement", function(cm) { forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج", - "خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", + "خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", "خ ة ق", ""], function(line) { var inv = line.charAt(0) == "خ"; cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart"); From bcf48d30b9170d7cf629452119a9b8fe52bd6c30 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 26 Nov 2013 10:56:33 +0100 Subject: [PATCH 020/155] Fix bug in IE bidi measuring hack --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index d1e82bccbf..5a8fc92b5c 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1113,7 +1113,7 @@ window.CodeMirror = (function() { } } if (!rect) rect = data[i] = measureRect(getRect(node)); - if (cur.measureRight) rect.right = getRect(cur.measureRight).left; + if (cur.measureRight) rect.right = getRect(cur.measureRight).left - outer.left; if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide)); } removeChildren(cm.display.measure); From 755f3a7bafaff694f488c9df390528a5a578ce7e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 26 Nov 2013 11:19:25 +0100 Subject: [PATCH 021/155] [javascript mode] Fix context tracking for comma-separated bodies Closes #2000 --- addon/tern/tern.js | 6 +++--- mode/javascript/javascript.js | 30 +++++++++++++++++------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/addon/tern/tern.js b/addon/tern/tern.js index 1f18a657fb..7f83c4e4c0 100644 --- a/addon/tern/tern.js +++ b/addon/tern/tern.js @@ -251,7 +251,7 @@ var lex = inner.state.lexical; if (lex.info != "call") return; - var ch, pos = lex.pos || 0, tabSize = cm.getOption("tabSize"); + var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize"); for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) { var str = cm.getLine(line), extra = 0; for (var pos = 0;;) { @@ -268,7 +268,7 @@ var start = Pos(line, ch); var cache = ts.cachedArgHints; if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0) - return showArgHints(ts, cm, pos); + return showArgHints(ts, cm, argPos); ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) { if (error || !data.type || !(/^fn\(/).test(data.type)) return; @@ -279,7 +279,7 @@ guess: data.guess, doc: cm.getDoc() }; - showArgHints(ts, cm, pos); + showArgHints(ts, cm, argPos); }); } diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index f27c06346c..a2974ee5c4 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -327,7 +327,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function expressionInner(type, noComma) { if (cx.state.fatArrowAt == cx.stream.start) { var body = noComma ? arrowBodyNoComma : arrowBody; - if (type == "(") return cont(pushcontext, commasep(pattern, ")"), expect("=>"), body, popcontext); + if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); } @@ -338,7 +338,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); if (type == "[") return cont(pushlex("]"), expressionNoComma, maybeArrayComprehension, poplex, maybeop); - if (type == "{") return cont(commasep(objprop, "}"), maybeop); + if (type == "{") return contCommasep(objprop, "}", null, maybeop); return cont(); } function maybeexpression(type) { @@ -365,7 +365,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } if (type == "quasi") { cx.cc.push(me); return quasi(value); } if (type == ";") return; - if (type == "(") return cont(commasep(expressionNoComma, ")", "call"), me); + if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); if (type == ".") return cont(property, me); if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); } @@ -418,7 +418,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == ":") return cont(expressionNoComma); if (type == "(") return pass(functiondef); } - function commasep(what, end, info) { + function commasep(what, end) { function proceed(type) { if (type == ",") { var lex = cx.state.lexical; @@ -430,10 +430,14 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } return function(type) { if (type == end) return cont(); - if (info === false) return pass(what, proceed); - return pass(pushlex(end, info), what, proceed, poplex); + return pass(what, proceed); }; } + function contCommasep(what, end, info) { + for (var i = 3; i < arguments.length; i++) + cx.cc.push(arguments[i]); + return cont(pushlex(end, info), commasep(what, end), poplex); + } function block(type) { if (type == "}") return cont(); return pass(statement, block); @@ -449,8 +453,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } function pattern(type, value) { if (type == "variable") { register(value); return cont(); } - if (type == "[") return cont(commasep(pattern, "]")); - if (type == "{") return cont(commasep(proppattern, "}")); + if (type == "[") return contCommasep(pattern, "]"); + if (type == "{") return contCommasep(proppattern, "}"); } function proppattern(type, value) { if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { @@ -493,7 +497,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function functiondef(type, value) { if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} if (type == "variable") {register(value); return cont(functiondef);} - if (type == "(") return cont(pushcontext, commasep(funarg, ")"), statement, popcontext); + if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext); } function funarg(type) { if (type == "spread") return cont(funarg); @@ -506,7 +510,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (value == "extends") return cont(expression); } function objlit(type) { - if (type == "{") return cont(commasep(objprop, "}")); + if (type == "{") return contCommasep(objprop, "}"); } function afterModule(type, value) { if (type == "string") return cont(statement); @@ -522,7 +526,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return pass(importSpec, maybeFrom); } function importSpec(type, value) { - if (type == "{") return cont(commasep(importSpec, "}")); + if (type == "{") return contCommasep(importSpec, "}"); if (type == "variable") register(value); return cont(); } @@ -531,8 +535,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } function maybeArrayComprehension(type) { if (type == "for") return pass(comprehension); - if (type == ",") return cont(commasep(expressionNoComma, "]", false)); - return pass(commasep(expressionNoComma, "]", false)); + if (type == ",") return cont(commasep(expressionNoComma, "]")); + return pass(commasep(expressionNoComma, "]")); } function comprehension(type) { if (type == "for") return cont(forspec, comprehension); From 3e710f9162bc3a030d3a157fea5a1dbeac864d26 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 26 Nov 2013 21:40:36 +0100 Subject: [PATCH 022/155] [javascript mode] Fix indentation after empty array literal Closes #2001 --- mode/javascript/javascript.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index a2974ee5c4..c49b873317 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -337,7 +337,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); - if (type == "[") return cont(pushlex("]"), expressionNoComma, maybeArrayComprehension, poplex, maybeop); + if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); if (type == "{") return contCommasep(objprop, "}", null, maybeop); return cont(); } @@ -533,6 +533,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function maybeFrom(_type, value) { if (value == "from") { cx.marked = "keyword"; return cont(expression); } } + function arrayLiteral(type) { + if (type == "]") return cont(); + return pass(expressionNoComma, maybeArrayComprehension); + } function maybeArrayComprehension(type) { if (type == "for") return pass(comprehension); if (type == ",") return cont(commasep(expressionNoComma, "]")); From 4841f63e21cc31af4c91aac3d566f67950809ed0 Mon Sep 17 00:00:00 2001 From: Pavel Strashkin Date: Tue, 26 Nov 2013 13:21:07 -0800 Subject: [PATCH 023/155] [docs] Make markText to be a ref within setBookmark --- doc/manual.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual.html b/doc/manual.html index a0c650e170..796663fc88 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1273,7 +1273,7 @@

Text-marking methods

widget: Element
Can be used to display a DOM node at the current location of the bookmark (analogous to the replacedWith - option to markText).
+ option to markText).
insertLeft: boolean
By default, text typed when the cursor is on top of the bookmark will end up to the right of the bookmark. Set this option to true to make it go From 6b131f19e89f3656079c98ebcce67148c4244fce Mon Sep 17 00:00:00 2001 From: Enam Mijbah Noor Date: Wed, 27 Nov 2013 05:00:30 +0600 Subject: [PATCH 024/155] added missing keywords in jinja2 mode --- mode/jinja2/jinja2.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mode/jinja2/jinja2.js b/mode/jinja2/jinja2.js index 16b06c48ef..b28af098d0 100644 --- a/mode/jinja2/jinja2.js +++ b/mode/jinja2/jinja2.js @@ -1,7 +1,17 @@ CodeMirror.defineMode("jinja2", function() { - var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false", - "loop", "none", "self", "super", "if", "as", "not", "and", - "else", "import", "with", "without", "context"]; + var keywords = ["and", "as", "block", "endblock", "by", "cycle", "debug", "else", "elif", + "extends", "filter", "endfilter", "firstof", "for", + "endfor", "if", "endif", "ifchanged", "endifchanged", + "ifequal", "endifequal", "ifnotequal", + "endifnotequal", "in", "include", "load", "not", "now", "or", + "parsed", "regroup", "reversed", "spaceless", + "endspaceless", "ssi", "templatetag", "openblock", + "closeblock", "openvariable", "closevariable", + "openbrace", "closebrace", "opencomment", + "closecomment", "widthratio", "url", "with", "endwith", + "get_current_language", "trans", "noop", "blocktrans", + "endblocktrans", "get_available_languages", + "get_current_language_bidi", "plural"]; keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b"); function tokenBase (stream, state) { From 268cc7422b21d95e8f3db0d346d4fec346bc76ff Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sun, 24 Nov 2013 11:48:48 -0500 Subject: [PATCH 025/155] [vim] Fix failing tests in ie8 --- keymap/vim.js | 20 +++++++++++++------- test/vim_test.js | 10 ++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index dab10e21a4..164d51b1a4 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2955,6 +2955,7 @@ exCommands[commandName](cm, params); } catch(e) { showConfirm(cm, e); + throw e; } }, parseInput_: function(cm, inputStream, result) { @@ -3115,7 +3116,7 @@ var args = new CodeMirror.StringStream(params.argString); if (args.eat('!')) { reverse = true; } if (args.eol()) { return; } - if (!args.eatSpace()) { throw new Error('invalid arguments ' + args.match(/.*/)[0]); } + if (!args.eatSpace()) { return 'Invalid arguments'; } var opts = args.match(/[a-z]+/); if (opts) { opts = opts[0]; @@ -3124,13 +3125,17 @@ var decimal = opts.indexOf('d') != -1 && 1; var hex = opts.indexOf('x') != -1 && 1; var octal = opts.indexOf('o') != -1 && 1; - if (decimal + hex + octal > 1) { throw new Error('invalid arguments'); } + if (decimal + hex + octal > 1) { return 'Invalid arguments'; } number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; } - if (args.eatSpace() && args.match(/\/.*\//)) { throw new Error('patterns not supported'); } + if (args.eatSpace() && args.match(/\/.*\//)) { 'patterns not supported'; } } } - parseArgs(); + var err = parseArgs(); + if (err) { + showConfirm(cm, err + ': ' + params.argString); + return; + } var lineStart = params.line || cm.firstLine(); var lineEnd = params.lineEnd || params.line || cm.lastLine(); if (lineStart == lineEnd) { return; } @@ -3251,13 +3256,13 @@ clearSearchHighlight(cm); }, delmarks: function(cm, params) { - if (!params.argString || !params.argString.trim()) { + if (!params.argString || !trim(params.argString)) { showConfirm(cm, 'Argument required'); return; } var state = cm.state.vim; - var stream = new CodeMirror.StringStream(params.argString.trim()); + var stream = new CodeMirror.StringStream(trim(params.argString)); while (!stream.eol()) { stream.eatSpace(); @@ -3394,7 +3399,8 @@ // Actually do replace. next(); if (done) { - throw new Error('No matches for ' + query.source); + showConfirm(cm, 'No matches for ' + query.source); + return; } if (!confirm) { replaceAll(); diff --git a/test/vim_test.js b/test/vim_test.js index fe272551c5..c65b5cad03 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -96,6 +96,12 @@ function copyCursor(cur) { return { ch: cur.ch, line: cur.line }; } +function forEach(arr, func) { + for (var i = 0; i < arr.length; i++) { + func(arr[i]); + } +} + function testVim(name, run, opts, expectedFail) { var vimOpts = { lineNumbers: true, @@ -1047,7 +1053,7 @@ testVim('ctrl-x', function(cm, vim, helpers) { eq('-3', cm.getValue()); }, {value: '0'}); testVim('/ search forward', function(cm, vim, helpers) { - ['', ''].forEach(function(key) { + forEach(['', ''], function(key) { cm.setCursor(0, 0); helpers.doKeys(key); helpers.assertCursorAt(0, 5); @@ -2080,7 +2086,7 @@ testVim('[(, ])', function(cm, vim, helpers) { helpers.assertCursorAt(8,0); }, { value: squareBracketMotionSandbox}); testVim('[*, ]*, [/, ]/', function(cm, vim, helpers) { - ['*', '/'].forEach(function(key){ + forEach(['*', '/'], function(key){ cm.setCursor(7, 0); helpers.doKeys('2', '[', key); helpers.assertCursorAt(2,2); From 8f7c87245d5c0a8841748c180b24401a51ab08d2 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 28 Nov 2013 13:38:24 +0100 Subject: [PATCH 026/155] [search addon] Make overlay's case-sensitivity correspond to search Closes #2008 --- addon/search/search.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/addon/search/search.js b/addon/search/search.js index ddb6eeea90..049f72f3dc 100644 --- a/addon/search/search.js +++ b/addon/search/search.js @@ -7,7 +7,15 @@ // Ctrl-G. (function() { - function searchOverlay(query) { + function searchOverlay(query, caseInsensitive) { + var startChar; + if (typeof query == "string") { + startChar = query.charAt(0); + query = new RegExp("^" + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), + caseInsensitive ? "i" : ""); + } else { + query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : ""); + } if (typeof query == "string") return {token: function(stream) { if (stream.match(query)) return "searching"; stream.next(); @@ -17,6 +25,8 @@ if (stream.match(query)) return "searching"; while (!stream.eol()) { stream.next(); + if (startChar) + stream.skipTo(startChar) || stream.skipToEnd(); if (stream.match(query, false)) break; } }}; @@ -29,9 +39,12 @@ function getSearchState(cm) { return cm.state.search || (cm.state.search = new SearchState()); } + function queryCaseInsensitive(query) { + return typeof query == "string" && query == query.toLowerCase(); + } function getSearchCursor(cm, query, pos) { // Heuristic: if the query string is all lowercase, do a case insensitive search. - return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase()); + return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); } function dialog(cm, text, shortText, deflt, f) { if (cm.openDialog) cm.openDialog(text, f, {value: deflt}); @@ -54,7 +67,7 @@ cm.operation(function() { if (!query || state.query) return; state.query = parseQuery(query); - cm.removeOverlay(state.overlay); + cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); state.overlay = searchOverlay(state.query); cm.addOverlay(state.overlay); state.posFrom = state.posTo = cm.getCursor(); From 4b3cbc1f83aff39f481c07883a5b98c82bdfabf1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 28 Nov 2013 16:49:46 +0100 Subject: [PATCH 027/155] Prevent gutter height from forcing a scrollbar when none is needed Issue #1999 --- lib/codemirror.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index 5a8fc92b5c..0e5592fb17 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -535,6 +535,7 @@ window.CodeMirror = (function() { } display.showingFrom = from; display.showingTo = to; + display.gutters.style.height = ""; updateHeightsInViewport(cm); updateViewOffset(cm); From 45f673a108dabcb2d1ace294286aa233da35dfaa Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 28 Nov 2013 17:48:27 +0100 Subject: [PATCH 028/155] [hardwrap addon] Preserve trailing whitespace --- addon/fold/indent-fold.js | 4 ++-- addon/wrap/hardwrap.js | 18 +++++++++++++----- demo/hardwrap.html | 7 ++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/addon/fold/indent-fold.js b/addon/fold/indent-fold.js index 1bd600be42..434c2bc5ca 100644 --- a/addon/fold/indent-fold.js +++ b/addon/fold/indent-fold.js @@ -1,8 +1,8 @@ CodeMirror.registerHelper("fold", "indent", function(cm, start) { var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line); if (!/\S/.test(firstLine)) return; - var getIndent = function(lineNum) { - return CodeMirror.countColumn(lineNum, null, tabSize); + var getIndent = function(line) { + return CodeMirror.countColumn(line, null, tabSize); }; var myIndent = getIndent(firstLine); var lastLineInFold = null; diff --git a/addon/wrap/hardwrap.js b/addon/wrap/hardwrap.js index f252ffc9e9..9260c75bbb 100644 --- a/addon/wrap/hardwrap.js +++ b/addon/wrap/hardwrap.js @@ -36,31 +36,39 @@ var killTrailing = options.killTrailingSpace !== false; var changes = [], curLine = "", curNo = from.line; var lines = cm.getRange(from, to, false); + var leadingSpace = lines[0].match(/^[ \t]*/)[0]; + for (var i = 0; i < lines.length; ++i) { var text = lines[i], oldLen = curLine.length, spaceInserted = 0; if (curLine && text && !wrapOn.test(curLine.charAt(curLine.length - 1) + text.charAt(0))) { curLine += " "; spaceInserted = 1; } + var spaceTrimmed = ""; + if (i) { + spaceTrimmed = text.match(/^\s*/)[0]; + text = text.slice(spaceTrimmed.length); + } curLine += text; if (i) { - var firstBreak = curLine.length > column && findBreakPoint(curLine, column, wrapOn, killTrailing); + var firstBreak = curLine.length > column && leadingSpace == spaceTrimmed && + findBreakPoint(curLine, column, wrapOn, killTrailing); // If this isn't broken, or is broken at a different point, remove old break if (!firstBreak || firstBreak.from != oldLen || firstBreak.to != oldLen + spaceInserted) { changes.push({text: spaceInserted ? " " : "", from: Pos(curNo, oldLen), - to: Pos(curNo + 1, 0)}); + to: Pos(curNo + 1, spaceTrimmed.length)}); } else { - curLine = text; + curLine = leadingSpace + text; ++curNo; } } while (curLine.length > column) { var bp = findBreakPoint(curLine, column, wrapOn, killTrailing); - changes.push({text: "\n", + changes.push({text: "\n" + leadingSpace, from: Pos(curNo, bp.from), to: Pos(curNo, bp.to)}); - curLine = curLine.slice(bp.to); + curLine = leadingSpace + curLine.slice(bp.to); ++curNo; } } diff --git a/demo/hardwrap.html b/demo/hardwrap.html index a53d68dec6..6cfd9c87c2 100644 --- a/demo/hardwrap.html +++ b/demo/hardwrap.html @@ -55,10 +55,15 @@ + diff --git a/test/search_test.js b/test/search_test.js new file mode 100644 index 0000000000..c6aaa5809e --- /dev/null +++ b/test/search_test.js @@ -0,0 +1,61 @@ +(function() { + "use strict"; + + function test(name) { + var text = Array.prototype.slice.call(arguments, 1, arguments.length - 1).join("\n"); + var body = arguments[arguments.length - 1]; + return window.test("search_" + name, function() { + body(new CodeMirror.Doc(text)); + }); + } + + function run(doc, query, insensitive) { + var cursor = doc.getSearchCursor(query, null, insensitive); + for (var i = 3; i < arguments.length; i += 4) { + var found = cursor.findNext(); + is(found, "not enough results (forward)"); + eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, forward, " + (i - 3) / 4); + eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, forward, " + (i - 3) / 4); + } + is(!cursor.findNext(), "too many matches (forward)"); + for (var i = arguments.length - 4; i >= 3; i -= 4) { + var found = cursor.findPrevious(); + is(found, "not enough results (backwards)"); + eqPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, backwards, " + (i - 3) / 4); + eqPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, backwards, " + (i - 3) / 4); + } + is(!cursor.findPrevious(), "too many matches (backwards)"); + } + + test("simple", "abcdefg", "abcdefg", function(doc) { + run(doc, "cde", false, 0, 2, 0, 5, 1, 2, 1, 5); + }); + + test("multiline", "hallo", "goodbye", function(doc) { + run(doc, "llo\ngoo", false, 0, 2, 1, 3); + run(doc, "blah\nhall", false); + run(doc, "bye\neye", false); + }); + + test("regexp", "abcde", "abcde", function(doc) { + run(doc, /bcd/, false, 0, 1, 0, 4, 1, 1, 1, 4); + run(doc, /BCD/, false); + run(doc, /BCD/i, false, 0, 1, 0, 4, 1, 1, 1, 4); + }); + + test("insensitive", "hallo", "HALLO", "oink", "hAllO", function(doc) { + run(doc, "All", false, 3, 1, 3, 4); + run(doc, "All", true, 0, 1, 0, 4, 1, 1, 1, 4, 3, 1, 3, 4); + }); + + test("multilineInsensitive", "zie ginds komT", "De Stoomboot", "uit Spanje weer aan", function(doc) { + run(doc, "komt\nde stoomboot\nuit", false); + run(doc, "komt\nde stoomboot\nuit", true, 0, 10, 2, 3); + run(doc, "kOMt\ndE stOOmboot\nuiT", true, 0, 10, 2, 3); + }); + + test("expandingCaseFold", "İİ İİ", "uu uu", function(doc) { + run(doc, "", true, 0, 8, 0, 12, 1, 8, 1, 12); + run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8); + }); +})(); From 48d11dad21e6cca117d0568469a7a3df6d90276d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 3 Dec 2013 21:09:56 +0100 Subject: [PATCH 033/155] [activeline addon] Optimize Issue #2007 --- addon/selection/active-line.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/addon/selection/active-line.js b/addon/selection/active-line.js index e50508653a..35e989dcf8 100644 --- a/addon/selection/active-line.js +++ b/addon/selection/active-line.js @@ -12,10 +12,10 @@ CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) { var prev = old && old != CodeMirror.Init; if (val && !prev) { - updateActiveLine(cm); - cm.on("cursorActivity", updateActiveLine); + updateActiveLine(cm, cm.getCursor().line); + cm.on("beforeSelectionChange", selectionChange); } else if (!val && prev) { - cm.off("cursorActivity", updateActiveLine); + cm.off("beforeSelecionChange", selectionChange); clearActiveLine(cm); delete cm.state.activeLine; } @@ -28,12 +28,18 @@ } } - function updateActiveLine(cm) { - var line = cm.getLineHandleVisualStart(cm.getCursor().line); + function updateActiveLine(cm, selectedLine) { + var line = cm.getLineHandleVisualStart(selectedLine); if (cm.state.activeLine == line) return; - clearActiveLine(cm); - cm.addLineClass(line, "wrap", WRAP_CLASS); - cm.addLineClass(line, "background", BACK_CLASS); - cm.state.activeLine = line; + cm.operation(function() { + clearActiveLine(cm); + cm.addLineClass(line, "wrap", WRAP_CLASS); + cm.addLineClass(line, "background", BACK_CLASS); + cm.state.activeLine = line; + }); + } + + function selectionChange(cm, sel) { + updateActiveLine(cm, sel.head.line); } })(); From 8914577694e56be06c1239755a20a383d9841ee9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 3 Dec 2013 21:27:37 +0100 Subject: [PATCH 034/155] Add an (experimental) hideFirstChars method to StringStream Issue #2019 --- lib/codemirror.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 8be133ace0..a98888256b 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3710,11 +3710,12 @@ window.CodeMirror = (function() { this.string = string; this.tabSize = tabSize || 8; this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; } StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, + sol: function() {return this.pos == this.lineStart;}, peek: function() {return this.string.charAt(this.pos) || undefined;}, next: function() { if (this.pos < this.string.length) @@ -3747,9 +3748,12 @@ window.CodeMirror = (function() { this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); this.lastColumnPos = this.start; } - return this.lastColumnValue; + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); }, - indentation: function() {return countColumn(this.string, null, this.tabSize);}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; @@ -3765,7 +3769,12 @@ window.CodeMirror = (function() { return match; } }, - current: function(){return this.string.slice(this.start, this.pos);} + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } }; CodeMirror.StringStream = StringStream; From e7b737c8a470b210ac4b8fb342c126d4d19f47b0 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 3 Dec 2013 21:51:07 +0100 Subject: [PATCH 035/155] Disable a search test on Phantom Due to unexplainable hanging. --- test/search_test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/search_test.js b/test/search_test.js index c6aaa5809e..04a1e685ab 100644 --- a/test/search_test.js +++ b/test/search_test.js @@ -55,6 +55,7 @@ }); test("expandingCaseFold", "İİ İİ", "uu uu", function(doc) { + if (phantom) return; // A Phantom bug makes this hang run(doc, "", true, 0, 8, 0, 12, 1, 8, 1, 12); run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8); }); From 004734cfbd0e1e0e109675dd9d51d985fdfe6ecb Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 4 Dec 2013 15:55:05 +0100 Subject: [PATCH 036/155] Replace accidental emdash with dash in regexp --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index a98888256b..7456147289 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5522,7 +5522,7 @@ window.CodeMirror = (function() { return true; } - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0–\u1DFF\u20D0–\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20–\uFE2F]/; + var isExtendingChar = window.ext = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0–\u1DFF\u20D0–\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20-\uFE2F]/; // DOM UTILITIES From 58ce53c1580339375d16e9111791ed5e8e3634b8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 4 Dec 2013 16:05:19 +0100 Subject: [PATCH 037/155] Remove all non-ascii text from codemirror.js --- lib/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 7456147289..dc3451b98c 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5522,7 +5522,7 @@ window.CodeMirror = (function() { return true; } - var isExtendingChar = window.ext = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0–\u1DFF\u20D0–\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20-\uFE2F]/; + var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0-\u1DFF\u20D0-\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20-\uFE2F]/; // DOM UTILITIES @@ -5595,7 +5595,7 @@ window.CodeMirror = (function() { if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false; } - return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); + return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); }; var knownScrollbarWidth; From 6b53fa295b58b4b19a42d61034ad2f1f40c43322 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 5 Dec 2013 12:25:49 +0100 Subject: [PATCH 038/155] [merge addon] Support passing in document objects as content --- addon/merge/merge.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addon/merge/merge.js b/addon/merge/merge.js index 70341e4d12..89f852652b 100644 --- a/addon/merge/merge.js +++ b/addon/merge/merge.js @@ -29,7 +29,7 @@ this.edit = this.mv.edit; this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options))); - this.diff = getDiff(orig, options.value); + this.diff = getDiff(asString(orig), asString(options.value)); this.diffOutOfDate = false; this.showDifferences = options.showDifferences !== false; @@ -352,6 +352,11 @@ } }; + function asString(obj) { + if (typeof obj == "string") return obj; + else return obj.getValue(); + } + // Operations on diffs var dmp = new diff_match_patch(); From 524159fc16f52cbdb11b65480dc8825d3fa3e5fd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 5 Dec 2013 15:39:32 +0100 Subject: [PATCH 039/155] [merge demo] Remove layout-breaking css rule --- demo/merge.html | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/merge.html b/demo/merge.html index d2df900c20..fc272f0652 100644 --- a/demo/merge.html +++ b/demo/merge.html @@ -12,7 +12,6 @@ + + + + + + + + + + + + + +
-

Code Folding Demo

-
-
JavaScript:
-
HTML:
-
- -
+ + + diff --git a/demo/html5complete.html b/demo/html5complete.html index 899260aa20..94ced527d0 100644 --- a/demo/html5complete.html +++ b/demo/html5complete.html @@ -1,37 +1,41 @@ -CodeMirror: HTML completion demo - - - - - - - - - - - - - - - - -
-

HTML completion demo

+ + CodeMirror: HTML completion demo + + + + + + + + + + + + + + + + + + + +
+

HTML completion demo

Shows the XML completer parameterized with information about the tags in HTML. @@ -40,15 +44,13 @@

+ diff --git a/doc/manual.html b/doc/manual.html index 98d7fb88ec..0dc120ddf3 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1533,18 +1533,35 @@

Mode, state, and token-related methods

unstyled tokens, and a string, potentially containing multiple space-separated style names, otherwise.
+
cm.getHelpers(pos: {line, ch}, type: string) → array<helper>
+
Fetch the set of applicable helper values for the given + position. Helpers provide a way to look up functionality + appropriate for a mode. The type argument provides + the helper namespace (see + registerHelper), in + which the values will be looked up. When the mode itself has a + property that corresponds to the type, that + directly determines the keys that are used to look up the helper + values (it may be either a single string, or an array of + strings). Failing that, the mode's helperType + property and finally the mode's name are used.
+
For example, the JavaScript mode has a + property fold containing "brace". When + the brace-fold addon is loaded, that defines a + helper named brace in the fold + namespace. This is then used by + the foldcode addon to + figure out that it can use that folding function to fold + JavaScript code.
+
When any 'global' + helpers are defined for the given namespace, their predicates + are called on the current mode and editor, and all those that + declare they are applicable will also be added to the array that + is returned.
+
cm.getHelper(pos: {line, ch}, type: string) → helper
-
Fetch appropriate helper for the given position. Helpers - provide a way to look up functionality appropriate for a mode. - The type argument provides the helper namespace - (see - also registerHelper), - in which the value will be looked up. The key that is used - depends on the mode active at the given - position. If the mode object contains a property with the same - name as the type argument, that is tried first. - Next, the mode's helperType, if any, is tried. And - finally, the mode's name.
+
Returns the first applicable helper value. + See getHelpers.
cm.getStateAfter(?line: integer, ?precise: boolean) → object
Returns the mode's parser state, if any, at the end of the @@ -1689,7 +1706,7 @@

Static properties

(with the instance as argument) whenever a new CodeMirror instance is initialized.
-
CodeMirror.registerHelper(type: string, name: string, value: helper)
+
CodeMirror.registerHelper(type: string, name: string, value: helper)
Registers a helper value with the given name in the given namespace (type). This is used to define functionality that may be looked up by mode. Will create (if it @@ -1700,6 +1717,14 @@

Static properties

myFoo), the value CodeMirror.hint.foo will point to myFoo.
+
CodeMirror.registerGlobalHelper(type: string, name: string, predicate: fn(mode, CodeMirror), value: helper)
+
Acts + like registerHelper, + but also registers this helper as 'global', meaning that it will + be included by getHelpers + whenever the given predicate returns true when + called with the local mode and editor.
+
CodeMirror.Pos(line: integer, ?ch: integer)
A constructor for the {line, ch} objects that are used to represent positions in editor documents.
@@ -1881,11 +1906,12 @@

Static properties

supporting the following properties:
rangeFinder: fn(CodeMirror, Pos)
-
The function that is used to find foldable ranges. If this - is not directly passed, it will - call getHelper with - a "fold" type to find one that's appropriate for - the mode. There are files in +
The function that is used to find + foldable ranges. If this is not directly passed, it will + default to CodeMirror.fold.auto, which + uses getHelpers with + a "fold" type to find folding functions + appropriate for the local mode. There are files in the addon/fold/ directory providing CodeMirror.fold.brace, which finds blocks in brace languages (JavaScript, C, Java, @@ -1944,8 +1970,8 @@

Static properties

rangeFinder: fn(CodeMirror, Pos)
The range-finder function to use when determining whether something can be folded. When not - given, getHelper will be - used to determine a default.
+ given, CodeMirror.fold.auto + will be used as default.
Demo here. @@ -2017,9 +2043,17 @@

Static properties

is an array of strings or objects (the completions), and from and to give the start and end of the token that is being completed as {line, ch} - objects. If no hinting function is given, the addon will try to - use getHelper with - the "hint" type to find one. When completions + objects. +
If no hinting function is given, the addon will + use CodeMirror.hint.auto, with + calls getHelpers with + the "hint" type to find applicable hinting + functions, and tries them one by one. If that fails, it looks + for a "hintWords" helper to fetch a list of + completable words for the mode, and + uses CodeMirror.hint.fromList to complete from + those.
+
When completions aren't simple strings, they should be objects with the folowing properties:
@@ -2523,6 +2557,13 @@

Static properties

object specifying a mode, as in the mode option.

+

If a mode specification wants to add some properties to the + resulting mode object, typically for use + with getHelpers, it may + contain a modeProps property, which holds an object. + This object's properties will be copied to the actual mode + object.

+

Sometimes, it is useful to add or override mode object properties from external code. The CodeMirror.extendMode function diff --git a/lib/codemirror.js b/lib/codemirror.js index ba24bbcf3d..7b1b67dc0e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2977,11 +2977,31 @@ window.CodeMirror = (function() { }, getHelper: function(pos, type) { - if (!helpers.hasOwnProperty(type)) return; + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return helpers; var help = helpers[type], mode = this.getModeAt(pos); - return mode[type] && help[mode[type]] || - mode.helperType && help[mode.helperType] || - help[mode.name]; + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; }, getStateAfter: function(line, precise) { @@ -3413,6 +3433,9 @@ window.CodeMirror = (function() { } } modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; return modeObj; }; @@ -3443,9 +3466,13 @@ window.CodeMirror = (function() { var helpers = CodeMirror.helpers = {}; CodeMirror.registerHelper = function(type, name, value) { - if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {}; + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; helpers[type][name] = value; }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; // UTILITIES diff --git a/mode/clike/clike.js b/mode/clike/clike.js index f6626cd0ea..4e4988dd81 100644 --- a/mode/clike/clike.js +++ b/mode/clike/clike.js @@ -203,18 +203,34 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { return "string"; } - function mimes(ms, mode) { - for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); + function def(mimes, mode) { + var words = []; + function add(obj) { + if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) + words.push(prop); + } + add(mode.keywords); + add(mode.builtin); + add(mode.atoms); + if (words.length) { + mode.helperType = mimes[0]; + CodeMirror.registerHelper("hintWords", mimes[0], words); + } + + for (var i = 0; i < mimes.length; ++i) + CodeMirror.defineMIME(mimes[i], mode); } - mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { + def(["text/x-csrc", "text/x-c", "text/x-chdr"], { name: "clike", keywords: words(cKeywords), blockKeywords: words("case do else for if switch while struct"), atoms: words("null"), - hooks: {"#": cppHook} + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} }); - mimes(["text/x-c++src", "text/x-c++hdr"], { + + def(["text/x-c++src", "text/x-c++hdr"], { name: "clike", keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + "static_cast typeid catch operator template typename class friend private " + @@ -222,7 +238,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { "wchar_t"), blockKeywords: words("catch class do else finally for if struct switch try while"), atoms: words("true false null"), - hooks: {"#": cppHook} + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} }); CodeMirror.defineMIME("text/x-java", { name: "clike", @@ -238,7 +255,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { stream.eatWhile(/[\w\$_]/); return "meta"; } - } + }, + modeProps: {fold: ["brace", "import"]} }); CodeMirror.defineMIME("text/x-csharp", { name: "clike", @@ -303,7 +321,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { } } }); - mimes(["x-shader/x-vertex", "x-shader/x-fragment"], { + def(["x-shader/x-vertex", "x-shader/x-fragment"], { name: "clike", keywords: words("float int bool void " + "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " + @@ -357,6 +375,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " + "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " + "gl_MaxDrawBuffers"), - hooks: {"#": cppHook} + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} }); }()); diff --git a/test/test.js b/test/test.js index 439f2412af..86b0bfe178 100644 --- a/test/test.js +++ b/test/test.js @@ -1625,3 +1625,22 @@ testCM("lineStyleFromMode", function(cm) { eq(parenElts[0].nodeName, "DIV"); is(!/parens.*parens/.test(parenElts[0].className)); }, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: nothing"}); + +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("/"), ""); +}); From 48b40a43680091b05e85bfcf709b32384079a874 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 10 Dec 2013 11:43:36 +0100 Subject: [PATCH 063/155] [css mode] Refactor, step 1 (tokenizer) --- mode/css/css.js | 56 ++++++++++++++++++++++----------------------------- mode/css/scss_test.js | 38 +++++++++++++++++----------------- mode/css/test.js | 36 ++++++++++++++++----------------- 3 files changed, 61 insertions(+), 69 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index d8c30cf33d..655261d887 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -18,57 +18,48 @@ CodeMirror.defineMode("css", function(config, parserConfig) { function tokenBase(stream, state) { var ch = stream.next(); if (hooks[ch]) { - // result[0] is style and result[1] is type var result = hooks[ch](stream, state); if (result !== false) return result; } - if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());} - else if (ch == "=") ret(null, "compare"); - else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); - else if (ch == "\"" || ch == "'") { + if (ch == "@") { + stream.eatWhile(/[\w\\\-]/); + return ret("def", stream.current()); + } else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) { + return ret(null, "compare"); + } else if (ch == "\"" || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); - } - else if (ch == "#") { + } else if (ch == "#") { stream.eatWhile(/[\w\\\-]/); return ret("atom", "hash"); - } - else if (ch == "!") { + } else if (ch == "!") { stream.match(/^\s*\w*/); return ret("keyword", "important"); - } - else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) { + } else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); - } - else if (ch === "-") { - if (/\d/.test(stream.peek())) { + } else if (ch === "-") { + if (/[\d.]/.test(stream.peek())) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); } else if (stream.match(/^[^-]+-/)) { return ret("meta", "meta"); } - } - else if (/[,+>*\/]/.test(ch)) { + } else if (/[,+>*\/]/.test(ch)) { return ret(null, "select-op"); - } - else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) { + } else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) { return ret("qualifier", "qualifier"); - } - else if (ch == ":") { - return ret("operator", ch); - } - else if (/[;{}\[\]\(\)]/.test(ch)) { + } else if (/[:;{}\[\]\(\)]/.test(ch)) { return ret(null, ch); - } - else if (ch == "u" && stream.match("rl(")) { + } else if (ch == "u" && stream.match("rl(")) { stream.backUp(1); state.tokenize = tokenParenthesized; return ret("property", "variable"); - } - else { + } else if (/[\w\\\-]/.test(ch)) { stream.eatWhile(/[\w\\\-]/); return ret("property", "variable"); + } else { + return ret(null, null); } } @@ -106,6 +97,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) { }, token: function(stream, state) { + if (!state.tokenize && stream.eatSpace()) return null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style && typeof style == "object") { + type = style[1]; + style = style[0]; + } // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50) // @@ -153,11 +150,6 @@ CodeMirror.defineMode("css", function(config, parserConfig) { // sequence of selectors: // One or more of the named type of selector chained with commas. - state.tokenize = state.tokenize || tokenBase; - if (state.tokenize == tokenBase && stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - if (style && typeof style != "string") style = ret(style[0], style[1]); - // Changing style returned based on context var context = state.stack[state.stack.length-1]; if (style == "variable") { diff --git a/mode/css/scss_test.js b/mode/css/scss_test.js index 9998e2aacb..ec66473f29 100644 --- a/mode/css/scss_test.js +++ b/mode/css/scss_test.js @@ -4,31 +4,31 @@ function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } MT('url_with_quotation', - "[tag foo] { [property background][operator :][string-2 url]([string test.jpg]) }"); + "[tag foo] { [property background]:[string-2 url]([string test.jpg]) }"); MT('url_with_double_quotes', - "[tag foo] { [property background][operator :][string-2 url]([string \"test.jpg\"]) }"); + "[tag foo] { [property background]:[string-2 url]([string \"test.jpg\"]) }"); MT('url_with_single_quotes', - "[tag foo] { [property background][operator :][string-2 url]([string \'test.jpg\']) }"); + "[tag foo] { [property background]:[string-2 url]([string \'test.jpg\']) }"); MT('string', "[def @import] [string \"compass/css3\"]"); MT('important_keyword', - "[tag foo] { [property background][operator :][string-2 url]([string \'test.jpg\']) [keyword !important] }"); + "[tag foo] { [property background]:[string-2 url]([string \'test.jpg\']) [keyword !important] }"); MT('variable', - "[variable-2 $blue][operator :][atom #333]"); + "[variable-2 $blue]:[atom #333]"); MT('variable_as_attribute', - "[tag foo] { [property color][operator :][variable-2 $blue] }"); + "[tag foo] { [property color]:[variable-2 $blue] }"); MT('numbers', - "[tag foo] { [property padding][operator :][number 10px] [number 10] [number 10em] [number 8in] }"); + "[tag foo] { [property padding]:[number 10px] [number 10] [number 10em] [number 8in] }"); MT('number_percentage', - "[tag foo] { [property width][operator :][number 80%] }"); + "[tag foo] { [property width]:[number 80%] }"); MT('selector', "[builtin #hello][qualifier .world]{}"); @@ -40,44 +40,44 @@ "[comment /*foobar*/]"); MT('attribute_with_hyphen', - "[tag foo] { [property font-size][operator :][number 10px] }"); + "[tag foo] { [property font-size]:[number 10px] }"); MT('string_after_attribute', - "[tag foo] { [property content][operator :][string \"::\"] }"); + "[tag foo] { [property content]:[string \"::\"] }"); MT('directives', "[def @include] [qualifier .mixin]"); MT('basic_structure', - "[tag p] { [property background][operator :][keyword red]; }"); + "[tag p] { [property background]:[keyword red]; }"); MT('nested_structure', - "[tag p] { [tag a] { [property color][operator :][keyword red]; } }"); + "[tag p] { [tag a] { [property color]:[keyword red]; } }"); MT('mixin', "[def @mixin] [tag table-base] {}"); MT('number_without_semicolon', - "[tag p] {[property width][operator :][number 12]}", - "[tag a] {[property color][operator :][keyword red];}"); + "[tag p] {[property width]:[number 12]}", + "[tag a] {[property color]:[keyword red];}"); MT('atom_in_nested_block', - "[tag p] { [tag a] { [property color][operator :][atom #000]; } }"); + "[tag p] { [tag a] { [property color]:[atom #000]; } }"); MT('interpolation_in_property', "[tag foo] { [operator #{][variable-2 $hello][operator }:][number 2]; }"); MT('interpolation_in_selector', - "[tag foo][operator #{][variable-2 $hello][operator }] { [property color][operator :][atom #000]; }"); + "[tag foo][operator #{][variable-2 $hello][operator }] { [property color]:[atom #000]; }"); MT('interpolation_error', - "[tag foo][operator #{][error foo][operator }] { [property color][operator :][atom #000]; }"); + "[tag foo][operator #{][error foo][operator }] { [property color]:[atom #000]; }"); MT("divide_operator", - "[tag foo] { [property width][operator :][number 4] [operator /] [number 2] }"); + "[tag foo] { [property width]:[number 4] [operator /] [number 2] }"); MT('nested_structure_with_id_selector', - "[tag p] { [builtin #hello] { [property color][operator :][keyword red]; } }"); + "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }"); IT('mixin', "@mixin container[1 (][2 $a: 10][1 , ][2 $b: 10][1 , ][2 $c: 10]) [1 {]}"); diff --git a/mode/css/test.js b/mode/css/test.js index 3c52460d9f..76b94327bc 100644 --- a/mode/css/test.js +++ b/mode/css/test.js @@ -62,7 +62,7 @@ // Make sure nesting works with media queries MT("atMediaMaxWidthNested", - "[def @media] [attribute screen] [operator and] ([property max-width][operator :] [number 25px]) { [tag foo] { } }"); + "[def @media] [attribute screen] [operator and] ([property max-width]: [number 25px]) { [tag foo] { } }"); MT("tagSelector", "[tag foo] { }"); @@ -74,53 +74,53 @@ "[builtin #foo] { [error #foo] }"); MT("tagSelectorUnclosed", - "[tag foo] { [property margin][operator :] [number 0] } [tag bar] { }"); + "[tag foo] { [property margin]: [number 0] } [tag bar] { }"); MT("tagStringNoQuotes", - "[tag foo] { [property font-family][operator :] [variable-2 hello] [variable-2 world]; }"); + "[tag foo] { [property font-family]: [variable-2 hello] [variable-2 world]; }"); MT("tagStringDouble", - "[tag foo] { [property font-family][operator :] [string \"hello world\"]; }"); + "[tag foo] { [property font-family]: [string \"hello world\"]; }"); MT("tagStringSingle", - "[tag foo] { [property font-family][operator :] [string 'hello world']; }"); + "[tag foo] { [property font-family]: [string 'hello world']; }"); MT("tagColorKeyword", "[tag foo] {" + - "[property color][operator :] [keyword black];" + - "[property color][operator :] [keyword navy];" + - "[property color][operator :] [keyword yellow];" + + "[property color]: [keyword black];" + + "[property color]: [keyword navy];" + + "[property color]: [keyword yellow];" + "}"); MT("tagColorHex3", - "[tag foo] { [property background][operator :] [atom #fff]; }"); + "[tag foo] { [property background]: [atom #fff]; }"); MT("tagColorHex6", - "[tag foo] { [property background][operator :] [atom #ffffff]; }"); + "[tag foo] { [property background]: [atom #ffffff]; }"); MT("tagColorHex4", - "[tag foo] { [property background][operator :] [atom&error #ffff]; }"); + "[tag foo] { [property background]: [atom&error #ffff]; }"); MT("tagColorHexInvalid", - "[tag foo] { [property background][operator :] [atom&error #ffg]; }"); + "[tag foo] { [property background]: [atom&error #ffg]; }"); MT("tagNegativeNumber", - "[tag foo] { [property margin][operator :] [number -5px]; }"); + "[tag foo] { [property margin]: [number -5px]; }"); MT("tagPositiveNumber", - "[tag foo] { [property padding][operator :] [number 5px]; }"); + "[tag foo] { [property padding]: [number 5px]; }"); MT("tagVendor", - "[tag foo] { [meta -foo-][property box-sizing][operator :] [meta -foo-][string-2 border-box]; }"); + "[tag foo] { [meta -foo-][property box-sizing]: [meta -foo-][string-2 border-box]; }"); MT("tagBogusProperty", - "[tag foo] { [property&error barhelloworld][operator :] [number 0]; }"); + "[tag foo] { [property&error barhelloworld]: [number 0]; }"); MT("tagTwoProperties", - "[tag foo] { [property margin][operator :] [number 0]; [property padding][operator :] [number 0]; }"); + "[tag foo] { [property margin]: [number 0]; [property padding]: [number 0]; }"); MT("tagTwoPropertiesURL", - "[tag foo] { [property background][operator :] [string-2 url]([string //example.com/foo.png]); [property padding][operator :] [number 0]; }"); + "[tag foo] { [property background]: [string-2 url]([string //example.com/foo.png]); [property padding]: [number 0]; }"); MT("commentSGML", "[comment ]"); From c2b4f1509ead56bdbae88d5c1489ffd9503b2964 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 10 Dec 2013 14:14:30 +0100 Subject: [PATCH 064/155] [css mode] Refactor, step 2 (state machine) --- mode/css/css.js | 491 ++++++++++++++++++++++++-------------------------- mode/css/scss_test.js | 10 +- mode/css/test.js | 12 +- test/mode_test.js | 7 +- 4 files changed, 248 insertions(+), 272 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index 655261d887..b8081e0530 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -3,22 +3,24 @@ CodeMirror.defineMode("css", function(config, parserConfig) { if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css"); - var indentUnit = config.indentUnit || config.tabSize || 2, - hooks = parserConfig.hooks || {}, - atMediaTypes = parserConfig.atMediaTypes || {}, - atMediaFeatures = parserConfig.atMediaFeatures || {}, + var indentUnit = config.indentUnit, + tokenHooks = parserConfig.tokenHooks, + mediaTypes = parserConfig.mediaTypes || {}, + mediaFeatures = parserConfig.mediaFeatures || {}, propertyKeywords = parserConfig.propertyKeywords || {}, colorKeywords = parserConfig.colorKeywords || {}, valueKeywords = parserConfig.valueKeywords || {}, - allowNested = !!parserConfig.allowNested, - type = null; + allowNested = parserConfig.allowNested; + var type, override; function ret(style, tp) { type = tp; return style; } + // Tokenizers + function tokenBase(stream, state) { var ch = stream.next(); - if (hooks[ch]) { - var result = hooks[ch](stream, state); + if (tokenHooks[ch]) { + var result = tokenHooks[ch](stream, state); if (result !== false) return result; } if (ch == "@") { @@ -54,10 +56,10 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } else if (ch == "u" && stream.match("rl(")) { stream.backUp(1); state.tokenize = tokenParenthesized; - return ret("property", "variable"); + return ret("property", "word"); } else if (/[\w\\\-]/.test(ch)) { stream.eatWhile(/[\w\\\-]/); - return ret("property", "variable"); + return ret("property", "word"); } else { return ret(null, null); } @@ -73,7 +75,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } if (!escaped) { if (nonInclusive) stream.backUp(1); - state.tokenize = tokenBase; + state.tokenize = null; } return ret("string", "string"); }; @@ -84,16 +86,189 @@ CodeMirror.defineMode("css", function(config, parserConfig) { if (!stream.match(/\s*[\"\']/, false)) state.tokenize = tokenString(")", true); else - state.tokenize = tokenBase; + state.tokenize = null; return ret(null, "("); } + // Context management + + function Context(type, indent, prev) { + this.type = type; + this.indent = indent; + this.prev = prev; + } + + function pushContext(state, type) { + state.context = new Context(type, state.context.indent + indentUnit, state.context); + return type; + } + + function popContext(state) { + state.context = state.context.prev; + return state.context.type; + } + + function pass(type, stream, state) { + return states[state.context.type](type, stream, state); + } + function popAndPass(type, stream, state, n) { + for (var i = n || 1; i > 0; i--) + state.context = state.context.prev; + return pass(type, stream, state); + } + + // Parser + + var states = {}; + + states.top = function(type, _stream, state) { + if (type == "{") { + return pushContext(state, "block"); + } else if (type == "}" && state.context.prev) { + return popContext(state); + } else if (type == "@media") { + return pushContext(state, "media"); + } else if (type && type.charAt(0) == "@") { + return pushContext(state, "at"); + } else if (type == "hash") { + override = "builtin"; + } else if (type == "word") { + override = "tag"; + } else if (type == "variable-definition") { + return pushContext(state, "maybeprop"); + } else if (type == "interpolation") { + return pushContext(state, "interpolation"); + } else if (allowNested && type == "(") { + return pushContext(state, "params"); + } + return state.context.type; + }; + states.block = function(type, stream, state) { + if (type == "word") { + if (propertyKeywords.hasOwnProperty(stream.current().toLowerCase())) { + override = "property"; + return "maybeprop"; + } else if (allowNested) { + override = "tag"; + return "block"; + } else { + override += " error"; + return "maybeprop"; + } + } else if (type == "meta") { + return "block"; + } else if (!allowNested && (type == "hash" || type == "qualifier")) { + override = "error"; + return "block"; + } else { + return states.top(type, stream, state); + } + }; + states.maybeprop = function(type, _stream, state) { + if (type == ":") return pushContext(state, "prop"); + else return state.context.type; + }; + states.prop = function(type, stream, state) { + if (type == ";") return popContext(state); + if (type == "}" || type == "{") return popAndPass(type, stream, state); + if (type == "(") return pushContext(state, "parens"); + + if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { + override += " error"; + } else if (type == "word") { + var word = stream.current().toLowerCase(); + if (valueKeywords.hasOwnProperty(word)) + override = "string-2"; + else if (colorKeywords.hasOwnProperty(word)) + override = "keyword"; + else + override = "variable-2"; + } else if (type == "interpolation") { + return pushContext(state, "interpolation"); + } + return "prop"; + }; + states.parens = function(type, stream, state) { + if (type == "{" || type == "}") return popAndPass(type, stream, state); + if (type == ")") return popContext(state); + return "parens"; + }; + states.media = function(type, stream, state) { + if (type == "(") return "media_parens"; + if (type == "}") return popAndPass(type, stream, state); + if (type == "{") return popContext(state) && pushContext(state, "top"); + if (type == "word") { + var word = stream.current(); + if (mediaTypes[word]) { + override = "attribute"; + return "mediatype"; + } else if (/^(only|not)$/.test(word)) { + override = "keyword"; + return "mediatype"; + } else if (word == "and" || mediaFeatures.hasOwnProperty(word)) { + override = "error"; + return "media"; + } else { + override = "attribute error"; + return "mediatype"; + } + } + return "media"; + }; + states.mediatype = function(type, stream, state) { + if (type == ",") return "media"; + if (type == "(") return pushContext(state, "media_parens"); + if (type == "}") return popAndPass(type, stream, state); + if (type == "{") return popContext(state) && pushContext(state, "top"); + if (type == "word") { + var word = stream.current(); + if (mediaTypes.hasOwnProperty(word)) + override = "attribute"; + else if (word == "and") + override = "operator"; + else + override = "error"; + } + return "mediatype"; + }; + states.media_parens = function(type, stream, state) { + if (type == ")") return popContext(state); + if (type == "{" || type == "}") return popAndPass(type, stream, state, 2); + if (type == "word") { + var word = stream.current().toLowerCase(); + if (propertyKeywords.hasOwnProperty(word)) + override = "property"; + else if (word == "and") + override = "operator"; + else + override = "error"; + } + return "media_parens"; + }; + states.at = function(type, stream, state) { + if (type == ";") return popContext(state); + if (type == "{" || type == "}") return popAndPass(type, stream, state); + if (type == "word") override = "tag"; + else if (type == "hash") override = "atom"; + return "at"; + }; + states.interpolation = function(type, stream, state) { + if (type == "}") return popContext(state); + if (type == "{" || type == ";") return popAndPass(type, stream, state); + if (type != "variable") override = "error"; + return "interpolation"; + }; + states.params = function(type, stream, state) { + if (type == ")") return popContext(state); + if (type == "{" || type == "}") return popAndPass(type, stream, state); + return "params"; + }; + return { startState: function(base) { - return {tokenize: tokenBase, - baseIndent: base || 0, - stack: [], - lastToken: null}; + return {tokenize: null, + state: "top", + context: new Context("top", base || 0, null)}; }, token: function(stream, state) { @@ -103,198 +278,15 @@ CodeMirror.defineMode("css", function(config, parserConfig) { type = style[1]; style = style[0]; } - - // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50) - // - // rule** or **ruleset: - // A selector + braces combo, or an at-rule. - // - // declaration block: - // A sequence of declarations. - // - // declaration: - // A property + colon + value combo. - // - // property value: - // The entire value of a property. - // - // component value: - // A single piece of a property value. Like the 5px in - // text-shadow: 0 0 5px blue;. Can also refer to things that are - // multiple terms, like the 1-4 terms that make up the background-size - // portion of the background shorthand. - // - // term: - // The basic unit of author-facing CSS, like a single number (5), - // dimension (5px), string ("foo"), or function. Officially defined - // by the CSS 2.1 grammar (look for the 'term' production) - // - // - // simple selector: - // A single atomic selector, like a type selector, an attr selector, a - // class selector, etc. - // - // compound selector: - // One or more simple selectors without a combinator. div.example is - // compound, div > .example is not. - // - // complex selector: - // One or more compound selectors chained with combinators. - // - // combinator: - // The parts of selectors that express relationships. There are four - // currently - the space (descendant combinator), the greater-than - // bracket (child combinator), the plus sign (next sibling combinator), - // and the tilda (following sibling combinator). - // - // sequence of selectors: - // One or more of the named type of selector chained with commas. - - // Changing style returned based on context - var context = state.stack[state.stack.length-1]; - if (style == "variable") { - if (type == "variable-definition") state.stack.push("propertyValue"); - return state.lastToken = "variable-2"; - } else if (style == "property") { - var word = stream.current().toLowerCase(); - if (context == "propertyValue") { - if (valueKeywords.hasOwnProperty(word)) { - style = "string-2"; - } else if (colorKeywords.hasOwnProperty(word)) { - style = "keyword"; - } else { - style = "variable-2"; - } - } else if (context == "rule") { - if (!propertyKeywords.hasOwnProperty(word)) { - style += " error"; - } - } else if (context == "block") { - // if a value is present in both property, value, or color, the order - // of preference is property -> color -> value - if (propertyKeywords.hasOwnProperty(word)) { - style = "property"; - } else if (colorKeywords.hasOwnProperty(word)) { - style = "keyword"; - } else if (valueKeywords.hasOwnProperty(word)) { - style = "string-2"; - } else { - style = "tag"; - } - } else if (!context || context == "@media{") { - style = "tag"; - } else if (context == "@media") { - if (atMediaTypes[stream.current()]) { - style = "attribute"; // Known attribute - } else if (/^(only|not)$/.test(word)) { - style = "keyword"; - } else if (word == "and") { - style = "error"; // "and" is only allowed in @mediaType - } else if (atMediaFeatures.hasOwnProperty(word)) { - style = "error"; // Known property, should be in @mediaType( - } else { - // Unknown, expecting keyword or attribute, assuming attribute - style = "attribute error"; - } - } else if (context == "@mediaType") { - if (atMediaTypes.hasOwnProperty(word)) { - style = "attribute"; - } else if (word == "and") { - style = "operator"; - } else if (/^(only|not)$/.test(word)) { - style = "error"; // Only allowed in @media - } else { - // Unknown attribute or property, but expecting property (preceded - // by "and"). Should be in parentheses - style = "error"; - } - } else if (context == "@mediaType(") { - if (propertyKeywords.hasOwnProperty(word)) { - // do nothing, remains "property" - } else if (atMediaTypes.hasOwnProperty(word)) { - style = "error"; // Known property, should be in parentheses - } else if (word == "and") { - style = "operator"; - } else if (/^(only|not)$/.test(word)) { - style = "error"; // Only allowed in @media - } else { - style += " error"; - } - } else if (context == "@import") { - style = "tag"; - } else { - style = "error"; - } - } else if (style == "atom") { - if(!context || context == "@media{" || context == "block") { - style = "builtin"; - } else if (context == "propertyValue") { - if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { - style += " error"; - } - } else { - style = "error"; - } - } else if (context == "@media" && type == "{") { - style = "error"; - } - - // Push/pop context stack - if (type == "{") { - if (context == "@media" || context == "@mediaType") { - state.stack[state.stack.length-1] = "@media{"; - } - else { - var newContext = allowNested ? "block" : "rule"; - state.stack.push(newContext); - } - } - else if (type == "}") { - if (context == "interpolation") style = "operator"; - // Pop off end of array until { is reached - while(state.stack.length){ - var removed = state.stack.pop(); - if(removed.indexOf("{") > -1 || removed == "block" || removed == "rule"){ - break; - } - } - } - else if (type == "interpolation") state.stack.push("interpolation"); - else if (type == "@media") state.stack.push("@media"); - else if (type == "@import") state.stack.push("@import"); - else if (context == "@media" && /\b(keyword|attribute)\b/.test(style)) - state.stack[state.stack.length-1] = "@mediaType"; - else if (context == "@mediaType" && stream.current() == ",") - state.stack[state.stack.length-1] = "@media"; - else if (type == "(") { - if (context == "@media" || context == "@mediaType") { - // Make sure @mediaType is used to avoid error on { - state.stack[state.stack.length-1] = "@mediaType"; - state.stack.push("@mediaType("); - } - else state.stack.push("("); - } - else if (type == ")") { - // Pop off end of array until ( is reached - while(state.stack.length){ - var removed = state.stack.pop(); - if(removed.indexOf("(") > -1){ - break; - } - } - } - else if (type == ":" && state.lastToken == "property") state.stack.push("propertyValue"); - else if (context == "propertyValue" && type == ";") state.stack.pop(); - else if (context == "@import" && type == ";") state.stack.pop(); - - return state.lastToken = style; + override = style; + state.state = states[state.state](type, stream, state); + return override; }, indent: function(state, textAfter) { - var n = state.stack.length; - if (/^\}/.test(textAfter)) - n -= state.stack[n-1] == "propertyValue" ? 2 : 1; - return state.baseIndent + n * indentUnit; + var cx = state.context; + if (/^\}/.test(textAfter)) cx = cx.prev; + return cx.indent; }, electricChars: "}", @@ -313,12 +305,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return keys; } - var atMediaTypes = keySet([ + var mediaTypes = keySet([ "all", "aural", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "embossed" ]); - var atMediaFeatures = keySet([ + var mediaFeatures = keySet([ "width", "min-width", "max-width", "height", "min-height", "max-height", "device-width", "min-device-width", "max-device-width", "device-height", "min-device-height", "max-device-height", "aspect-ratio", @@ -545,67 +537,45 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return ["comment", "comment"]; } + function tokenSGMLComment(stream, state) { + if (stream.skipTo("-->")) { + stream.match("-->"); + state.tokenize = null; + } else { + stream.skipToEnd(); + } + return ["comment", "comment"]; + } + CodeMirror.defineMIME("text/css", { - atMediaTypes: atMediaTypes, - atMediaFeatures: atMediaFeatures, + mediaTypes: mediaTypes, + mediaFeatures: mediaFeatures, propertyKeywords: propertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, - hooks: { + tokenHooks: { "<": function(stream, state) { - function tokenSGMLComment(stream, state) { - var dashes = 0, ch; - while ((ch = stream.next()) != null) { - if (dashes >= 2 && ch == ">") { - state.tokenize = null; - break; - } - dashes = (ch == "-") ? dashes + 1 : 0; - } - return ["comment", "comment"]; - } - if (stream.eat("!")) { - state.tokenize = tokenSGMLComment; - return tokenSGMLComment(stream, state); - } + if (!stream.match("!--")) return false; + state.tokenize = tokenSGMLComment; + return tokenSGMLComment(stream, state); }, "/": function(stream, state) { - if (stream.eat("*")) { - state.tokenize = tokenCComment; - return tokenCComment(stream, state); - } - return false; + if (!stream.eat("*")) return false; + state.tokenize = tokenCComment; + return tokenCComment(stream, state); } }, name: "css" }); CodeMirror.defineMIME("text/x-scss", { - atMediaTypes: atMediaTypes, - atMediaFeatures: atMediaFeatures, + mediaTypes: mediaTypes, + mediaFeatures: mediaFeatures, propertyKeywords: propertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, allowNested: true, - hooks: { - ":": function(stream) { - if (stream.match(/\s*{/)) { - return [null, "{"]; - } - return false; - }, - "$": function(stream) { - stream.match(/^[\w-]+/); - if (stream.peek() == ":") { - return ["variable", "variable-definition"]; - } - return ["variable", "variable"]; - }, - ",": function(stream, state) { - if (state.stack[state.stack.length - 1] == "propertyValue" && stream.match(/^ *\$/, false)) { - return ["operator", ";"]; - } - }, + tokenHooks: { "/": function(stream, state) { if (stream.eat("/")) { stream.skipToEnd(); @@ -617,13 +587,20 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return ["operator", "operator"]; } }, + ":": function(stream) { + if (stream.match(/\s*{/)) + return [null, "{"]; + return false; + }, + "$": function(stream) { + stream.match(/^[\w-]+/); + if (stream.match(/^\s*:/, false)) + return ["variable-2", "variable-definition"]; + return ["variable-2", "variable"]; + }, "#": function(stream) { - if (stream.eat("{")) { - return ["operator", "interpolation"]; - } else { - stream.eatWhile(/[\w\\\-]/); - return ["atom", "hash"]; - } + if (!stream.eat("{")) return false; + return [null, "interpolation"]; } }, name: "css" diff --git a/mode/css/scss_test.js b/mode/css/scss_test.js index ec66473f29..6218974b12 100644 --- a/mode/css/scss_test.js +++ b/mode/css/scss_test.js @@ -1,5 +1,5 @@ (function() { - var mode = CodeMirror.getMode({tabSize: 1}, "text/x-scss"); + var mode = CodeMirror.getMode({indentUnit: 1}, "text/x-scss"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } @@ -65,13 +65,13 @@ "[tag p] { [tag a] { [property color]:[atom #000]; } }"); MT('interpolation_in_property', - "[tag foo] { [operator #{][variable-2 $hello][operator }:][number 2]; }"); + "[tag foo] { #{[variable-2 $hello]}:[number 2]; }"); MT('interpolation_in_selector', - "[tag foo][operator #{][variable-2 $hello][operator }] { [property color]:[atom #000]; }"); + "[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }"); MT('interpolation_error', - "[tag foo][operator #{][error foo][operator }] { [property color]:[atom #000]; }"); + "[tag foo]#{[error foo]} { [property color]:[atom #000]; }"); MT("divide_operator", "[tag foo] { [property width]:[number 4] [operator /] [number 2] }"); @@ -80,7 +80,7 @@ "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }"); IT('mixin', - "@mixin container[1 (][2 $a: 10][1 , ][2 $b: 10][1 , ][2 $c: 10]) [1 {]}"); + "@mixin container[1 ($a: 10, $b: 10, $c: 10]) [1 {]}"); IT('nested', "foo [1 { bar ][2 { ][1 } ]}"); diff --git a/mode/css/test.js b/mode/css/test.js index 76b94327bc..2fa451a624 100644 --- a/mode/css/test.js +++ b/mode/css/test.js @@ -1,12 +1,8 @@ (function() { - var mode = CodeMirror.getMode({tabSize: 1}, "css"); + var mode = CodeMirror.getMode({indentUnit: 1}, "css"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1)); } - // Requires at least one media query - MT("atMediaEmpty", - "[def @media] [error {] }"); - MT("atMediaMultiple", "[def @media] [keyword not] [attribute screen] [operator and] ([property color]), [keyword not] [attribute print] [operator and] ([property color]) { }"); @@ -58,7 +54,7 @@ // Soft error, because "foobarhello" is not a known property or type. MT("atMediaUnknownProperty", - "[def @media] [attribute screen] [operator and] ([property&error foobarhello]) { }"); + "[def @media] [attribute screen] [operator and] ([error foobarhello]) { }"); // Make sure nesting works with media queries MT("atMediaMaxWidthNested", @@ -125,6 +121,10 @@ MT("commentSGML", "[comment ]"); + MT("commentSGML2", + "[comment ] [tag div] {}"); + IT("tagSelector", "strong, em [1 { background][2 : rgba][3 (255, 255, 0, .2][2 )][1 ;]}"); diff --git a/test/mode_test.js b/test/mode_test.js index 4b63cf517c..08deab74b2 100644 --- a/test/mode_test.js +++ b/test/mode_test.js @@ -132,11 +132,10 @@ if (line == "" && mode.blankLine) mode.blankLine(state); /* Start copied code from CodeMirror.highlight */ while (!stream.eol()) { - var compare = mode.token(stream, state), substr = stream.current(); - if(compareIndentation) compare = mode.indent(state) || null; + var compare = mode.token(stream, state), substr = stream.current(); + if (compareIndentation) compare = mode.indent(state) || null; else if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' '); - - stream.start = stream.pos; + stream.start = stream.pos; if (pos && st[pos-2] == compare && !newLine) { st[pos-1] += substr; } else if (substr) { From e42edbc43e338b4024811de4c454b96da5002597 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 10 Dec 2013 15:58:50 +0100 Subject: [PATCH 065/155] [css mode] Refactor, step 3 (Less support) --- mode/css/css.js | 120 ++++++++++++++++++++++++--------------- mode/css/index.html | 2 +- mode/css/less.html | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++ mode/css/less_test.js | 48 ++++++++++++++++ mode/css/scss.html | 2 +- mode/css/scss_test.js | 18 ++++-- mode/css/test.js | 56 ++----------------- mode/gfm/test.js | 2 +- mode/index.html | 2 +- test/index.html | 1 + 10 files changed, 298 insertions(+), 105 deletions(-) create mode 100644 mode/css/less.html create mode 100644 mode/css/less_test.js diff --git a/mode/css/css.js b/mode/css/css.js index b8081e0530..df4ab4d2c6 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -119,6 +119,16 @@ CodeMirror.defineMode("css", function(config, parserConfig) { // Parser + function wordAsValue(stream) { + var word = stream.current().toLowerCase(); + if (valueKeywords.hasOwnProperty(word)) + override = "atom"; + else if (colorKeywords.hasOwnProperty(word)) + override = "keyword"; + else + override = "variable"; + } + var states = {}; states.top = function(type, _stream, state) { @@ -135,14 +145,17 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } else if (type == "word") { override = "tag"; } else if (type == "variable-definition") { - return pushContext(state, "maybeprop"); + return "maybeprop"; } else if (type == "interpolation") { return pushContext(state, "interpolation"); + } else if (type == ":") { + return "pseudo"; } else if (allowNested && type == "(") { return pushContext(state, "params"); } return state.context.type; }; + states.block = function(type, stream, state) { if (type == "word") { if (propertyKeywords.hasOwnProperty(stream.current().toLowerCase())) { @@ -164,10 +177,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return states.top(type, stream, state); } }; + states.maybeprop = function(type, _stream, state) { if (type == ":") return pushContext(state, "prop"); else return state.context.type; }; + states.prop = function(type, stream, state) { if (type == ";") return popContext(state); if (type == "}" || type == "{") return popAndPass(type, stream, state); @@ -176,91 +191,71 @@ CodeMirror.defineMode("css", function(config, parserConfig) { if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { override += " error"; } else if (type == "word") { - var word = stream.current().toLowerCase(); - if (valueKeywords.hasOwnProperty(word)) - override = "string-2"; - else if (colorKeywords.hasOwnProperty(word)) - override = "keyword"; - else - override = "variable-2"; + wordAsValue(stream); } else if (type == "interpolation") { return pushContext(state, "interpolation"); } return "prop"; }; + states.parens = function(type, stream, state) { if (type == "{" || type == "}") return popAndPass(type, stream, state); if (type == ")") return popContext(state); return "parens"; }; - states.media = function(type, stream, state) { - if (type == "(") return "media_parens"; - if (type == "}") return popAndPass(type, stream, state); - if (type == "{") return popContext(state) && pushContext(state, "top"); + + states.pseudo = function(type, stream, state) { if (type == "word") { - var word = stream.current(); - if (mediaTypes[word]) { - override = "attribute"; - return "mediatype"; - } else if (/^(only|not)$/.test(word)) { - override = "keyword"; - return "mediatype"; - } else if (word == "and" || mediaFeatures.hasOwnProperty(word)) { - override = "error"; - return "media"; - } else { - override = "attribute error"; - return "mediatype"; - } + override = "variable-2"; + return state.context.type; } - return "media"; + return pass(type, stream, state); }; - states.mediatype = function(type, stream, state) { - if (type == ",") return "media"; + + states.media = function(type, stream, state) { if (type == "(") return pushContext(state, "media_parens"); if (type == "}") return popAndPass(type, stream, state); - if (type == "{") return popContext(state) && pushContext(state, "top"); + if (type == "{") return popContext(state) && pushContext(state, allowNested ? "block" : "top"); + if (type == "word") { - var word = stream.current(); - if (mediaTypes.hasOwnProperty(word)) + var word = stream.current().toLowerCase(); + if (word == "only" || word == "not" || word == "and") + override = "keyword"; + else if (mediaTypes.hasOwnProperty(word)) override = "attribute"; - else if (word == "and") - override = "operator"; + else if (mediaFeatures.hasOwnProperty(word)) + override = "property"; else override = "error"; } - return "mediatype"; + return state.context.type; }; + states.media_parens = function(type, stream, state) { if (type == ")") return popContext(state); if (type == "{" || type == "}") return popAndPass(type, stream, state, 2); - if (type == "word") { - var word = stream.current().toLowerCase(); - if (propertyKeywords.hasOwnProperty(word)) - override = "property"; - else if (word == "and") - override = "operator"; - else - override = "error"; - } - return "media_parens"; + return states.media(type, stream, state); }; + states.at = function(type, stream, state) { if (type == ";") return popContext(state); if (type == "{" || type == "}") return popAndPass(type, stream, state); if (type == "word") override = "tag"; - else if (type == "hash") override = "atom"; + else if (type == "hash") override = "builtin"; return "at"; }; + states.interpolation = function(type, stream, state) { if (type == "}") return popContext(state); if (type == "{" || type == ";") return popAndPass(type, stream, state); if (type != "variable") override = "error"; return "interpolation"; }; + states.params = function(type, stream, state) { if (type == ")") return popContext(state); if (type == "{" || type == "}") return popAndPass(type, stream, state); + if (type == "word") wordAsValue(stream); return "params"; }; @@ -605,4 +600,37 @@ CodeMirror.defineMode("css", function(config, parserConfig) { }, name: "css" }); + + CodeMirror.defineMIME("text/x-less", { + mediaTypes: mediaTypes, + mediaFeatures: mediaFeatures, + propertyKeywords: propertyKeywords, + colorKeywords: colorKeywords, + valueKeywords: valueKeywords, + allowNested: true, + tokenHooks: { + "/": function(stream, state) { + if (stream.eat("/")) { + stream.skipToEnd(); + return ["comment", "comment"]; + } else if (stream.eat("*")) { + state.tokenize = tokenCComment; + return tokenCComment(stream, state); + } else { + return ["operator", "operator"]; + } + }, + "@": function(stream) { + if (stream.match(/^(media|import)\b/, false)) return false; + stream.eatWhile(/[\w\\\-]/); + if (stream.match(/^\s*:/, false)) + return ["variable-2", "variable-definition"]; + return ["variable-2", "variable"]; + }, + "&": function() { + return ["atom", "atom"]; + } + }, + name: "css" + }); })(); diff --git a/mode/css/index.html b/mode/css/index.html index 1d1865e2e3..80ef518e90 100644 --- a/mode/css/index.html +++ b/mode/css/index.html @@ -63,7 +63,7 @@ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {}); -

MIME types defined: text/css.

+

MIME types defined: text/css, text/x-scss (demo), text/x-less (demo).

Parsing/Highlighting Tests: normal, verbose.

diff --git a/mode/css/less.html b/mode/css/less.html new file mode 100644 index 0000000000..1030ca43fe --- /dev/null +++ b/mode/css/less.html @@ -0,0 +1,152 @@ + + +CodeMirror: LESS mode + + + + + + + + + + +
+

LESS mode

+
+ + +

The LESS mode is a sub-mode of the CSS mode (defined in css.js.

+ +

Parsing/Highlighting Tests: normal, verbose.

+
diff --git a/mode/css/less_test.js b/mode/css/less_test.js new file mode 100644 index 0000000000..9b065f23d8 --- /dev/null +++ b/mode/css/less_test.js @@ -0,0 +1,48 @@ +(function() { + "use strict"; + + var mode = CodeMirror.getMode({indentUnit: 1}, "text/x-less"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); } + + MT("variable", + "[variable-2 @base]: [atom #f04615];", + "[qualifier .class] {", + " [property width]: [variable percentage]([number 0.5]); [comment // returns `50%`]", + " [property color]: [variable saturate]([variable-2 @base], [number 5%]);", + "}"); + + MT("amp", + "[qualifier .child], [qualifier .sibling] {", + " [qualifier .parent] [atom &] {", + " [property color]: [keyword black];", + " }", + " [atom &] + [atom &] {", + " [property color]: [keyword red];", + " }", + "}"); + + MT("mixin", + "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {", + " [property color]: [variable darken]([variable-2 @color], [number 10%]);", + "}", + "[qualifier .mixin] ([variable light]; [variable-2 @color]) {", + " [property color]: [variable lighten]([variable-2 @color], [number 10%]);", + "}", + "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {", + " [property display]: [atom block];", + "}", + "[variable-2 @switch]: [variable light];", + "[qualifier .class] {", + " [qualifier .mixin]([variable-2 @switch]; [atom #888]);", + "}"); + + MT("nest", + "[qualifier .one] {", + " [def @media] ([property width]: [number 400px]) {", + " [property font-size]: [number 1.2em];", + " [def @media] [attribute print] [keyword and] [property color] {", + " [property color]: [keyword blue];", + " }", + " }", + "}"); +})(); diff --git a/mode/css/scss.html b/mode/css/scss.html index 72781c0dc3..0677d08fe1 100644 --- a/mode/css/scss.html +++ b/mode/css/scss.html @@ -150,7 +150,7 @@ }); -

MIME types defined: text/scss.

+

The SCSS mode is a sub-mode of the CSS mode (defined in css.js.

Parsing/Highlighting Tests: normal, verbose.

diff --git a/mode/css/scss_test.js b/mode/css/scss_test.js index 6218974b12..ed5de83801 100644 --- a/mode/css/scss_test.js +++ b/mode/css/scss_test.js @@ -4,19 +4,19 @@ function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } MT('url_with_quotation', - "[tag foo] { [property background]:[string-2 url]([string test.jpg]) }"); + "[tag foo] { [property background]:[atom url]([string test.jpg]) }"); MT('url_with_double_quotes', - "[tag foo] { [property background]:[string-2 url]([string \"test.jpg\"]) }"); + "[tag foo] { [property background]:[atom url]([string \"test.jpg\"]) }"); MT('url_with_single_quotes', - "[tag foo] { [property background]:[string-2 url]([string \'test.jpg\']) }"); + "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) }"); MT('string', "[def @import] [string \"compass/css3\"]"); MT('important_keyword', - "[tag foo] { [property background]:[string-2 url]([string \'test.jpg\']) [keyword !important] }"); + "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) [keyword !important] }"); MT('variable', "[variable-2 $blue]:[atom #333]"); @@ -80,7 +80,7 @@ "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }"); IT('mixin', - "@mixin container[1 ($a: 10, $b: 10, $c: 10]) [1 {]}"); + "[1 @mixin container ($a: 10, $b: 10, $c: 10) {]}"); IT('nested', "foo [1 { bar ][2 { ][1 } ]}"); @@ -90,4 +90,12 @@ IT('parentheses', "foo [1 { color][2 : darken][3 ($blue, 9%][2 )][1 ; ]}"); + + IT('vardef', + "$name[1 : 'val'];", + "tag [1 {]", + "[1 inner ][2 {]", + "[2 margin][3 : 3px][2 ;]", + "[2 ][1 }]", + "}"); })(); diff --git a/mode/css/test.js b/mode/css/test.js index 2fa451a624..cbfbef46cc 100644 --- a/mode/css/test.js +++ b/mode/css/test.js @@ -3,62 +3,18 @@ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1)); } - MT("atMediaMultiple", - "[def @media] [keyword not] [attribute screen] [operator and] ([property color]), [keyword not] [attribute print] [operator and] ([property color]) { }"); - - MT("atMediaCheckStack", - "[def @media] [attribute screen] { } [tag foo] { }"); - - MT("atMediaCheckStack", - "[def @media] [attribute screen] ([property color]) { } [tag foo] { }"); - - MT("atMediaPropertyOnly", - "[def @media] ([property color]) { } [tag foo] { }"); - - MT("atMediaCheckStackInvalidAttribute", - "[def @media] [attribute&error foobarhello] { [tag foo] { } }"); - - MT("atMediaCheckStackInvalidAttribute", - "[def @media] [attribute&error foobarhello] { } [tag foo] { }"); - - // Error, because "and" is only allowed immediately preceding a media expression - MT("atMediaInvalidAttribute", - "[def @media] [attribute&error foobarhello] { }"); - - // Error, because "and" is only allowed immediately preceding a media expression - MT("atMediaInvalidAnd", - "[def @media] [error and] [attribute screen] { }"); - - // Error, because "not" is only allowed as the first item in each media query - MT("atMediaInvalidNot", - "[def @media] [attribute screen] [error not] ([error not]) { }"); - - // Error, because "only" is only allowed as the first item in each media query - MT("atMediaInvalidOnly", - "[def @media] [attribute screen] [error only] ([error only]) { }"); - // Error, because "foobarhello" is neither a known type or property, but // property was expected (after "and"), and it should be in parenthese. MT("atMediaUnknownType", - "[def @media] [attribute screen] [operator and] [error foobarhello] { }"); - - // Error, because "color" is not a known type, but is a known property, and - // should be in parentheses. - MT("atMediaInvalidType", - "[def @media] [attribute screen] [operator and] [error color] { }"); - - // Error, because "print" is not a known property, but is a known type, - // and should not be in parenthese. - MT("atMediaInvalidProperty", - "[def @media] [attribute screen] [operator and] ([error print]) { }"); + "[def @media] [attribute screen] [keyword and] [error foobarhello] { }"); // Soft error, because "foobarhello" is not a known property or type. MT("atMediaUnknownProperty", - "[def @media] [attribute screen] [operator and] ([error foobarhello]) { }"); + "[def @media] [attribute screen] [keyword and] ([error foobarhello]) { }"); // Make sure nesting works with media queries MT("atMediaMaxWidthNested", - "[def @media] [attribute screen] [operator and] ([property max-width]: [number 25px]) { [tag foo] { } }"); + "[def @media] [attribute screen] [keyword and] ([property max-width]: [number 25px]) { [tag foo] { } }"); MT("tagSelector", "[tag foo] { }"); @@ -73,7 +29,7 @@ "[tag foo] { [property margin]: [number 0] } [tag bar] { }"); MT("tagStringNoQuotes", - "[tag foo] { [property font-family]: [variable-2 hello] [variable-2 world]; }"); + "[tag foo] { [property font-family]: [variable hello] [variable world]; }"); MT("tagStringDouble", "[tag foo] { [property font-family]: [string \"hello world\"]; }"); @@ -107,7 +63,7 @@ "[tag foo] { [property padding]: [number 5px]; }"); MT("tagVendor", - "[tag foo] { [meta -foo-][property box-sizing]: [meta -foo-][string-2 border-box]; }"); + "[tag foo] { [meta -foo-][property box-sizing]: [meta -foo-][atom border-box]; }"); MT("tagBogusProperty", "[tag foo] { [property&error barhelloworld]: [number 0]; }"); @@ -116,7 +72,7 @@ "[tag foo] { [property margin]: [number 0]; [property padding]: [number 0]; }"); MT("tagTwoPropertiesURL", - "[tag foo] { [property background]: [string-2 url]([string //example.com/foo.png]); [property padding]: [number 0]; }"); + "[tag foo] { [property background]: [atom url]([string //example.com/foo.png]); [property padding]: [number 0]; }"); MT("commentSGML", "[comment ]"); diff --git a/mode/gfm/test.js b/mode/gfm/test.js index 4cf46fe5a5..7d17517e18 100644 --- a/mode/gfm/test.js +++ b/mode/gfm/test.js @@ -114,7 +114,7 @@ MT("notALink", "[comment ```css]", - "[tag foo] {[property color][operator :][keyword black];}", + "[tag foo] {[property color]:[keyword black];}", "[comment ```][link http://www.example.com/]"); MT("notALink", diff --git a/mode/index.html b/mode/index.html index 81abc42d8c..e332f84f70 100644 --- a/mode/index.html +++ b/mode/index.html @@ -61,7 +61,7 @@
  • JavaScript
  • Jinja2
  • Julia
  • -
  • LESS
  • +
  • LESS
  • LiveScript
  • Lua
  • Markdown (GitHub-flavour)
  • diff --git a/test/index.html b/test/index.html index bec6c0bd29..ac12b2274c 100644 --- a/test/index.html +++ b/test/index.html @@ -78,6 +78,7 @@ + From 6ad770675b70b08d0523e22aea1ef17161d3f8dc Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 12:20:33 +0100 Subject: [PATCH 066/155] [css mode] Add a hintWords helper value --- addon/hint/show-hint.js | 1 - mode/css/css.js | 23 +++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index 31e919dcbb..76f020cf0a 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -302,7 +302,6 @@ CodeMirror.registerHelper("hint", "fromList", function(cm, options) { var cur = cm.getCursor(), token = cm.getTokenAt(cur); - if (!/^[\w$_]*$/.test(token.string)) return null; var found = []; for (var i = 0; i < options.words.length; i++) { var word = options.words[i]; diff --git a/mode/css/css.js b/mode/css/css.js index df4ab4d2c6..2a211dc3c7 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -300,12 +300,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return keys; } - var mediaTypes = keySet([ + var mediaTypes_ = [ "all", "aural", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "embossed" - ]); + ], mediaTypes = keySet(mediaTypes_); - var mediaFeatures = keySet([ + var mediaFeatures_ = [ "width", "min-width", "max-width", "height", "min-height", "max-height", "device-width", "min-device-width", "max-device-width", "device-height", "min-device-height", "max-device-height", "aspect-ratio", @@ -314,9 +314,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "max-color", "color-index", "min-color-index", "max-color-index", "monochrome", "min-monochrome", "max-monochrome", "resolution", "min-resolution", "max-resolution", "scan", "grid" - ]); + ], mediaFeatures = keySet(mediaFeatures_); - var propertyKeywords = keySet([ + var propertyKeywords_ = [ "align-content", "align-items", "align-self", "alignment-adjust", "alignment-baseline", "anchor-point", "animation", "animation-delay", "animation-direction", "animation-duration", "animation-iteration-count", @@ -404,9 +404,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering", "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal", "glyph-orientation-vertical", "kerning", "text-anchor", "writing-mode" - ]); + ], propertyKeywords = keySet(propertyKeywords_); - var colorKeywords = keySet([ + var colorKeywords_ = [ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", @@ -433,9 +433,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen" - ]); + ], colorKeywords = keySet(colorKeywords_); - var valueKeywords = keySet([ + var valueKeywords_ = [ "above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", @@ -518,7 +518,10 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "visibleStroke", "visual", "w-resize", "wait", "wave", "wider", "window", "windowframe", "windowtext", "x-large", "x-small", "xor", "xx-large", "xx-small" - ]); + ], valueKeywords = keySet(valueKeywords_); + + var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_).concat(colorKeywords_).concat(valueKeywords_); + CodeMirror.registerHelper("hintWords", "css", allWords); function tokenCComment(stream, state) { var maybeEnd = false, ch; From fad3f49c26d706105969cc2d80919766a855ebbe Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 12:52:50 +0100 Subject: [PATCH 067/155] [css-hint addon] Rewrite to work with overhauled css mode --- addon/hint/css-hint.js | 62 +++++++++++++++++++++++--------------------------- doc/manual.html | 2 +- mode/css/css.js | 8 ++++--- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/addon/hint/css-hint.js b/addon/hint/css-hint.js index 2b15300d0c..43bdf5de02 100644 --- a/addon/hint/css-hint.js +++ b/addon/hint/css-hint.js @@ -1,50 +1,46 @@ (function () { "use strict"; - function getHints(cm) { + var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1, + "first-letter": 1, "first-line": 1, "first-child": 1, + before: 1, after: 1, lang: 1}; + + CodeMirror.registerHelper("hint", "css", function(cm) { var cur = cm.getCursor(), token = cm.getTokenAt(cur); var inner = CodeMirror.innerMode(cm.getMode(), token.state); if (inner.mode.name != "css") return; - // If it's not a 'word-style' token, ignore the token. - if (!/^[\w$_-]*$/.test(token.string)) { - token = { - start: cur.ch, end: cur.ch, string: "", state: token.state, - type: null - }; - var stack = token.state.stack; - var lastToken = stack && stack.length > 0 ? stack[stack.length - 1] : ""; - if (token.string == ":" || lastToken.indexOf("property") == 0) - token.type = "variable"; - else if (token.string == "{" || lastToken.indexOf("rule") == 0) - token.type = "property"; + var word = token.string, start = token.start, end = token.end; + if (/[^\w$_-]/.test(word)) { + word = ""; start = end = cur.ch; } - if (!token.type) - return; - var spec = CodeMirror.resolveMode("text/css"); - var keywords = null; - if (token.type.indexOf("property") == 0) - keywords = spec.propertyKeywords; - else if (token.type.indexOf("variable") == 0) - keywords = spec.valueKeywords; - - if (!keywords) - return; var result = []; - for (var name in keywords) { - if (name.indexOf(token.string) == 0 /* > -1 */) - result.push(name); + function add(keywords) { + for (var name in keywords) + if (!word || name.indexOf(word) == 0) + result.push(name); } - return { + var st = token.state.state; + if (st == "pseudo" || token.type == "variable-3") { + add(pseudoClasses); + } else if (st == "block" || st == "maybeprop") { + add(spec.propertyKeywords); + } else if (st == "prop" || st == "parens" || st == "at" || st == "params") { + add(spec.valueKeywords); + add(spec.colorKeywords); + } else if (st == "media" || st == "media_parens") { + add(spec.mediaTypes); + add(spec.mediaFeatures); + } + + if (result.length) return { list: result, - from: CodeMirror.Pos(cur.line, token.start), - to: CodeMirror.Pos(cur.line, token.end) + from: CodeMirror.Pos(cur.line, start), + to: CodeMirror.Pos(cur.line, end) }; - } - - CodeMirror.registerHelper("hint", "css", getHints); + }); })(); diff --git a/doc/manual.html b/doc/manual.html index 0dc120ddf3..957ab347c5 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -2159,7 +2159,7 @@

    Static properties

    the demo.
    hint/css-hint.js
    -
    A minimal hinting function for CSS code. +
    A hinting function for CSS, SCSS, or LESS code. Defines CodeMirror.hint.css.
    hint/python-hint.js
    diff --git a/mode/css/css.js b/mode/css/css.js index 2a211dc3c7..11394077ca 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -206,7 +206,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { states.pseudo = function(type, stream, state) { if (type == "word") { - override = "variable-2"; + override = "variable-3"; return state.context.type; } return pass(type, stream, state); @@ -601,7 +601,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return [null, "interpolation"]; } }, - name: "css" + name: "css", + helperType: "scss" }); CodeMirror.defineMIME("text/x-less", { @@ -634,6 +635,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return ["atom", "atom"]; } }, - name: "css" + name: "css", + helperType: "less" }); })(); From c1064c8d7b1c18547bfeeaece59b5992f6b64195 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 14:20:28 +0100 Subject: [PATCH 068/155] [css mode] Fix bad context management Closes #2046 --- mode/css/css.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index 11394077ca..1723fe84ed 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -178,9 +178,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } }; - states.maybeprop = function(type, _stream, state) { + states.maybeprop = function(type, stream, state) { if (type == ":") return pushContext(state, "prop"); - else return state.context.type; + return pass(type, stream, state); }; states.prop = function(type, stream, state) { @@ -280,7 +280,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { indent: function(state, textAfter) { var cx = state.context; - if (/^\}/.test(textAfter)) cx = cx.prev; + if (/^\}/.test(textAfter) && cx.prev) cx = cx.prev; return cx.indent; }, From b29f875bf71f2fcef9ce35f5b77c5d67adc2b5bf Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 14:40:01 +0100 Subject: [PATCH 069/155] [css mode] Tweak indentation when opening @media block on new line Closes #2047 --- mode/css/css.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mode/css/css.js b/mode/css/css.js index 1723fe84ed..478bc4132f 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -281,6 +281,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { indent: function(state, textAfter) { var cx = state.context; if (/^\}/.test(textAfter) && cx.prev) cx = cx.prev; + if (/^\{/.test(textAfter) && cx.type == "media") cx = cx.prev; return cx.indent; }, From febe3acfe42bc1f94279fe6dd81a93946d12fa32 Mon Sep 17 00:00:00 2001 From: eborden Date: Wed, 11 Dec 2013 11:44:31 -0500 Subject: [PATCH 070/155] [javascript-lint addon] Put JSHint options in a sub-object Passing options causes issues in jshint since lint options are also passed. These options cause bad option errors in the annotation list. --- addon/lint/lint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/lint/lint.js b/addon/lint/lint.js index b502ee41f1..6121735d3f 100644 --- a/addon/lint/lint.js +++ b/addon/lint/lint.js @@ -112,7 +112,7 @@ if (options.async) options.getAnnotations(cm, updateLinting, options); else - updateLinting(cm, options.getAnnotations(cm.getValue(), options)); + updateLinting(cm, options.getAnnotations(cm.getValue(), options.options)); } function updateLinting(cm, annotationsNotSorted) { From d3c3a5a2b8df669ed12fe90df4d62a143c0a9d25 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 14:47:16 +0100 Subject: [PATCH 071/155] [css mode] Fix tokenization of paren-wrapped values Closes #2048 --- mode/css/css.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index 478bc4132f..265b5c9d48 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -69,14 +69,13 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) + if (ch == quote && !escaped) { + if (nonInclusive) stream.backUp(1); break; + } escaped = !escaped && ch == "\\"; } - if (!escaped) { - if (nonInclusive) stream.backUp(1); - state.tokenize = null; - } + if (!escaped) state.tokenize = null; return ret("string", "string"); }; } From 0b17e247b036bcb484f36c9f966ea8dbbcb6fa72 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 15:04:52 +0100 Subject: [PATCH 072/155] [css mode] Fix previous fix (d3c3a5a2b8df669ed12fe90df4d62a143c0a9d25) Issue #2048 --- mode/css/css.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index 265b5c9d48..cfeafbd5e8 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -65,17 +65,17 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } } - function tokenString(quote, nonInclusive) { + function tokenString(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) { - if (nonInclusive) stream.backUp(1); + if (quote == ")") stream.backUp(1); break; } escaped = !escaped && ch == "\\"; } - if (!escaped) state.tokenize = null; + if (ch == quote || !escaped && quote != ")") state.tokenize = null; return ret("string", "string"); }; } @@ -83,7 +83,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { function tokenParenthesized(stream, state) { stream.next(); // Must be '(' if (!stream.match(/\s*[\"\']/, false)) - state.tokenize = tokenString(")", true); + state.tokenize = tokenString(")"); else state.tokenize = null; return ret(null, "("); From a76bd4f2d28068f12aff0713dd0300a51eb8d3d3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 15:30:09 +0100 Subject: [PATCH 073/155] [css mode] Revise indentation model Issue #2049 --- mode/css/css.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index cfeafbd5e8..d1a1def6f7 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -97,8 +97,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { this.prev = prev; } - function pushContext(state, type) { - state.context = new Context(type, state.context.indent + indentUnit, state.context); + function pushContext(state, stream, type) { + state.context = new Context(type, stream.indentation() + indentUnit, state.context); return type; } @@ -130,15 +130,15 @@ CodeMirror.defineMode("css", function(config, parserConfig) { var states = {}; - states.top = function(type, _stream, state) { + states.top = function(type, stream, state) { if (type == "{") { - return pushContext(state, "block"); + return pushContext(state, stream, "block"); } else if (type == "}" && state.context.prev) { return popContext(state); } else if (type == "@media") { - return pushContext(state, "media"); + return pushContext(state, stream, "media"); } else if (type && type.charAt(0) == "@") { - return pushContext(state, "at"); + return pushContext(state, stream, "at"); } else if (type == "hash") { override = "builtin"; } else if (type == "word") { @@ -146,11 +146,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } else if (type == "variable-definition") { return "maybeprop"; } else if (type == "interpolation") { - return pushContext(state, "interpolation"); + return pushContext(state, stream, "interpolation"); } else if (type == ":") { return "pseudo"; } else if (allowNested && type == "(") { - return pushContext(state, "params"); + return pushContext(state, stream, "params"); } return state.context.type; }; @@ -178,21 +178,21 @@ CodeMirror.defineMode("css", function(config, parserConfig) { }; states.maybeprop = function(type, stream, state) { - if (type == ":") return pushContext(state, "prop"); + if (type == ":") return pushContext(state, stream, "prop"); return pass(type, stream, state); }; states.prop = function(type, stream, state) { if (type == ";") return popContext(state); if (type == "}" || type == "{") return popAndPass(type, stream, state); - if (type == "(") return pushContext(state, "parens"); + if (type == "(") return pushContext(state, stream, "parens"); if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { override += " error"; } else if (type == "word") { wordAsValue(stream); } else if (type == "interpolation") { - return pushContext(state, "interpolation"); + return pushContext(state, stream, "interpolation"); } return "prop"; }; @@ -212,9 +212,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) { }; states.media = function(type, stream, state) { - if (type == "(") return pushContext(state, "media_parens"); + if (type == "(") return pushContext(state, stream, "media_parens"); if (type == "}") return popAndPass(type, stream, state); - if (type == "{") return popContext(state) && pushContext(state, allowNested ? "block" : "top"); + if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top"); if (type == "word") { var word = stream.current().toLowerCase(); @@ -278,10 +278,16 @@ CodeMirror.defineMode("css", function(config, parserConfig) { }, indent: function(state, textAfter) { - var cx = state.context; - if (/^\}/.test(textAfter) && cx.prev) cx = cx.prev; - if (/^\{/.test(textAfter) && cx.type == "media") cx = cx.prev; - return cx.indent; + var cx = state.context, ch = textAfter && textAfter.charAt(0); + var indent = cx.indent; + if (cx.prev && + (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation") || + ch == ")" && (cx.type == "parens" || cx.type == "params" || cx.type == "media_parens") || + ch == "{" && (cx.type == "at" || cx.type == "media"))) { + indent = cx.indent - indentUnit; + cx = cx.prev; + } + return indent; }, electricChars: "}", From f943b027c8646a055835b6c23f5de655ed803f0d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 16:38:37 +0100 Subject: [PATCH 074/155] [javascript mode] Fix bug in parsing of array/iterator comprehension --- mode/javascript/javascript.js | 9 ++++----- mode/javascript/test.js | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index c49b873317..c113fcb433 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -15,7 +15,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var jsKeywords = { "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, - "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, + "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C, "var": kw("var"), "const": kw("var"), "let": kw("var"), "function": kw("function"), "catch": kw("catch"), "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), @@ -304,7 +304,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == ";") return cont(); if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse); if (type == "function") return cont(functiondef); - if (type == "for") return cont(pushlex("form"), forspec, poplex, statement, poplex); + if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); if (type == "variable") return cont(pushlex("stat"), maybelabel); if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex); @@ -370,7 +370,6 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); } function quasi(value) { - if (!value) debugger; if (value.slice(value.length - 2) != "${") return cont(); return cont(expression, continueQuasi); } @@ -474,7 +473,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex); } function forspec(type) { - if (type == "(") return cont(pushlex(")"), forspec1, expect(")")); + if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); } function forspec1(type) { if (type == "var") return cont(vardef, expect(";"), forspec2); @@ -538,7 +537,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return pass(expressionNoComma, maybeArrayComprehension); } function maybeArrayComprehension(type) { - if (type == "for") return pass(comprehension); + if (type == "for") return pass(comprehension, expect("]")); if (type == ",") return cont(commasep(expressionNoComma, "]")); return pass(commasep(expressionNoComma, "]")); } diff --git a/mode/javascript/test.js b/mode/javascript/test.js index 760152499e..8abd633542 100644 --- a/mode/javascript/test.js +++ b/mode/javascript/test.js @@ -63,10 +63,44 @@ MT("comprehension", "[keyword function] [variable f]() {", - " [[ [variable x] + [number 1] [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", + " [[([variable x] + [number 1]) [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", " ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] === [string 'blue']));", "}"); MT("quasi", "[variable re][string-2 `fofdlakj${][variable x] + ([variable re][string-2 `foo`]) + [number 1][string-2 }fdsa`] + [number 2]"); + + MT("indent_statement", + "[keyword var] [variable x] = [number 10]", + "[variable x] += [variable y] +", + " [atom Infinity]", + "[keyword debugger];"); + + MT("indent_if", + "[keyword if] ([number 1])", + " [keyword break];", + "[keyword else] [keyword if] ([number 2])", + " [keyword continue];", + "[keyword else]", + " [number 10];", + "[keyword if] ([number 1]) {", + " [keyword break];", + "} [keyword else] [keyword if] ([number 2]) {", + " [keyword continue];", + "} [keyword else] {", + " [number 10];", + "}"); + + MT("indent_for", + "[keyword for] ([keyword var] [variable i] = [number 0];", + " [variable i] < [number 100];", + " [variable i]++)", + " [variable doSomething]([variable i]);", + "[keyword debugger];"); + + MT("indent_c_style", + "[keyword function] [variable foo]()", + "{", + " [keyword debugger];", + "}"); })(); From d09247c69a051868fd966b5b8b19e91bc998dc8c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 12 Dec 2013 16:47:45 +0100 Subject: [PATCH 075/155] [test driver] Different approach to indentation tests All tests now check whether indentation from mode corresponds to the indentation in the test source. test.indentation is gone. --- mode/css/less_test.js | 2 +- mode/css/scss_test.js | 40 +++++++++++--------- mode/css/test.js | 55 +++++++++++++++++---------- mode/haml/haml.js | 4 -- mode/haml/test.js | 2 +- test/mode_test.js | 103 ++++++++++++++++++-------------------------------- 6 files changed, 95 insertions(+), 111 deletions(-) diff --git a/mode/css/less_test.js b/mode/css/less_test.js index 9b065f23d8..ea64f91d12 100644 --- a/mode/css/less_test.js +++ b/mode/css/less_test.js @@ -1,7 +1,7 @@ (function() { "use strict"; - var mode = CodeMirror.getMode({indentUnit: 1}, "text/x-less"); + var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); } MT("variable", diff --git a/mode/css/scss_test.js b/mode/css/scss_test.js index ed5de83801..c51cb42bba 100644 --- a/mode/css/scss_test.js +++ b/mode/css/scss_test.js @@ -1,7 +1,6 @@ (function() { - var mode = CodeMirror.getMode({indentUnit: 1}, "text/x-scss"); + var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } - function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } MT('url_with_quotation', "[tag foo] { [property background]:[atom url]([string test.jpg]) }"); @@ -79,23 +78,30 @@ MT('nested_structure_with_id_selector', "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }"); - IT('mixin', - "[1 @mixin container ($a: 10, $b: 10, $c: 10) {]}"); + MT('indent_mixin', + "[def @mixin] [tag container] (", + " [variable-2 $a]: [number 10],", + " [variable-2 $b]: [number 10])", + "{}"); - IT('nested', - "foo [1 { bar ][2 { ][1 } ]}"); - - IT('comma', - "foo [1 { font-family][2 : verdana, sans-serif][1 ; ]}"); + MT('indent_nested', + "[tag foo] {", + " [tag bar] {", + " }", + "}"); - IT('parentheses', - "foo [1 { color][2 : darken][3 ($blue, 9%][2 )][1 ; ]}"); + MT('indent_parentheses', + "[tag foo] {", + " [property color]: [variable darken]([variable-2 $blue],", + " [number 9%]);", + "}"); - IT('vardef', - "$name[1 : 'val'];", - "tag [1 {]", - "[1 inner ][2 {]", - "[2 margin][3 : 3px][2 ;]", - "[2 ][1 }]", + MT('indent_vardef', + "[variable-2 $name]:", + " [string 'val'];", + "[tag tag] {", + " [tag inner] {", + " [property margin]: [number 3px];", + " }", "}"); })(); diff --git a/mode/css/test.js b/mode/css/test.js index cbfbef46cc..425c26ed91 100644 --- a/mode/css/test.js +++ b/mode/css/test.js @@ -1,7 +1,6 @@ (function() { - var mode = CodeMirror.getMode({indentUnit: 1}, "css"); + var mode = CodeMirror.getMode({indentUnit: 2}, "css"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } - function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1)); } // Error, because "foobarhello" is neither a known type or property, but // property was expected (after "and"), and it should be in parenthese. @@ -38,11 +37,11 @@ "[tag foo] { [property font-family]: [string 'hello world']; }"); MT("tagColorKeyword", - "[tag foo] {" + - "[property color]: [keyword black];" + - "[property color]: [keyword navy];" + - "[property color]: [keyword yellow];" + - "}"); + "[tag foo] {", + " [property color]: [keyword black];", + " [property color]: [keyword navy];", + " [property color]: [keyword yellow];", + "}"); MT("tagColorHex3", "[tag foo] { [property background]: [atom #fff]; }"); @@ -81,18 +80,32 @@ "[comment ] [tag div] {}"); - IT("tagSelector", - "strong, em [1 { background][2 : rgba][3 (255, 255, 0, .2][2 )][1 ;]}"); - - IT("atMedia", - "[1 @media { foo ][2 { ][1 } ]}"); - - IT("comma", - "foo [1 { font-family][2 : verdana, sans-serif][1 ; ]}"); - - IT("parentheses", - "foo [1 { background][2 : url][3 (\"bar\"][2 )][1 ; ]}"); - - IT("pseudo", - "foo:before [1 { ]}"); + MT("indent_tagSelector", + "[tag strong], [tag em] {", + " [property background]: [atom rgba](", + " [number 255], [number 255], [number 0], [number .2]", + " );", + "}"); + + MT("indent_atMedia", + "[def @media] {", + " [tag foo] {", + " [property color]:", + " [keyword yellow];", + " }", + "}"); + + MT("indent_comma", + "[tag foo] {", + " [property font-family]: [variable verdana],", + " [atom sans-serif];", + "}"); + + MT("indent_parentheses", + "[tag foo]:[variable-3 before] {", + " [property background]: [atom url](", + "[string blahblah]", + "[string etc]", + "[string ]) [keyword !important];", + "}"); })(); diff --git a/mode/haml/haml.js b/mode/haml/haml.js index 793308f6fb..5ea4ed12c8 100644 --- a/mode/haml/haml.js +++ b/mode/haml/haml.js @@ -141,10 +141,6 @@ style = null; } return style; - }, - - indent: function(state) { - return state.indented; } }; }, "htmlmixed", "ruby"); diff --git a/mode/haml/test.js b/mode/haml/test.js index b7178d40fb..163b09f8dd 100644 --- a/mode/haml/test.js +++ b/mode/haml/test.js @@ -1,5 +1,5 @@ (function() { - var mode = CodeMirror.getMode({tabSize: 4}, "haml"); + var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "haml"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } // Requires at least one media query diff --git a/test/mode_test.js b/test/mode_test.js index 08deab74b2..46174e1f78 100644 --- a/test/mode_test.js +++ b/test/mode_test.js @@ -59,13 +59,6 @@ return {tokens: tokens, plain: plain}; } - test.indentation = function(name, mode, tokens, modeName) { - var data = parseTokens(tokens); - return test((modeName || mode.name) + "_indent_" + name, function() { - return compare(data.plain, data.tokens, mode, true); - }); - }; - test.mode = function(name, mode, tokens, modeName) { var data = parseTokens(tokens); return test((modeName || mode.name) + "_" + name, function() { @@ -73,7 +66,11 @@ }); }; - function compare(text, expected, mode, compareIndentation) { + function esc(str) { + return str.replace('&', '&').replace('<', '<'); + } + + function compare(text, expected, mode) { var expectedOutput = []; for (var i = 0; i < expected.length; i += 2) { @@ -82,59 +79,48 @@ expectedOutput.push(sty, expected[i + 1]); } - var observedOutput = highlight(text, mode, compareIndentation); + var observedOutput = highlight(text, mode); - var pass, passStyle = ""; - pass = highlightOutputsEqual(expectedOutput, observedOutput); - passStyle = pass ? 'mt-pass' : 'mt-fail'; - - var s = ''; - if (pass) { - s += '
    '; - s += '
    ' + text.replace('&', '&').replace('<', '<') + '
    '; - s += '
    '; - s += prettyPrintOutputTable(observedOutput); - s += '
    '; - s += '
    '; - return s; - } else { - s += '
    '; - s += '
    ' + text.replace('&', '&').replace('<', '<') + '
    '; + var s = ""; + var diff = highlightOutputsDifferent(expectedOutput, observedOutput); + if (diff != null) { + s += '
    '; + s += '
    ' + esc(text) + '
    '; s += '
    '; s += 'expected:'; - s += prettyPrintOutputTable(expectedOutput); + s += prettyPrintOutputTable(expectedOutput, diff); s += 'observed:'; - s += prettyPrintOutputTable(observedOutput); + s += prettyPrintOutputTable(observedOutput, diff); s += '
    '; s += '
    '; - throw s; } + if (observedOutput.indentFailures) { + for (var i = 0; i < observedOutput.indentFailures.length; i++) + s += "
    " + esc(observedOutput.indentFailures[i]) + "
    "; + } + if (s) throw new Failure(s); } - /** - * Emulation of CodeMirror's internal highlight routine for testing. Multi-line - * input is supported. - * - * @param string to highlight - * - * @param mode the mode that will do the actual highlighting - * - * @return array of [style, token] pairs - */ - function highlight(string, mode, compareIndentation) { + function highlight(string, mode) { var state = mode.startState() var lines = string.replace(/\r\n/g,'\n').split('\n'); var st = [], pos = 0; for (var i = 0; i < lines.length; ++i) { var line = lines[i], newLine = true; + if (mode.indent) { + var ws = line.match(/^\s*/)[0]; + var indent = mode.indent(state, line.slice(ws.length)); + if (indent != CodeMirror.Pass && indent != ws.length) + (st.indentFailures || (st.indentFailures = [])).push( + "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")"); + } var stream = new CodeMirror.StringStream(line); if (line == "" && mode.blankLine) mode.blankLine(state); /* Start copied code from CodeMirror.highlight */ while (!stream.eol()) { var compare = mode.token(stream, state), substr = stream.current(); - if (compareIndentation) compare = mode.indent(state) || null; - else if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' '); + if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' '); stream.start = stream.pos; if (pos && st[pos-2] == compare && !newLine) { st[pos-1] += substr; @@ -153,39 +139,22 @@ return st; } - /** - * Compare two arrays of output from highlight. - * - * @param o1 array of [style, token] pairs - * - * @param o2 array of [style, token] pairs - * - * @return boolean; true iff outputs equal - */ - function highlightOutputsEqual(o1, o2) { - if (o1.length != o2.length) return false; - for (var i = 0; i < o1.length; ++i) - if (o1[i] != o2[i]) return false; - return true; + function highlightOutputsDifferent(o1, o2) { + var minLen = Math.min(o1.length, o2.length); + for (var i = 0; i < minLen; ++i) + if (o1[i] != o2[i]) return i >> 1; + if (o1.length > minLen || o2.length > minLen) return minLen; } - /** - * Print tokens and corresponding styles in a table. Spaces in the token are - * replaced with 'interpunct' dots (·). - * - * @param output array of [style, token] pairs - * - * @return html string - */ - function prettyPrintOutputTable(output) { + function prettyPrintOutputTable(output, diffAt) { var s = ''; s += ''; for (var i = 0; i < output.length; i += 2) { var style = output[i], val = output[i+1]; s += - ''; } From 72f01e8cd33808cc56a85457949bfd5a49362656 Mon Sep 17 00:00:00 2001 From: Alexander Solovyov Date: Thu, 12 Dec 2013 23:30:47 +0200 Subject: [PATCH 076/155] [coffeescript mode] Allow single-quote-character strings to span lines Closes #2050 --- mode/coffeescript/coffeescript.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mode/coffeescript/coffeescript.js b/mode/coffeescript/coffeescript.js index e8bfe48a24..0e9e6352f8 100644 --- a/mode/coffeescript/coffeescript.js +++ b/mode/coffeescript/coffeescript.js @@ -119,13 +119,13 @@ CodeMirror.defineMode("coffeescript", function(conf) { // Handle strings if (stream.match(stringPrefixes)) { - state.tokenize = tokenFactory(stream.current(), "string"); + state.tokenize = tokenFactory(stream.current(), false, "string"); return state.tokenize(stream, state); } // Handle regex literals if (stream.match(regexPrefixes)) { if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division - state.tokenize = tokenFactory(stream.current(), "string-2"); + state.tokenize = tokenFactory(stream.current(), true, "string-2"); return state.tokenize(stream, state); } else { stream.backUp(1); @@ -161,8 +161,7 @@ CodeMirror.defineMode("coffeescript", function(conf) { return ERRORCLASS; } - function tokenFactory(delimiter, outclass) { - var singleline = delimiter.length == 1; + function tokenFactory(delimiter, singleline, outclass) { return function(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"\/\\]/); From b8e7854ada5c231c63e60ce0498fffbff65be46d Mon Sep 17 00:00:00 2001 From: Abe Fettig Date: Wed, 27 Nov 2013 12:16:05 -0500 Subject: [PATCH 077/155] [show-hint addon] Add completeOnSingleClick option ... to make a single-click pick a hint option instead of just selecting it. --- addon/hint/show-hint.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index 76f020cf0a..1c60603ea2 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -235,7 +235,10 @@ CodeMirror.on(hints, "click", function(e) { var t = getHintElement(hints, e.target || e.srcElement); - if (t && t.hintId != null) widget.changeActive(t.hintId); + if (t && t.hintId != null) { + widget.changeActive(t.hintId); + if (options.completeOnSingleClick) widget.pick(); + } }); CodeMirror.on(hints, "mousedown", function() { From 0cf23eea94afbf735ada31a5f300e60f65425e77 Mon Sep 17 00:00:00 2001 From: hitsthings Date: Mon, 2 Dec 2013 10:50:04 +1100 Subject: [PATCH 078/155] Fix IE showing empty with 0 padding and resize-to-content Fixes #2011 in at least IE8+. When the resize-to-content styles are applied along with a removal of vertical padding on .CodeMirror-lines, IE would not display any content. Using `offsetWidth` instead of `clientWidth` fixes this for modern-ish IE. A more complex solution is needed to solve it for older browsers. --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 7b1b67dc0e..ec1bee3c28 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -450,7 +450,7 @@ window.CodeMirror = (function() { // updates. function updateDisplayInner(cm, changes, visible, forced) { var display = cm.display, doc = cm.doc; - if (!display.wrapper.clientWidth) { + if (!display.wrapper.offsetWidth) { display.showingFrom = display.showingTo = doc.first; display.viewOffset = 0; return; From 78435a5f1d61f8ba93386e8d865c4051b2e3c0f2 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 15 Dec 2013 13:43:09 +0100 Subject: [PATCH 079/155] [runmode addon] Add resolveMode and registerHelper shims Needed for some modes to load. Closes #2054 --- addon/runmode/runmode-standalone.js | 21 +++++++++++++-------- addon/runmode/runmode.node.js | 21 +++++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/addon/runmode/runmode-standalone.js b/addon/runmode/runmode-standalone.js index d117166c98..2866b252ee 100644 --- a/addon/runmode/runmode-standalone.js +++ b/addon/runmode/runmode-standalone.js @@ -69,17 +69,22 @@ CodeMirror.startState = function (mode, a1, a2) { var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; CodeMirror.defineMode = function (name, mode) { modes[name] = mode; }; CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; }; -CodeMirror.getMode = function (options, spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) +CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + spec = mimeModes[spec.name]; + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; +}; +CodeMirror.getMode = function (options, spec) { + spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; if (!mfactory) throw new Error("Unknown mode: " + spec); - return mfactory(options, config || {}); + return mfactory(options, spec); }; +CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min; CodeMirror.runMode = function (string, modespec, callback, options) { var mode = CodeMirror.getMode({ indentUnit: 2 }, modespec); diff --git a/addon/runmode/runmode.node.js b/addon/runmode/runmode.node.js index 0f1088fa2e..304640fa62 100644 --- a/addon/runmode/runmode.node.js +++ b/addon/runmode/runmode.node.js @@ -76,17 +76,22 @@ exports.defineMode("null", function() { }); exports.defineMIME("text/plain", "null"); -exports.getMode = function(options, spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) +exports.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + spec = mimeModes[spec.name]; + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; +}; +exports.getMode = function(options, spec) { + spec = exports.resolveMode(mimeModes[spec]); + var mfactory = modes[spec.name]; if (!mfactory) throw new Error("Unknown mode: " + spec); - return mfactory(options, config || {}); + return mfactory(options, spec); }; +CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min; exports.runMode = function(string, modespec, callback) { var mode = exports.getMode({indentUnit: 2}, modespec); From ea7397c17662607248a83f2769cfaea1100d2d8b Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 16 Dec 2013 21:05:47 -0500 Subject: [PATCH 080/155] [markdown] Lists after atx headers should be highlighted Closes #2040 --- mode/markdown/markdown.js | 13 +++++++++---- mode/markdown/test.js | 5 +++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 2469479ea0..c959242ac6 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -664,7 +664,15 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.formatting = false; if (stream.sol()) { - if (stream.match(/^\s*$/, true)) { + var forceBlankLine = false; + if (stream.match(/^\s*$/, true) || state.header) { + forceBlankLine = true; + } + + // Reset state.header + state.header = 0; + + if (forceBlankLine) { state.prevLineHasContent = false; return blankLine(state); } else { @@ -675,9 +683,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { // Reset state.escape state.escape = false; - // Reset state.header - state.header = 0; - // Reset state.taskList state.taskList = false; diff --git a/mode/markdown/test.js b/mode/markdown/test.js index 21ceab7650..bc5cc97380 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -276,6 +276,11 @@ "1. bar", "2. hello"); + // List after header + MT("listAfterHeader", + "[header&header1 # foo]", + "[variable-2 - bar]"); + // Formatting in lists (*) MT("listAsteriskFormatting", "[variable-2 * ][variable-2&em *foo*][variable-2 bar]", From 8f8da43cc78f41a399d44f78b3a876b8e34a56f8 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 16 Dec 2013 21:42:09 -0500 Subject: [PATCH 081/155] [markdown] Specify type of list when highlightFormatting is enabled Closes #2039 --- mode/gfm/test.js | 4 ++-- mode/markdown/markdown.js | 11 +++++++++-- mode/markdown/test.js | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mode/gfm/test.js b/mode/gfm/test.js index 7d17517e18..e5c3486ebd 100644 --- a/mode/gfm/test.js +++ b/mode/gfm/test.js @@ -16,8 +16,8 @@ "[comment&formatting&formatting-code-block ```]"); FT("taskList", - "[variable-2&formatting&formatting-list - ][meta&formatting&formatting-task [ ]]][variable-2 foo]", - "[variable-2&formatting&formatting-list - ][property&formatting&formatting-task [x]]][variable-2 foo]"); + "[variable-2&formatting&formatting-list&formatting-list-ul - ][meta&formatting&formatting-task [ ]]][variable-2 foo]", + "[variable-2&formatting&formatting-list&formatting-list-ul - ][property&formatting&formatting-task [x]]][variable-2 foo]"); MT("emInWordAsterisk", "foo[em *bar*]hello"); diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index c959242ac6..6370462772 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -165,7 +165,14 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return switchInline(stream, state, footnoteLink); } else if (stream.match(hrRE, true)) { return hr; - } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) { + } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) { + var listType = null; + if (stream.match(ulRE, true)) { + listType = 'ul'; + } else { + stream.match(olRE, true); + listType = 'ol'; + } state.indentation += 4; state.list = true; state.listDepth++; @@ -173,7 +180,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.taskList = true; } state.f = state.inline; - if (modeCfg.highlightFormatting) state.formatting = "list"; + if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType]; return getType(state); } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { // try switching mode diff --git a/mode/markdown/test.js b/mode/markdown/test.js index bc5cc97380..96572025d8 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -33,9 +33,9 @@ "[atom&formatting&formatting-quote > ][atom foo]"); FT("formatting_list", - "[variable-2&formatting&formatting-list - ][variable-2 foo]"); + "[variable-2&formatting&formatting-list&formatting-list-ul - ][variable-2 foo]"); FT("formatting_list", - "[variable-2&formatting&formatting-list 1. ][variable-2 foo]"); + "[variable-2&formatting&formatting-list&formatting-list-ol 1. ][variable-2 foo]"); FT("formatting_link", "[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string (][string http://example.com/][string&formatting&formatting-link-string )]"); From 4536d5a81db213a0b82f07f2bbde5b48602165ea Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 17 Dec 2013 18:19:36 +0100 Subject: [PATCH 082/155] [real-world uses] Add writeLaTeX --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index 156eacd097..b9e35f2dfc 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -128,6 +128,7 @@
  • WebKit Web inspector
  • WeScheme (learning tool)
  • WordPress plugin
  • +
  • writeLaTeX (Collaborative LaTeX Editor)
  • XOSide (online editor)
  • XQuery tester
  • xsd2codemirror (convert XSD to CM XML completion info)
  • From 2f266c38a6ea69f7fff990dbd861b93f7689de22 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 18 Dec 2013 13:48:33 +0100 Subject: [PATCH 083/155] Don't close undo events by default in changeGeneration Closes #2059 --- doc/manual.html | 8 ++++++-- lib/codemirror.js | 9 +++++---- test/test.js | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 957ab347c5..33953c11ea 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -935,10 +935,14 @@

    Content manipulation methods

    of changeGeneration, which allows multiple subsystems to track different notions of cleanness without interfering. -
    doc.changeGeneration() → integer
    +
    doc.changeGeneration(?closeEvent: boolean) → integer
    Returns a number that can later be passed to isClean to test whether - any edits were made (and not undone) in the meantime.
    + any edits were made (and not undone) in the meantime. + If closeEvent is true, the current history event + will be ‘closed’, meaning it can't be combined with further + changes (rapid typing or deleting events are typically + combined).
    doc.isClean(?generation: integer) → boolean
    Returns whether the document is currently clean — not modified since initialization or the last call diff --git a/lib/codemirror.js b/lib/codemirror.js index ec1bee3c28..abf17eed74 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4984,10 +4984,11 @@ window.CodeMirror = (function() { clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);}, markClean: function() { - this.cleanGeneration = this.changeGeneration(); + this.cleanGeneration = this.changeGeneration(true); }, - changeGeneration: function() { - this.history.lastOp = this.history.lastOrigin = null; + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastOrigin = null; return this.history.generation; }, isClean: function (gen) { @@ -5291,10 +5292,10 @@ window.CodeMirror = (function() { anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, anchorAfter: selAfter.anchor, headAfter: selAfter.head}; hist.done.push(cur); - hist.generation = ++hist.maxGeneration; while (hist.done.length > hist.undoDepth) hist.done.shift(); } + hist.generation = ++hist.maxGeneration; hist.lastTime = time; hist.lastOp = opId; hist.lastOrigin = change.origin; diff --git a/test/test.js b/test/test.js index 86b0bfe178..3ad8d35499 100644 --- a/test/test.js +++ b/test/test.js @@ -1479,6 +1479,21 @@ testCM("dirtyBit", function(cm) { eq(cm.isClean(), true); }); +testCM("changeGeneration", function(cm) { + cm.replaceSelection("x", null, "+insert"); + var softGen = cm.changeGeneration(); + cm.replaceSelection("x", null, "+insert"); + cm.undo(); + eq(cm.getValue(), ""); + is(!cm.isClean(softGen)); + cm.replaceSelection("x", null, "+insert"); + var hardGen = cm.changeGeneration(true); + cm.replaceSelection("x", null, "+insert"); + cm.undo(); + eq(cm.getValue(), "x"); + is(cm.isClean(hardGen)); +}); + testCM("addKeyMap", function(cm) { function sendKey(code) { cm.triggerOnKeyDown({type: "keydown", keyCode: code, From d13582f3d8867fb39a9c154bfd4d60a17e213667 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 18 Dec 2013 14:06:53 +0100 Subject: [PATCH 084/155] [run-mode addon] Add hideFirstChars to stand-alone shims Issue #2054 --- addon/runmode/runmode-standalone.js | 10 ++++++++-- addon/runmode/runmode.node.js | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/addon/runmode/runmode-standalone.js b/addon/runmode/runmode-standalone.js index 2866b252ee..fcb3db35c8 100644 --- a/addon/runmode/runmode-standalone.js +++ b/addon/runmode/runmode-standalone.js @@ -10,6 +10,7 @@ function splitLines(string){ return string.split(/\r?\n|\r/); }; function StringStream(string) { this.pos = this.start = 0; this.string = string; + this.lineStart = 0; } StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, @@ -41,7 +42,7 @@ StringStream.prototype = { if (found > -1) {this.pos = found; return true;} }, backUp: function(n) {this.pos -= n;}, - column: function() {return this.start;}, + column: function() {return this.start - this.lineStart;}, indentation: function() {return 0;}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { @@ -58,7 +59,12 @@ StringStream.prototype = { return match; } }, - current: function(){return this.string.slice(this.start, this.pos);} + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } }; CodeMirror.StringStream = StringStream; diff --git a/addon/runmode/runmode.node.js b/addon/runmode/runmode.node.js index 304640fa62..99f72ef02e 100644 --- a/addon/runmode/runmode.node.js +++ b/addon/runmode/runmode.node.js @@ -5,6 +5,7 @@ function splitLines(string){ return string.split(/\r?\n|\r/); }; function StringStream(string) { this.pos = this.start = 0; this.string = string; + this.lineStart = 0; } StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, @@ -36,7 +37,7 @@ StringStream.prototype = { if (found > -1) {this.pos = found; return true;} }, backUp: function(n) {this.pos -= n;}, - column: function() {return this.start;}, + column: function() {return this.start - this.lineStart;}, indentation: function() {return 0;}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { @@ -53,7 +54,12 @@ StringStream.prototype = { return match; } }, - current: function(){return this.string.slice(this.start, this.pos);} + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } }; exports.StringStream = StringStream; From e3d64baf825459668f1c68e30ed11a40091df515 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 20 Dec 2013 17:22:49 +0100 Subject: [PATCH 085/155] [runmode addon] Fix broken node.js version Issue #2054 --- addon/runmode/runmode.node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/runmode/runmode.node.js b/addon/runmode/runmode.node.js index 99f72ef02e..691af8b360 100644 --- a/addon/runmode/runmode.node.js +++ b/addon/runmode/runmode.node.js @@ -97,7 +97,7 @@ exports.getMode = function(options, spec) { if (!mfactory) throw new Error("Unknown mode: " + spec); return mfactory(options, spec); }; -CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min; +exports.registerHelper = exports.registerGlobalHelper = Math.min; exports.runMode = function(string, modespec, callback) { var mode = exports.getMode({indentUnit: 2}, modespec); From 51234f9f35024f6c4f1abcf4a19b8df693826c09 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 26 Dec 2013 21:24:25 +0100 Subject: [PATCH 086/155] [runmode addon] Support a state option to provide a custom stating state --- addon/runmode/runmode-standalone.js | 2 +- addon/runmode/runmode.js | 2 +- addon/runmode/runmode.node.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addon/runmode/runmode-standalone.js b/addon/runmode/runmode-standalone.js index fcb3db35c8..9ebde8b054 100644 --- a/addon/runmode/runmode-standalone.js +++ b/addon/runmode/runmode-standalone.js @@ -133,7 +133,7 @@ CodeMirror.runMode = function (string, modespec, callback, options) { }; } - var lines = splitLines(string), state = CodeMirror.startState(mode); + var lines = splitLines(string), state = (options && options.state) || CodeMirror.startState(mode); for (var i = 0, e = lines.length; i < e; ++i) { if (i) callback("\n"); var stream = new CodeMirror.StringStream(lines[i]); diff --git a/addon/runmode/runmode.js b/addon/runmode/runmode.js index 7aafa2ad8f..2cafa811f8 100644 --- a/addon/runmode/runmode.js +++ b/addon/runmode/runmode.js @@ -43,7 +43,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) { }; } - var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); + var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode); for (var i = 0, e = lines.length; i < e; ++i) { if (i) callback("\n"); var stream = new CodeMirror.StringStream(lines[i]); diff --git a/addon/runmode/runmode.node.js b/addon/runmode/runmode.node.js index 691af8b360..e8bccb0cf2 100644 --- a/addon/runmode/runmode.node.js +++ b/addon/runmode/runmode.node.js @@ -101,7 +101,7 @@ exports.registerHelper = exports.registerGlobalHelper = Math.min; exports.runMode = function(string, modespec, callback) { var mode = exports.getMode({indentUnit: 2}, modespec); - var lines = splitLines(string), state = exports.startState(mode); + var lines = splitLines(string), state = (options && options.state) || exports.startState(mode); for (var i = 0, e = lines.length; i < e; ++i) { if (i) callback("\n"); var stream = new exports.StringStream(lines[i]); From 4e9d808ff6c31475c5b85ab2de72328245343d75 Mon Sep 17 00:00:00 2001 From: Peter Kroon Date: Thu, 19 Dec 2013 21:03:42 +0100 Subject: [PATCH 087/155] Added builtin values And put string in alphabetical order. - varying - xml - bit --- mode/sql/sql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/sql/sql.js b/mode/sql/sql.js index 3be68caa1a..53e06bab93 100644 --- a/mode/sql/sql.js +++ b/mode/sql/sql.js @@ -326,7 +326,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) { name: "sql", client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"), keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"), - builtin: set("bfile blob character clob dec float int integer mlslabel natural naturaln nchar nclob number numeric nvarchar2 real rowtype signtype smallint string varchar varchar2 abs acos add_months ascii asin atan atan2 average bfilename ceil chartorowid chr concat convert cos cosh count decode deref dual dump dup_val_on_index empty error exp false floor found glb greatest hextoraw initcap instr instrb isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mod months_between new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null nvl others power rawtohex reftohex round rowcount rowidtochar rpad rtrim sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv variance vsize"), + builtin: set("abs acos add_months ascii asin atan atan2 average bfile bfilename bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv varchar varchar2 variance varying vsize xml"), operatorChars: /^[*+\-%<>!=~]/, dateSQL: set("date time timestamp"), support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber") From b6efcb93982269cfe1707563a45bb4f11886b421 Mon Sep 17 00:00:00 2001 From: Travis Heppe Date: Wed, 27 Nov 2013 14:46:08 -0800 Subject: [PATCH 088/155] [vim keymap] Add support for , and --- keymap/vim.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ test/vim_test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/keymap/vim.js b/keymap/vim.js index 164d51b1a4..efb4de48ae 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -84,6 +84,7 @@ { keys: [''], type: 'keyToKey', toKeys: ['$'] }, { keys: [''], type: 'keyToKey', toKeys: [''] }, { keys: [''], type: 'keyToKey', toKeys: [''] }, + { keys: [''], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' }, // Motions { keys: ['H'], type: 'motion', motion: 'moveToTopLine', @@ -247,6 +248,12 @@ actionArgs: { forward: true }}, { keys: [''], type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }}, + { keys: [''], type: 'action', + action: 'scroll', + actionArgs: { forward: true, linewise: true }}, + { keys: [''], type: 'action', + action: 'scroll', + actionArgs: { forward: false, linewise: true }}, { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }}, { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true, @@ -1636,6 +1643,43 @@ markPos = markPos ? markPos : cm.getCursor(); cm.setCursor(markPos); }, + scroll: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var lineHeight = cm.defaultTextHeight(); + var top = cm.getScrollInfo().top; + var delta = lineHeight * repeat; + var newPos = actionArgs.forward ? top + delta : top - delta; + var cursor = cm.getCursor(); + var cursorCoords = cm.charCoords(cursor, 'local'); + if (actionArgs.forward) { + if (newPos > cursorCoords.top) { + cursor.line += (newPos - cursorCoords.top) / lineHeight; + cursor.line = Math.ceil(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo(null, cursorCoords.top); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } else { + var newBottom = newPos + cm.getScrollInfo().clientHeight; + if (newBottom < cursorCoords.bottom) { + cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; + cursor.line = Math.floor(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo( + null, cursorCoords.bottom - cm.getScrollInfo().clientHeight); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } + }, scrollToCursor: function(cm, actionArgs) { var lineNum = cm.getCursor().line; var charCoords = cm.charCoords({line: lineNum, ch: 0}, 'local'); diff --git a/test/vim_test.js b/test/vim_test.js index c65b5cad03..1a5cc3ee3f 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -2003,6 +2003,33 @@ testVim('zt==z', function(cm, vim, helpers){ eq(zVals[2], zVals[5]); }); +var scrollMotionSandbox = + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'; +testVim('scrollMotion', function(cm, vim, helpers){ + var prevCursor, prevScrollInfo; + cm.setCursor(0, 0); + // ctrl-y at the top of the file should have no effect. + helpers.doKeys(''); + eq(0, cm.getCursor().line); + prevScrollInfo = cm.getScrollInfo(); + helpers.doKeys(''); + eq(1, cm.getCursor().line); + eq(true, prevScrollInfo.top < cm.getScrollInfo().top); + // Jump to the end of the sandbox. + cm.setCursor(1000, 0); + prevCursor = cm.getCursor(); + // ctrl-e at the bottom of the file should have no effect. + helpers.doKeys(''); + eq(prevCursor.line, cm.getCursor().line); + prevScrollInfo = cm.getScrollInfo(); + helpers.doKeys(''); + eq(prevCursor.line - 1, cm.getCursor().line); + eq(true, prevScrollInfo.top > cm.getScrollInfo().top); +}, { value: scrollMotionSandbox}); + var squareBracketMotionSandbox = ''+ '({\n'+//0 ' ({\n'+//11 From d99da21fe85ac965b8ddc57b5daaa21104d650c9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 26 Dec 2013 21:55:17 +0100 Subject: [PATCH 089/155] Fix problems introduced by new rule for indenting empty lines --- addon/edit/closetag.js | 4 ++-- lib/codemirror.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 106448d3d7..60e204bcf2 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -67,8 +67,8 @@ cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "", {head: curPos, anchor: curPos}); if (doIndent) { - cm.indentLine(pos.line + 1); - cm.indentLine(pos.line + 2); + cm.indentLine(pos.line + 1, null, true); + cm.indentLine(pos.line + 2, null); } } diff --git a/lib/codemirror.js b/lib/codemirror.js index abf17eed74..d66bfc4670 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2932,7 +2932,7 @@ window.CodeMirror = (function() { }), indentSelection: operation(null, function(how) { var sel = this.doc.sel; - if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); + if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how, true); var e = sel.to.line - (sel.to.ch ? 0 : 1); for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); }), From 7bf3a0018c4248127c9939a6b48b8651f5088204 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 26 Dec 2013 21:57:10 +0100 Subject: [PATCH 090/155] [hardwrap addon] Prevent null-use error in corner case Closes #2070 --- addon/wrap/hardwrap.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/wrap/hardwrap.js b/addon/wrap/hardwrap.js index 9260c75bbb..1faa3d84a6 100644 --- a/addon/wrap/hardwrap.js +++ b/addon/wrap/hardwrap.js @@ -36,6 +36,7 @@ var killTrailing = options.killTrailingSpace !== false; var changes = [], curLine = "", curNo = from.line; var lines = cm.getRange(from, to, false); + if (!lines.length) return; var leadingSpace = lines[0].match(/^[ \t]*/)[0]; for (var i = 0; i < lines.length; ++i) { From ed7f8d083f52e7b58827d46410278a8688e7aa74 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 26 Dec 2013 22:03:26 +0100 Subject: [PATCH 091/155] [sql mode] Don't return CodeMirror.Pass when in top context Issue #2064 --- mode/sql/sql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/sql/sql.js b/mode/sql/sql.js index 53e06bab93..946e7b38fa 100644 --- a/mode/sql/sql.js +++ b/mode/sql/sql.js @@ -177,7 +177,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) { indent: function(state, textAfter) { var cx = state.context; - if (!cx) return CodeMirror.Pass; + if (!cx) return 0; if (cx.align) return cx.col + (textAfter.charAt(0) == cx.type ? 0 : 1); else return cx.indent + config.indentUnit; }, From d50ced613d1383e7e2f6ebe04217d71804493aea Mon Sep 17 00:00:00 2001 From: Peter Kroon Date: Mon, 23 Dec 2013 21:19:43 +0100 Subject: [PATCH 092/155] [matchbrackets addon] Fix typos --- addon/edit/matchbrackets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/edit/matchbrackets.js b/addon/edit/matchbrackets.js index 9d9b3882f7..465b6ccaa9 100644 --- a/addon/edit/matchbrackets.js +++ b/addon/edit/matchbrackets.js @@ -54,7 +54,7 @@ var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style}); var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style}); // Kludge to work around the IE bug from issue #1193, where text - // input stops going to the textare whever this fires. + // input stops going to the textarea whenever this fires. if (ie_lt8 && cm.state.focused) cm.display.input.focus(); var clear = function() { cm.operation(function() { one.clear(); two && two.clear(); }); From 6fac052cc36082d1dadfa01f76755374b3c92a20 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Dec 2013 16:29:18 +0100 Subject: [PATCH 093/155] [gfm mode] Fix exponential running time in regexp --- mode/gfm/gfm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/gfm/gfm.js b/mode/gfm/gfm.js index 492cbc463a..10d5e05c06 100644 --- a/mode/gfm/gfm.js +++ b/mode/gfm/gfm.js @@ -75,7 +75,7 @@ CodeMirror.defineMode("gfm", function(config, modeConfig) { return "link"; } } - if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i) && + if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i) && stream.string.slice(stream.start - 2, stream.start) != "](") { // URLs // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls From 4f90bd87f1c5a20d0a40719169a02841202bdf38 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Dec 2013 16:33:16 +0100 Subject: [PATCH 094/155] Clear input field when disableInput flag is cleared Issue #2060 --- lib/codemirror.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index d66bfc4670..086f84151e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -244,6 +244,7 @@ window.CodeMirror = (function() { var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); + if (cm.state.disableInput && !map.disableInput) resetInput(cm, true); cm.state.disableInput = map.disableInput; } From f5b8e1000dcd3d2e080ee5fd845eb333eefc04a5 Mon Sep 17 00:00:00 2001 From: Travis Heppe Date: Fri, 20 Dec 2013 17:29:31 -0800 Subject: [PATCH 095/155] [test driver] Clean up test selection --- test/doc_test.js | 2 +- test/driver.js | 49 ++++++++++++++++++------------------------------- test/index.html | 40 ++++++++++++++++++++++++---------------- test/vim_test.js | 2 +- 4 files changed, 44 insertions(+), 49 deletions(-) diff --git a/test/doc_test.js b/test/doc_test.js index 3e04e155b3..f0f40e76e0 100644 --- a/test/doc_test.js +++ b/test/doc_test.js @@ -57,7 +57,7 @@ run.apply(null, editors); successful = true; } finally { - if ((debug && !successful) || verbose) { + if (!successful || verbose) { place.style.visibility = "visible"; } else { for (var i = 0; i < editors.length; ++i) diff --git a/test/driver.js b/test/driver.js index 5befa77278..10088ab1ed 100644 --- a/test/driver.js +++ b/test/driver.js @@ -1,4 +1,4 @@ -var tests = [], debug = null, debugUsed = new Array(), allNames = []; +var tests = [], filters = [], allNames = []; function Failure(why) {this.message = why;} Failure.prototype.toString = function() { return this.message; }; @@ -32,7 +32,7 @@ function testCM(name, run, opts, expectedFail) { run(cm); successful = true; } finally { - if ((debug && !successful) || verbose) { + if (!successful || verbose) { place.style.visibility = "visible"; } else { place.removeChild(cm.getWrapperElement()); @@ -42,39 +42,23 @@ function testCM(name, run, opts, expectedFail) { } function runTests(callback) { - if (debug) { - if (indexOf(debug, "verbose") === 0) { - verbose = true; - debug.splice(0, 1); - } - if (debug.length < 1) { - debug = null; - } - } var totalTime = 0; function step(i) { if (i === tests.length){ running = false; return callback("done"); - } + } var test = tests[i], expFail = test.expectedFail, startTime = +new Date; - if (debug !== null) { - var debugIndex = indexOf(debug, test.name); - if (debugIndex !== -1) { - // Remove from array for reporting incorrect tests later - debug.splice(debugIndex, 1); - } else { - var wildcardName = test.name.split("_")[0] + "_*"; - debugIndex = indexOf(debug, wildcardName); - if (debugIndex !== -1) { - // Remove from array for reporting incorrect tests later - debug.splice(debugIndex, 1); - debugUsed.push(wildcardName); - } else { - debugIndex = indexOf(debugUsed, wildcardName); - if (debugIndex == -1) return step(i + 1); + if (filters.length) { + for (var j = 0; j < filters.length; j++) { + if (test.name.match(filters[j])) { + break; } } + if (j == filters.length) { + callback("skipped", test.name, message); + return step(i + 1); + } } var threw = false; try { @@ -127,13 +111,16 @@ function is(a, msg) { } function countTests() { - if (!debug) return tests.length; + if (!filters.length) return tests.length; var sum = 0; for (var i = 0; i < tests.length; ++i) { var name = tests[i].name; - if (indexOf(debug, name) != -1 || - indexOf(debug, name.split("_")[0] + "_*") != -1) - ++sum; + for (var j = 0; j < filters.length; j++) { + if (name.match(filters[j])) { + ++sum; + break; + } + } } return sum; } diff --git a/test/index.html b/test/index.html index ac12b2274c..0a10f3c945 100644 --- a/test/index.html +++ b/test/index.html @@ -109,10 +109,11 @@ progressTotal = document.getElementById("progress_total").childNodes[0]; var count = 0, failed = 0, + skipped = 0, bad = "", running = false, // Flag that states tests are running - quit = false, // Flag to quit tests ASAP - verbose = false; // Adds message for *every* test to output + quit = false, // Flag to quit tests ASAP + verbose = false; // Adds message for *every* test to output function runHarness(){ if (running) { @@ -121,19 +122,26 @@ setTimeout(function(){runHarness();}, 500); return; } + filters = []; + verbose = false; if (window.location.hash.substr(1)){ - debug = window.location.hash.substr(1).split(","); - } else { - debug = null; + var strings = window.location.hash.substr(1).split(","); + while (strings.length) { + var s = strings.shift(); + if (s === "verbose") { + verbose = true; + } else { + filters.push(new RegExp(decodeURIComponent(s, 'i'))); + } + } } quit = false; running = true; setStatus("Loading tests..."); count = 0; failed = 0; + skipped = 0; bad = ""; - verbose = false; - debugUsed = Array(); totalTests = countTests(); progressTotal.nodeValue = " of " + totalTests; progressRan.nodeValue = count; @@ -166,12 +174,16 @@ } function displayTest(type, name, customMessage) { var message = "???"; - if (type != "done") ++count; + if (type != "done" && type != "skipped") ++count; progress.style.width = (count * (progress.parentNode.clientWidth - 2) / totalTests) + "px"; progressRan.nodeValue = count; if (type == "ok") { message = "Test '" + name + "' succeeded"; if (!verbose) customMessage = false; + } else if (type == "skipped") { + message = "Test '" + name + "' skipped"; + ++skipped; + if (!verbose) customMessage = false; } else if (type == "expected") { message = "Test '" + name + "' failed as expected"; if (!verbose) customMessage = false; @@ -189,15 +201,11 @@ } else { type += " ok"; message = "All passed"; + if (skipped) { + message += " (" + skipped + " skipped)"; + } } - if (debug && debug.length) { - var bogusTests = totalTests - count; - message += " — " + bogusTests + " nonexistent test" + - (bogusTests > 1 ? "s" : "") + " requested by location.hash: " + - "`" + debug.join("`, `") + "`"; - } else { - progressTotal.nodeValue = ''; - } + progressTotal.nodeValue = ''; customMessage = true; // Hack to avoid adding to output } if (verbose && !customMessage) customMessage = message; diff --git a/test/vim_test.js b/test/vim_test.js index 1a5cc3ee3f..28d492178c 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -195,7 +195,7 @@ function testVim(name, run, opts, expectedFail) { run(cm, vim, helpers); successful = true; } finally { - if ((debug && !successful) || verbose) { + if (!successful || verbose) { place.style.visibility = "visible"; } else { place.removeChild(cm.getWrapperElement()); From 9228a29bc0d6fb5ada1a2186dd9a27dffecb6af6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Dec 2013 17:27:31 +0100 Subject: [PATCH 096/155] Clean up f5b8e1000dcd3d2e080ee5fd845eb333eefc04a5 --- test/driver.js | 5 +++++ test/index.html | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/driver.js b/test/driver.js index 10088ab1ed..13952dcc65 100644 --- a/test/driver.js +++ b/test/driver.js @@ -124,3 +124,8 @@ function countTests() { } return sum; } + +function parseTestFilter(s) { + if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i"); + else return new RegExp(s, "i"); +} diff --git a/test/index.html b/test/index.html index 0a10f3c945..8177038e30 100644 --- a/test/index.html +++ b/test/index.html @@ -128,11 +128,10 @@ var strings = window.location.hash.substr(1).split(","); while (strings.length) { var s = strings.shift(); - if (s === "verbose") { + if (s === "verbose") verbose = true; - } else { - filters.push(new RegExp(decodeURIComponent(s, 'i'))); - } + else + filters.push(parseTestFilter(decodeURIComponent(s)));; } } quit = false; From b226604b1395621c69a1326a8ef12cdf02694778 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Dec 2013 21:18:16 +0100 Subject: [PATCH 097/155] [sql mode] Another indentation tweak Issue #2065 --- mode/sql/sql.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mode/sql/sql.js b/mode/sql/sql.js index 946e7b38fa..903fa6740a 100644 --- a/mode/sql/sql.js +++ b/mode/sql/sql.js @@ -178,8 +178,9 @@ CodeMirror.defineMode("sql", function(config, parserConfig) { indent: function(state, textAfter) { var cx = state.context; if (!cx) return 0; - if (cx.align) return cx.col + (textAfter.charAt(0) == cx.type ? 0 : 1); - else return cx.indent + config.indentUnit; + var closing = textAfter.charAt(0) == cx.type; + if (cx.align) return cx.col + (closing ? 0 : 1); + else return cx.indent + (closing ? 0 : config.indentUnit); }, blockCommentStart: "/*", From 4b5195fbe5776435e64e3ef4e925f34859ac1d93 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 27 Dec 2013 21:21:48 +0100 Subject: [PATCH 098/155] [hardwrap demo] Remove debugging code Closes #2080 --- demo/hardwrap.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/demo/hardwrap.html b/demo/hardwrap.html index 6cfd9c87c2..d3f085ad43 100644 --- a/demo/hardwrap.html +++ b/demo/hardwrap.html @@ -61,9 +61,7 @@ } }); var wait, options = {column: 60}; -var done = false; editor.on("change", function(cm, change) { -if (done) return; done = true; clearTimeout(wait); wait = setTimeout(function() { cm.wrapParagraphsInRange(change.from, CodeMirror.changeEnd(change), options); From 14877c387fe0cedff2189c421b1a39de882e3bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haso=C5=88?= Date: Fri, 27 Dec 2013 12:50:33 +0100 Subject: [PATCH 099/155] [gherkin mode] Improve, add localizations --- mode/gherkin/gherkin.js | 95 +++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/mode/gherkin/gherkin.js b/mode/gherkin/gherkin.js index dadb58362b..784afc50ce 100644 --- a/mode/gherkin/gherkin.js +++ b/mode/gherkin/gherkin.js @@ -18,31 +18,40 @@ CodeMirror.defineMode("gherkin", function () { startState: function () { return { lineNumber: 0, - tableHeaderLine: null, + tableHeaderLine: false, allowFeature: true, allowBackground: false, allowScenario: false, allowSteps: false, allowPlaceholders: false, - inMultilineArgument: false, + allowMultilineArgument: false, inMultilineString: false, - inMultilineTable: false + inMultilineTable: false, + inKeywordLine: false }; }, token: function (stream, state) { if (stream.sol()) { state.lineNumber++; + state.inKeywordLine = false; + if (state.inMultilineTable) { + state.tableHeaderLine = false; + if (!stream.match(/\s*\|/, false)) { + state.allowMultilineArgument = false; + state.inMultilineTable = false; + } + } } + stream.eatSpace(); - // INSIDE OF MULTILINE ARGUMENTS - if (state.inMultilineArgument) { + if (state.allowMultilineArgument) { // STRING if (state.inMultilineString) { if (stream.match('"""')) { state.inMultilineString = false; - state.inMultilineArgument = false; + state.allowMultilineArgument = false; } else { stream.match(/.*/); } @@ -51,19 +60,11 @@ CodeMirror.defineMode("gherkin", function () { // TABLE if (state.inMultilineTable) { - // New table, assume first row is headers - if (state.tableHeaderLine === null) { - state.tableHeaderLine = state.lineNumber; - } - if (stream.match(/\|\s*/)) { - if (stream.eol()) { - state.inMultilineTable = false; - } return "bracket"; } else { stream.match(/[^\|]*/); - return state.tableHeaderLine === state.lineNumber ? "property" : "string"; + return state.tableHeaderLine ? "property" : "string"; } } @@ -75,15 +76,10 @@ CodeMirror.defineMode("gherkin", function () { } else if (stream.match("|")) { // Table state.inMultilineTable = true; + state.tableHeaderLine = true; return "bracket"; - } else { - // Or abort - state.inMultilineArgument = false; - state.tableHeaderLine = null; } - - return null; } // LINE COMMENT @@ -91,76 +87,75 @@ CodeMirror.defineMode("gherkin", function () { return "comment"; // TAG - } else if (stream.match(/@\S+/)) { - return "def"; + } else if (!state.inKeywordLine && stream.match(/@\S+/)) { + return "tag"; // FEATURE - } else if (state.allowFeature && stream.match(/Feature:/)) { + } else if (!state.inKeywordLine && state.allowFeature && stream.match(/(機能|功能|フィーチャ|기능|โครงหลัก|ความสามารถ|ความต้องการทางธุรกิจ|ಹೆಚ್ಚಳ|గుణము|ਮੁਹਾਂਦਰਾ|ਨਕਸ਼ ਨੁਹਾਰ|ਖਾਸੀਅਤ|रूप लेख|وِیژگی|خاصية|תכונה|Функціонал|Функция|Функционалност|Функционал|Үзенчәлеклелек|Свойство|Особина|Мөмкинлек|Могућност|Λειτουργία|Δυνατότητα|Właściwość|Vlastnosť|Trajto|Tính năng|Savybė|Pretty much|Požiadavka|Požadavek|Potrzeba biznesowa|Özellik|Osobina|Ominaisuus|Omadus|OH HAI|Mogućnost|Mogucnost|Jellemző|Hwæt|Hwaet|Funzionalità|Funktionalitéit|Funktionalität|Funkcja|Funkcionalnost|Funkcionalitāte|Funkcia|Fungsi|Functionaliteit|Funcționalitate|Funcţionalitate|Functionalitate|Funcionalitat|Funcionalidade|Fonctionnalité|Fitur|Fīča|Feature|Eiginleiki|Egenskap|Egenskab|Característica|Caracteristica|Business Need|Aspekt|Arwedd|Ahoy matey!|Ability):/)) { state.allowScenario = true; state.allowBackground = true; state.allowPlaceholders = false; state.allowSteps = false; + state.allowMultilineArgument = false; + state.inKeywordLine = true; return "keyword"; // BACKGROUND - } else if (state.allowBackground && stream.match("Background:")) { + } else if (!state.inKeywordLine && state.allowBackground && stream.match(/(背景|배경|แนวคิด|ಹಿನ್ನೆಲೆ|నేపథ్యం|ਪਿਛੋਕੜ|पृष्ठभूमि|زمینه|الخلفية|רקע|Тарих|Предыстория|Предистория|Позадина|Передумова|Основа|Контекст|Кереш|Υπόβαθρο|Założenia|Yo\-ho\-ho|Tausta|Taust|Situācija|Rerefons|Pozadina|Pozadie|Pozadí|Osnova|Latar Belakang|Kontext|Konteksts|Kontekstas|Kontekst|Háttér|Hannergrond|Grundlage|Geçmiş|Fundo|Fono|First off|Dis is what went down|Dasar|Contexto|Contexte|Context|Contesto|Cenário de Fundo|Cenario de Fundo|Cefndir|Bối cảnh|Bakgrunnur|Bakgrunn|Bakgrund|Baggrund|Background|B4|Antecedents|Antecedentes|Ær|Aer|Achtergrond):/)) { state.allowPlaceholders = false; state.allowSteps = true; state.allowBackground = false; + state.allowMultilineArgument = false; + state.inKeywordLine = true; return "keyword"; // SCENARIO OUTLINE - } else if (state.allowScenario && stream.match("Scenario Outline:")) { + } else if (!state.inKeywordLine && state.allowScenario && stream.match(/(場景大綱|场景大纲|劇本大綱|剧本大纲|テンプレ|シナリオテンプレート|シナリオテンプレ|シナリオアウトライン|시나리오 개요|สรุปเหตุการณ์|โครงสร้างของเหตุการณ์|ವಿವರಣೆ|కథనం|ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ|ਪਟਕਥਾ ਢਾਂਚਾ|परिदृश्य रूपरेखा|سيناريو مخطط|الگوی سناریو|תבנית תרחיש|Сценарийның төзелеше|Сценарий структураси|Структура сценарію|Структура сценария|Структура сценарија|Скица|Рамка на сценарий|Концепт|Περιγραφή Σεναρίου|Wharrimean is|Template Situai|Template Senario|Template Keadaan|Tapausaihio|Szenariogrundriss|Szablon scenariusza|Swa hwær swa|Swa hwaer swa|Struktura scenarija|Structură scenariu|Structura scenariu|Skica|Skenario konsep|Shiver me timbers|Senaryo taslağı|Schema dello scenario|Scenariomall|Scenariomal|Scenario Template|Scenario Outline|Scenario Amlinellol|Scenārijs pēc parauga|Scenarijaus šablonas|Reckon it's like|Raamstsenaarium|Plang vum Szenario|Plan du Scénario|Plan du scénario|Osnova scénáře|Osnova Scenára|Náčrt Scenáru|Náčrt Scénáře|Náčrt Scenára|MISHUN SRSLY|Menggariskan Senario|Lýsing Dæma|Lýsing Atburðarásar|Konturo de la scenaro|Koncept|Khung tình huống|Khung kịch bản|Forgatókönyv vázlat|Esquema do Cenário|Esquema do Cenario|Esquema del escenario|Esquema de l'escenari|Esbozo do escenario|Delineação do Cenário|Delineacao do Cenario|All y'all|Abstrakt Scenario|Abstract Scenario):/)) { state.allowPlaceholders = true; state.allowSteps = true; + state.allowMultilineArgument = false; + state.inKeywordLine = true; return "keyword"; // EXAMPLES - } else if (state.allowScenario && stream.match("Examples:")) { + } else if (state.allowScenario && stream.match(/(例子|例|サンプル|예|ชุดของเหตุการณ์|ชุดของตัวอย่าง|ಉದಾಹರಣೆಗಳು|ఉదాహరణలు|ਉਦਾਹਰਨਾਂ|उदाहरण|نمونه ها|امثلة|דוגמאות|Үрнәкләр|Сценарији|Примеры|Примери|Приклади|Мисоллар|Мисаллар|Σενάρια|Παραδείγματα|You'll wanna|Voorbeelden|Variantai|Tapaukset|Se þe|Se the|Se ðe|Scenarios|Scenariji|Scenarijai|Przykłady|Primjeri|Primeri|Příklady|Príklady|Piemēri|Példák|Pavyzdžiai|Paraugs|Örnekler|Juhtumid|Exemplos|Exemples|Exemple|Exempel|EXAMPLZ|Examples|Esempi|Enghreifftiau|Ekzemploj|Eksempler|Ejemplos|Dữ liệu|Dead men tell no tales|Dæmi|Contoh|Cenários|Cenarios|Beispiller|Beispiele|Atburðarásir):/)) { state.allowPlaceholders = false; state.allowSteps = true; state.allowBackground = false; - state.inMultilineArgument = true; + state.allowMultilineArgument = true; return "keyword"; // SCENARIO - } else if (state.allowScenario && stream.match(/Scenario:/)) { + } else if (!state.inKeywordLine && state.allowScenario && stream.match(/(場景|场景|劇本|剧本|シナリオ|시나리오|เหตุการณ์|ಕಥಾಸಾರಾಂಶ|సన్నివేశం|ਪਟਕਥਾ|परिदृश्य|سيناريو|سناریو|תרחיש|Сценарій|Сценарио|Сценарий|Пример|Σενάριο|Tình huống|The thing of it is|Tapaus|Szenario|Swa|Stsenaarium|Skenario|Situai|Senaryo|Senario|Scenaro|Scenariusz|Scenariu|Scénario|Scenario|Scenarijus|Scenārijs|Scenarij|Scenarie|Scénář|Scenár|Primer|MISHUN|Kịch bản|Keadaan|Heave to|Forgatókönyv|Escenario|Escenari|Cenário|Cenario|Awww, look mate|Atburðarás):/)) { state.allowPlaceholders = false; state.allowSteps = true; state.allowBackground = false; + state.allowMultilineArgument = false; + state.inKeywordLine = true; return "keyword"; // STEPS - } else if (state.allowSteps && stream.match(/(Given|When|Then|And|But)/)) { + } else if (!state.inKeywordLine && state.allowSteps && stream.match(/(那麼|那么|而且|當|当|并且|同時|同时|前提|假设|假設|假定|假如|但是|但し|並且|もし|ならば|ただし|しかし|かつ|하지만|조건|먼저|만일|만약|단|그리고|그러면|และ |เมื่อ |แต่ |ดังนั้น |กำหนดให้ |ಸ್ಥಿತಿಯನ್ನು |ಮತ್ತು |ನೀಡಿದ |ನಂತರ |ಆದರೆ |మరియు |చెప్పబడినది |కాని |ఈ పరిస్థితిలో |అప్పుడు |ਪਰ |ਤਦ |ਜੇਕਰ |ਜਿਵੇਂ ਕਿ |ਜਦੋਂ |ਅਤੇ |यदि |परन्तु |पर |तब |तदा |तथा |जब |चूंकि |किन्तु |कदा |और |अगर |و |هنگامی |متى |لكن |عندما |ثم |بفرض |با فرض |اما |اذاً |آنگاه |כאשר |וגם |בהינתן |אזי |אז |אבל |Якщо |Һәм |Унда |Тоді |Тогда |То |Также |Та |Пусть |Припустимо, що |Припустимо |Онда |Но |Нехай |Нәтиҗәдә |Лекин |Ләкин |Коли |Когда |Когато |Када |Кад |К тому же |І |И |Задато |Задати |Задате |Если |Допустим |Дано |Дадено |Вә |Ва |Бирок |Әмма |Әйтик |Әгәр |Аммо |Али |Але |Агар |А також |А |Τότε |Όταν |Και |Δεδομένου |Αλλά |Þurh |Þegar |Þa þe |Þá |Þa |Zatati |Zakładając |Zadato |Zadate |Zadano |Zadani |Zadan |Za předpokladu |Za predpokladu |Youse know when youse got |Youse know like when |Yna |Yeah nah |Y'know |Y |Wun |Wtedy |When y'all |When |Wenn |WEN |wann |Ve |Và |Und |Un |ugeholl |Too right |Thurh |Thì |Then y'all |Then |Tha the |Tha |Tetapi |Tapi |Tak |Tada |Tad |Stel |Soit |Siis |Și |Şi |Si |Sed |Se |Så |Quando |Quand |Quan |Pryd |Potom |Pokud |Pokiaľ |Però |Pero |Pak |Oraz |Onda |Ond |Oletetaan |Og |Och |O zaman |Niin |Nhưng |När |Når |Mutta |Men |Mas |Maka |Majd |Mając |Mais |Maar |mä |Ma |Lorsque |Lorsqu'|Logo |Let go and haul |Kun |Kuid |Kui |Kiedy |Khi |Ketika |Kemudian |Keď |Když |Kaj |Kai |Kada |Kad |Jeżeli |Jeśli |Ja |It's just unbelievable |Ir |I CAN HAZ |I |Ha |Givun |Givet |Given y'all |Given |Gitt |Gegeven |Gegeben seien |Gegeben sei |Gdy |Gangway! |Fakat |Étant donnés |Etant donnés |Étant données |Etant données |Étant donnée |Etant donnée |Étant donné |Etant donné |Et |És |Entonces |Entón |Então |Entao |En |Eğer ki |Ef |Eeldades |E |Ðurh |Duota |Dun |Donitaĵo |Donat |Donada |Do |Diyelim ki |Diberi |Dengan |Den youse gotta |DEN |De |Dato |Dați fiind |Daţi fiind |Dati fiind |Dati |Date fiind |Date |Data |Dat fiind |Dar |Dann |dann |Dan |Dados |Dado |Dadas |Dada |Ða ðe |Ða |Cuando |Cho |Cando |Când |Cand |Cal |But y'all |But at the end of the day I reckon |BUT |But |Buh |Blimey! |Biết |Bet |Bagi |Aye |awer |Avast! |Atunci |Atesa |Atès |Apabila |Anrhegedig a |Angenommen |And y'all |And |AN |An |an |Amikor |Amennyiben |Ama |Als |Alors |Allora |Ali |Aleshores |Ale |Akkor |Ak |Adott |Ac |Aber |A zároveň |A tiež |A taktiež |A také |A |a |7 |\* )/)) { + state.inStep = true; + state.allowPlaceholders = true; + state.allowMultilineArgument = true; + state.inKeywordLine = true; return "keyword"; // INLINE STRING - } else if (!state.inMultilineArgument && stream.match(/"/)) { - stream.match(/.*?"/); + } else if (stream.match(/"[^"]*"?/)) { return "string"; - // MULTILINE ARGUMENTS - } else if (state.allowSteps && stream.eat(":")) { - if (stream.match(/\s*$/)) { - state.inMultilineArgument = true; - return "keyword"; - } else { - return null; - } - - } else if (state.allowSteps && stream.match("<")) { - if (stream.match(/.*?>/)) { - return "property"; - } else { - return null; - } + // PLACEHOLDER + } else if (state.allowPlaceholders && stream.match(/<[^>]*>?/)) { + return "attribute"; // Fall through } else { - stream.eatWhile(/[^":<]/); + stream.next(); + stream.eatWhile(/[^@"<#]/); + return null; } - - return null; } }; }); From db1753be3764e6c6ce46b74a4cdd0f2d9c90a1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haso=C5=88?= Date: Fri, 27 Dec 2013 22:53:32 +0100 Subject: [PATCH 100/155] [gherkin] Renamed tokens for placeholder and table header --- mode/gherkin/gherkin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mode/gherkin/gherkin.js b/mode/gherkin/gherkin.js index 784afc50ce..685b373df7 100644 --- a/mode/gherkin/gherkin.js +++ b/mode/gherkin/gherkin.js @@ -64,7 +64,7 @@ CodeMirror.defineMode("gherkin", function () { return "bracket"; } else { stream.match(/[^\|]*/); - return state.tableHeaderLine ? "property" : "string"; + return state.tableHeaderLine ? "header" : "string"; } } @@ -148,7 +148,7 @@ CodeMirror.defineMode("gherkin", function () { // PLACEHOLDER } else if (state.allowPlaceholders && stream.match(/<[^>]*>?/)) { - return "attribute"; + return "variable"; // Fall through } else { From 74910aebed399821cb030421762fa145bbeb52ae Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 29 Dec 2013 15:37:01 +0100 Subject: [PATCH 101/155] [markdown mode] Fix HTML embedding, add innerMode method Closes #2083 Issue #2084 --- mode/markdown/markdown.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 6370462772..b4322d1ca2 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -197,14 +197,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { function htmlBlock(stream, state) { var style = htmlMode.token(stream, state.htmlState); - if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { + if ((htmlFound && !state.htmlState.tagName && !state.htmlState.context) || + (state.md_inside && stream.current().indexOf(">") > -1)) { state.f = inlineNormal; state.block = blockNormal; - } - if (state.md_inside && stream.current().indexOf(">")!=-1) { - state.f = inlineNormal; - state.block = blockNormal; - state.htmlState.context = undefined; + state.htmlState = null; } return style; } @@ -421,13 +418,14 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } if (ch === '<' && stream.match(/^\w/, false)) { - if (stream.string.indexOf(">")!=-1) { + if (stream.string.indexOf(">") != -1) { var atts = stream.string.substring(1,stream.string.indexOf(">")); if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { state.md_inside = true; } } stream.backUp(1); + state.htmlState = CodeMirror.startState(htmlMode); return switchBlock(stream, state, htmlBlock); } @@ -601,7 +599,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return savedInlineRE[endChar]; } - return { + var mode = { startState: function() { return { f: blockNormal, @@ -610,7 +608,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { thisLineHasContent: false, block: blockNormal, - htmlState: CodeMirror.startState(htmlMode), + htmlState: null, indentation: 0, inline: inlineNormal, @@ -641,7 +639,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { thisLineHasContent: s.thisLineHasContent, block: s.block, - htmlState: CodeMirror.copyState(htmlMode, s.htmlState), + htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState), indentation: s.indentation, localMode: s.localMode, @@ -671,10 +669,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.formatting = false; if (stream.sol()) { - var forceBlankLine = false; - if (stream.match(/^\s*$/, true) || state.header) { - forceBlankLine = true; - } + var forceBlankLine = stream.match(/^\s*$/, true) || state.header; // Reset state.header state.header = 0; @@ -712,11 +707,17 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return state.f(stream, state); }, + innerMode: function(state) { + if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode}; + if (state.block == local) return {state: state.localState, mode: state.localMode}; + return {state: state, mode: mode}; + }, + blankLine: blankLine, getType: getType }; - + return mode; }, "xml"); CodeMirror.defineMIME("text/x-markdown", "markdown"); From a2d531380d71279f95a8293bbb36f2ae40ee55c1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sun, 29 Dec 2013 16:46:48 +0100 Subject: [PATCH 102/155] [indentwrap demo] Fix inherited text-indent messing up tabs Issue #2085 --- demo/indentwrap.html | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/indentwrap.html b/demo/indentwrap.html index 6c1fc596e1..efdba5d59e 100644 --- a/demo/indentwrap.html +++ b/demo/indentwrap.html @@ -9,6 +9,7 @@
    +
    addModeClass: boolean
    +
    When enabled (off by default), an extra CSS class will be + added to each token, indicating the + (inner) mode that produced it, prefixed + with "cm-m-". For example, tokens from the XML mode + will get the cm-m-xml class.
    +
    maxHighlightLength: number
    When highlighting long lines, in order to stay responsive, the editor will give up and simply style the rest of the line as diff --git a/lib/codemirror.js b/lib/codemirror.js index bc5da913ce..2d2fb0b45c 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -195,6 +195,10 @@ window.CodeMirror = (function() { function loadMode(cm) { cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { cm.doc.iter(function(line) { if (line.stateAfter) line.stateAfter = null; if (line.styles) line.styles = null; @@ -3312,7 +3316,7 @@ window.CodeMirror = (function() { option("indentWithTabs", false); option("smartIndent", true); option("tabSize", 4, function(cm) { - loadMode(cm); + resetModeState(cm); clearCaches(cm); regChange(cm); }, true); @@ -3372,12 +3376,13 @@ window.CodeMirror = (function() { option("cursorHeight", 1); option("workTime", 100); option("workDelay", 100); - option("flattenSpans", true); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); option("pollInterval", 100); option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); option("historyEventDelay", 500); option("viewportMargin", 10, function(cm){cm.refresh();}, true); - option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); option("crudeMeasuringFrom", 10000); option("moveInputWithCursor", true, function(cm, val) { if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; @@ -4381,6 +4386,10 @@ window.CodeMirror = (function() { } else { style = mode.token(stream, state); } + if (cm.options.addModeClass) { + var mName = CodeMirror.innerMode(mode, state).mode.name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } if (!flattenSpans || curStyle != style) { if (curStart < stream.start) f(stream.start, curStyle); curStart = stream.start; curStyle = style; @@ -4452,7 +4461,7 @@ window.CodeMirror = (function() { } } - var styleToClassCache = {}; + var styleToClassCache = {}, styleToClassCacheWithMode = {}; function interpretTokenStyle(style, builder) { if (!style) return null; for (;;) { @@ -4465,8 +4474,9 @@ window.CodeMirror = (function() { else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop])) builder[prop] += " " + lineClass[2]; } - return styleToClassCache[style] || - (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); + var cache = builder.cm.options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = "cm-" + style.replace(/ +/g, " cm-")); } function buildLineContent(cm, realLine, measure, copyWidgets) { From 337e435328b920af0f74c9c707df9b4326b210b1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 6 Jan 2014 13:23:36 +0100 Subject: [PATCH 120/155] Add a disableInput option, change vim keymap to use it. --- keymap/vim.js | 5 ++++- lib/codemirror.js | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index c83f6f08ae..78b83e5116 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -331,12 +331,14 @@ CodeMirror.defineOption('vimMode', false, function(cm, val) { if (val) { cm.setOption('keyMap', 'vim'); + cm.setOption('disableInput', true); CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); cm.on('beforeSelectionChange', beforeSelectionChange); maybeInitVimState(cm); CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); } else if (cm.state.vim) { cm.setOption('keyMap', 'default'); + cm.setOption('disableInput', false); cm.off('beforeSelectionChange', beforeSelectionChange); CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); cm.state.vim = null; @@ -1749,6 +1751,7 @@ cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); } cm.setOption('keyMap', 'vim-insert'); + cm.setOption('disableInput', false); if (actionArgs && actionArgs.replace) { // Handle Replace-mode as a special case of insert mode. cm.toggleOverwrite(true); @@ -3509,7 +3512,6 @@ var cmToVimKeymap = { 'nofallthrough': true, - 'disableInput': true, 'style': 'fat-cursor' }; function bindKeys(keys, modifier) { @@ -3556,6 +3558,7 @@ cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true); vim.insertMode = false; cm.setOption('keyMap', 'vim'); + cm.setOption('disableInput', true); cm.toggleOverwrite(false); // exit replace mode if we were in it. CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); } diff --git a/lib/codemirror.js b/lib/codemirror.js index 2d2fb0b45c..0bff9eccf4 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -248,8 +248,6 @@ window.CodeMirror = (function() { var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); - if (cm.state.disableInput && !map.disableInput) resetInput(cm, true); - cm.state.disableInput = map.disableInput; } function themeChanged(cm) { @@ -1487,7 +1485,7 @@ window.CodeMirror = (function() { // supported or compatible enough yet to rely on.) function readInput(cm) { var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false; if (cm.state.pasteIncoming && cm.state.fakedLastChar) { input.value = input.value.substring(0, input.value.length - 1); cm.state.fakedLastChar = false; @@ -3369,6 +3367,7 @@ window.CodeMirror = (function() { if (!val) resetInput(cm, true); } }); + option("disableInput", false, function(cm, val) {if (!val) resetInput(cm, true);}, true); option("dragDrop", true); option("cursorBlinkRate", 530); From 58113e8a8945cfd8f6310240c5b86564d59b0e07 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 6 Jan 2014 13:28:58 +0100 Subject: [PATCH 121/155] Adjust addons to respect disableInput --- addon/comment/continuecomment.js | 2 +- addon/edit/closebrackets.js | 8 +++++--- addon/edit/closetag.js | 5 +++-- addon/edit/continuelist.js | 2 ++ lib/codemirror.js | 4 +++- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/addon/comment/continuecomment.js b/addon/comment/continuecomment.js index 94e5a3760f..a3370a6072 100644 --- a/addon/comment/continuecomment.js +++ b/addon/comment/continuecomment.js @@ -5,7 +5,7 @@ function continueComment(cm) { var pos = cm.getCursor(), token = cm.getTokenAt(pos); - if (token.type != "comment") return CodeMirror.Pass; + if (token.type != "comment" || cm.getOption("disableInput")) return CodeMirror.Pass; var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode; var insert; diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index 88718b7729..0575222be6 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -28,7 +28,7 @@ var map = { name : "autoCloseBrackets", Backspace: function(cm) { - if (cm.somethingSelected()) return CodeMirror.Pass; + if (cm.somethingSelected() || cm.getOption("disableInput")) return CodeMirror.Pass; var cur = cm.getCursor(), around = charsAround(cm, cur); if (around && pairs.indexOf(around) % 2 == 0) cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1)); @@ -49,7 +49,8 @@ else cm.execCommand("goCharRight"); } map["'" + left + "'"] = function(cm) { - if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment") + if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment" || + cm.getOption("disableInput")) return CodeMirror.Pass; if (cm.somethingSelected()) return surround(cm); if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return; @@ -70,7 +71,8 @@ function buildExplodeHandler(pairs) { return function(cm) { var cur = cm.getCursor(), around = charsAround(cm, cur); - if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; + if (!around || pairs.indexOf(around) % 2 != 0 || cm.getOption("disableInput")) + return CodeMirror.Pass; cm.operation(function() { var newPos = CodeMirror.Pos(cur.line + 1, 0); cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input"); diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 60e204bcf2..cad776a783 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -43,7 +43,7 @@ function autoCloseGT(cm) { var pos = cm.getCursor(), tok = cm.getTokenAt(pos); var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; - if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass; + if (inner.mode.name != "xml" || !state.tagName || cm.getOption("disableInput")) return CodeMirror.Pass; var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html"; var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose); @@ -76,7 +76,8 @@ var pos = cm.getCursor(), tok = cm.getTokenAt(pos); var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; if (tok.type == "string" || tok.string.charAt(0) != "<" || - tok.start != pos.ch - 1 || inner.mode.name != "xml") + tok.start != pos.ch - 1 || inner.mode.name != "xml" || + cm.getOption("disableInput")) return CodeMirror.Pass; var tagName = state.context && state.context.tagName; diff --git a/addon/edit/continuelist.js b/addon/edit/continuelist.js index 826d17d716..190b41dcbe 100644 --- a/addon/edit/continuelist.js +++ b/addon/edit/continuelist.js @@ -5,6 +5,8 @@ unorderedBullets = '*+-'; CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + var pos = cm.getCursor(), inList = cm.getStateAfter(pos.line).list !== false, match; diff --git a/lib/codemirror.js b/lib/codemirror.js index 0bff9eccf4..c728d97c45 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3582,7 +3582,9 @@ window.CodeMirror = (function() { indentAuto: function(cm) {cm.indentSelection("smart");}, indentMore: function(cm) {cm.indentSelection("add");}, indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");}, + insertTab: function(cm) { + cm.replaceSelection("\t", "end", "+input"); + }, defaultTab: function(cm) { if (cm.somethingSelected()) cm.indentSelection("add"); else cm.replaceSelection("\t", "end", "+input"); From ad2fccb4fd38344441593201f7491e15ef2d2255 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 6 Jan 2014 13:48:23 +0100 Subject: [PATCH 122/155] [anyword-hint addon] Optimize Issue #2109 --- addon/hint/anyword-hint.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/addon/hint/anyword-hint.js b/addon/hint/anyword-hint.js index 36ff618e09..67eaa0f617 100644 --- a/addon/hint/anyword-hint.js +++ b/addon/hint/anyword-hint.js @@ -13,22 +13,20 @@ var curWord = start != end && curLine.slice(start, end); var list = [], seen = {}; - function scan(dir) { + var re = new RegExp(word.source, "g"); + for (var dir = -1; dir <= 1; dir += 2) { var line = cur.line, end = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir; for (; line != end; line += dir) { var text = editor.getLine(line), m; - var re = new RegExp(word.source, "g"); while (m = re.exec(text)) { if (line == cur.line && m[0] === curWord) continue; - if ((!curWord || m[0].indexOf(curWord) == 0) && !seen.hasOwnProperty(m[0])) { + if ((!curWord || m[0].indexOf(curWord) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) { seen[m[0]] = true; list.push(m[0]); } } } } - scan(-1); - scan(1); return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)}; }); })(); From 22dd7235e352b2e1caf3fe9b1656989d5983b968 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 6 Jan 2014 14:28:52 +0100 Subject: [PATCH 123/155] Use lastIndexOf(a, 0) to check starts-with in hint addons Issue #2109 --- addon/hint/anyword-hint.js | 2 +- addon/hint/css-hint.js | 2 +- addon/hint/javascript-hint.js | 2 +- addon/hint/pig-hint.js | 2 +- addon/hint/python-hint.js | 2 +- addon/hint/xml-hint.js | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/addon/hint/anyword-hint.js b/addon/hint/anyword-hint.js index 67eaa0f617..a144768c81 100644 --- a/addon/hint/anyword-hint.js +++ b/addon/hint/anyword-hint.js @@ -20,7 +20,7 @@ var text = editor.getLine(line), m; while (m = re.exec(text)) { if (line == cur.line && m[0] === curWord) continue; - if ((!curWord || m[0].indexOf(curWord) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) { + if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) { seen[m[0]] = true; list.push(m[0]); } diff --git a/addon/hint/css-hint.js b/addon/hint/css-hint.js index 43bdf5de02..6789c458ba 100644 --- a/addon/hint/css-hint.js +++ b/addon/hint/css-hint.js @@ -20,7 +20,7 @@ var result = []; function add(keywords) { for (var name in keywords) - if (!word || name.indexOf(word) == 0) + if (!word || name.lastIndexOf(word, 0) == 0) result.push(name); } diff --git a/addon/hint/javascript-hint.js b/addon/hint/javascript-hint.js index c66b0a7a5b..c347c206ec 100644 --- a/addon/hint/javascript-hint.js +++ b/addon/hint/javascript-hint.js @@ -87,7 +87,7 @@ function getCompletions(token, context, keywords, options) { var found = [], start = token.string; function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); + if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); } function gatherCompletions(obj) { if (typeof obj == "string") forEach(stringProps, maybeAdd); diff --git a/addon/hint/pig-hint.js b/addon/hint/pig-hint.js index 7ef336cecf..6c0c523774 100644 --- a/addon/hint/pig-hint.js +++ b/addon/hint/pig-hint.js @@ -84,7 +84,7 @@ function getCompletions(token, context) { var found = [], start = token.string; function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); + if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); } function gatherCompletions(obj) { diff --git a/addon/hint/python-hint.js b/addon/hint/python-hint.js index ace1941bd6..248284e8c5 100644 --- a/addon/hint/python-hint.js +++ b/addon/hint/python-hint.js @@ -62,7 +62,7 @@ function getCompletions(token, context) { var found = [], start = token.string; function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); + if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); } function gatherCompletions(_obj) { diff --git a/addon/hint/xml-hint.js b/addon/hint/xml-hint.js index a721743449..69f2b771fe 100644 --- a/addon/hint/xml-hint.js +++ b/addon/hint/xml-hint.js @@ -20,13 +20,13 @@ var cx = inner.state.context, curTag = cx && tags[cx.tagName]; var childList = cx ? curTag && curTag.children : tags["!top"]; if (childList) { - for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].indexOf(prefix) == 0) + for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0) result.push("<" + childList[i]); } else { - for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.indexOf(prefix) == 0)) + for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.lastIndexOf(prefix, 0) == 0)) result.push("<" + name); } - if (cx && (!prefix || ("/" + cx.tagName).indexOf(prefix) == 0)) + if (cx && (!prefix || ("/" + cx.tagName).lastIndexOf(prefix, 0) == 0)) result.push(""); } else { // Attribute completion @@ -46,14 +46,14 @@ } replaceToken = true; } - for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].indexOf(prefix) == 0) + for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0) result.push(quote + atValues[i] + quote); } else { // An attribute name if (token.type == "attribute") { prefix = token.string; replaceToken = true; } - for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.indexOf(prefix) == 0)) + for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0)) result.push(attr); } } From d73e38fdbf43508267b257a595cd962e8572415a Mon Sep 17 00:00:00 2001 From: AtomicPages LLC Date: Sun, 5 Jan 2014 14:41:57 -0800 Subject: [PATCH 124/155] [pastel-on-dark theme] Add --- demo/theme.html | 2 ++ theme/pastel-on-dark.css | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 theme/pastel-on-dark.css diff --git a/demo/theme.html b/demo/theme.html index a6e2b2da09..e36e62eaa3 100644 --- a/demo/theme.html +++ b/demo/theme.html @@ -23,6 +23,7 @@ + @@ -88,6 +89,7 @@ + diff --git a/theme/pastel-on-dark.css b/theme/pastel-on-dark.css new file mode 100644 index 0000000000..df95699a01 --- /dev/null +++ b/theme/pastel-on-dark.css @@ -0,0 +1,49 @@ +/** + * Pastel On Dark theme ported from ACE editor + * @license MIT + * @copyright AtomicPages LLC 2014 + * @author Dennis Thompson, AtomicPages LLC + * @version 1.1 + * @source https://github.com/atomicpages/codemirror-pastel-on-dark-theme + */ + +.cm-s-pastel-on-dark.CodeMirror { + background: #2c2827; + color: #8F938F; + line-height: 1.5; + font-family: consolas, Courier, monospace; + font-size: 14px; +} +.cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2) !important; } +.cm-s-pastel-on-dark .CodeMirror-gutters { + background: #34302f; + border-right: 0px; + padding: 0 3px; +} +.cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F; } +.cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } +.cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF; } +.cm-s-pastel-on-dark span.cm-atom { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-number { color: #CCCCCC; } +.cm-s-pastel-on-dark span.cm-property { color: #8F938F; } +.cm-s-pastel-on-dark span.cm-attribute { color: #a6e22e; } +.cm-s-pastel-on-dark span.cm-keyword { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-string { color: #66A968; } +.cm-s-pastel-on-dark span.cm-variable { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-variable-2 { color: #BEBF55; } +.cm-s-pastel-on-dark span.cm-variable-3 { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-def { color: #757aD8; } +.cm-s-pastel-on-dark span.cm-bracket { color: #f8f8f2; } +.cm-s-pastel-on-dark span.cm-tag { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-link { color: #ae81ff; } +.cm-s-pastel-on-dark span.cm-qualifier,.cm-s-pastel-on-dark span.cm-builtin { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-error { + background: #757aD8; + color: #f8f8f0; +} +.cm-s-pastel-on-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.031) !important; } +.cm-s-pastel-on-dark .CodeMirror-matchingbracket { + border: 1px solid rgba(255,255,255,0.25); + color: #8F938F !important; + margin: -1px -1px 0 -1px; +} From a8440788cdbfc79f998a84b8320d047936dda60c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 6 Jan 2014 20:11:54 +0100 Subject: [PATCH 125/155] [multiplex addon] Support end: null to mean switch back at end of line Issue #2111 --- addon/mode/multiplex.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/addon/mode/multiplex.js b/addon/mode/multiplex.js index 32cc579c3a..614ab1add3 100644 --- a/addon/mode/multiplex.js +++ b/addon/mode/multiplex.js @@ -47,7 +47,11 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { return outerToken; } else { var curInner = state.innerActive, oldContent = stream.string; - var found = indexOf(oldContent, curInner.close, stream.pos); + if (!curInner.close && stream.sol()) { + state.innerActive = state.inner = null; + return this.token(stream, state); + } + var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos) : -1; if (found == stream.pos) { stream.match(curInner.close); state.innerActive = state.inner = null; @@ -56,8 +60,6 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { if (found > -1) stream.string = oldContent.slice(0, found); var innerToken = curInner.mode.token(stream, state.inner); if (found > -1) stream.string = oldContent; - var cur = stream.current(), found = cur.indexOf(curInner.close); - if (found > -1) stream.backUp(cur.length - found); if (curInner.innerStyle) { if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle; From c1b518e91e93c5812093810dfb5d049971d1a926 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 7 Jan 2014 12:50:19 +0100 Subject: [PATCH 126/155] [hardwrap addon] Make methods return true when they made a change --- addon/wrap/hardwrap.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/addon/wrap/hardwrap.js b/addon/wrap/hardwrap.js index 1faa3d84a6..3023ca5b8e 100644 --- a/addon/wrap/hardwrap.js +++ b/addon/wrap/hardwrap.js @@ -36,7 +36,7 @@ var killTrailing = options.killTrailingSpace !== false; var changes = [], curLine = "", curNo = from.line; var lines = cm.getRange(from, to, false); - if (!lines.length) return; + if (!lines.length) return false; var leadingSpace = lines[0].match(/^[ \t]*/)[0]; for (var i = 0; i < lines.length; ++i) { @@ -79,17 +79,18 @@ cm.replaceRange(change.text, change.from, change.to); } }); + return changes.length > 0; } CodeMirror.defineExtension("wrapParagraph", function(pos, options) { options = options || {}; if (!pos) pos = this.getCursor(); var para = findParagraph(this, pos, options); - wrapRange(this, Pos(para.from, 0), Pos(para.to - 1), options); + return wrapRange(this, Pos(para.from, 0), Pos(para.to - 1), options); }); CodeMirror.defineExtension("wrapRange", function(from, to, options) { - wrapRange(this, from, to, options || {}); + return wrapRange(this, from, to, options || {}); }); CodeMirror.defineExtension("wrapParagraphsInRange", function(from, to, options) { @@ -100,9 +101,11 @@ paras.push(para); line = para.to; } + var madeChange = false; if (paras.length) cm.operation(function() { for (var i = paras.length - 1; i >= 0; --i) - wrapRange(cm, Pos(paras[i].from, 0), Pos(paras[i].to - 1), options); + madeChange = madeChange || wrapRange(cm, Pos(paras[i].from, 0), Pos(paras[i].to - 1), options); }); + return madeChange; }); })(); From bc5bc136b16b2df4ea3285f2bb6fd3f5a711afd4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 6 Jan 2014 20:54:32 +0100 Subject: [PATCH 127/155] [xml mode] Fix bug with value-less attributes Closes #2112 --- mode/xml/xml.js | 1 - 1 file changed, 1 deletion(-) diff --git a/mode/xml/xml.js b/mode/xml/xml.js index bf1fd14a4e..c75200ec7b 100644 --- a/mode/xml/xml.js +++ b/mode/xml/xml.js @@ -254,7 +254,6 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { function attrEqState(type, stream, state) { if (type == "equals") return attrValueState; if (!Kludges.allowMissing) setStyle = "error"; - else if (type == "word") { setStyle = "attribute"; return attrState; } return attrState(type, stream, state); } function attrValueState(type, stream, state) { From 232d819c4b17bdd6ca372fac4d32b1d101c5d0bd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 8 Jan 2014 09:50:19 +0100 Subject: [PATCH 128/155] [mbo theme] Fix searching style --- theme/mbo.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/mbo.css b/theme/mbo.css index 93fe3ee24c..bb52e6d17f 100644 --- a/theme/mbo.css +++ b/theme/mbo.css @@ -30,7 +30,7 @@ .cm-s-mbo .CodeMirror-matchingtag {background: #4e4e4e;} -div.CodeMirror span.CodeMirror-searching { +.cm-s-mbo span.cm-searching { background-color: none; background: none; box-shadow: 0 0 0 1px #ffffec; From 83df2a9ef5a4f68eefd0e1e01f5c7417c32196d7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 8 Jan 2014 11:40:10 +0100 Subject: [PATCH 129/155] Apply some IE workarounds to IE11 as well. Despite the 'I am not IE!' user-agent string, it seems to have inherited a lot of the old bugs. Closes #2118 --- index.html | 2 +- lib/codemirror.js | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index f9a93897fb..914bb7867d 100644 --- a/index.html +++ b/index.html @@ -133,7 +133,7 @@
  • Programmable gutters
  • Making ranges of text styled, read-only, or atomic
  • Bi-directional text support -
  • Many other methods and addons... +
  • Many other methods and addons... diff --git a/lib/codemirror.js b/lib/codemirror.js index c728d97c45..728b7faef4 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -10,10 +10,11 @@ window.CodeMirror = (function() { // IE11 currently doesn't count as 'ie', since it has almost none of // the same bugs as earlier versions. Use ie_gt10 to handle // incompatibilities in that version. - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); + var old_ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = old_ie && (document.documentMode == null || document.documentMode < 8); + var ie_lt9 = old_ie && (document.documentMode == null || document.documentMode < 9); var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent); + var ie = old_ie || ie_gt10; var webkit = /WebKit\//.test(navigator.userAgent); var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); var chrome = /Chrome\//.test(navigator.userAgent); @@ -35,7 +36,7 @@ window.CodeMirror = (function() { if (opera_version && opera_version >= 15) { opera = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); - var captureMiddleClick = gecko || (ie && !ie_lt9); + var captureMiddleClick = gecko || (old_ie && !ie_lt9); // Optimize some code when these features are not used var sawReadOnlySpans = false, sawCollapsedSpans = false; @@ -75,7 +76,7 @@ window.CodeMirror = (function() { // Override magic textarea content restore that IE sometimes does // on our hidden textarea on reload - if (ie) setTimeout(bind(resetInput, this, true), 20); + if (old_ie) setTimeout(bind(resetInput, this, true), 20); registerEventHandlers(this); // IE throws unspecified error in certain cases, when @@ -1057,7 +1058,7 @@ window.CodeMirror = (function() { // doesn't work when wrapping is on, but in that case the // situation is slightly better, since IE does cache line-wrapping // information and only recomputes per-line. - if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { + if (old_ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { var fragment = document.createDocumentFragment(); var chunk = 10, n = pre.childNodes.length; for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { @@ -1563,7 +1564,7 @@ window.CodeMirror = (function() { function registerEventHandlers(cm) { var d = cm.display; on(d.scroller, "mousedown", operation(cm, onMouseDown)); - if (ie) + if (old_ie) on(d.scroller, "dblclick", operation(cm, function(e) { if (signalDOMEvent(cm, e)) return; var pos = posFromMouse(cm, e); @@ -1682,8 +1683,8 @@ window.CodeMirror = (function() { // Needed to handle Tab key in KHTML if (khtml) on(d.sizer, "mouseup", function() { - if (document.activeElement == d.input) d.input.blur(); - focusInput(cm); + if (document.activeElement == d.input) d.input.blur(); + focusInput(cm); }); } @@ -1841,7 +1842,7 @@ window.CodeMirror = (function() { } var move = operation(cm, function(e) { - if (!ie && !e_button(e)) done(e); + if (!old_ie && !e_button(e)) done(e); else extend(e); }); var up = operation(cm, done); @@ -1986,7 +1987,7 @@ window.CodeMirror = (function() { // know one. These don't have to be accurate -- the result of them // being wrong would just be a slight flicker on the first wheel // scroll (if it is large enough). - if (ie) wheelPixelsPerUnit = -.53; + if (old_ie) wheelPixelsPerUnit = -.53; else if (gecko) wheelPixelsPerUnit = 15; else if (chrome) wheelPixelsPerUnit = -.7; else if (safari) wheelPixelsPerUnit = -1/3; @@ -2140,7 +2141,7 @@ window.CodeMirror = (function() { var cm = this; if (!cm.state.focused) onFocus(cm); if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - if (ie && e.keyCode == 27) e.returnValue = false; + if (old_ie && e.keyCode == 27) e.returnValue = false; var code = e.keyCode; // IE does strange things with escape. cm.doc.sel.shift = code == 16 || e.shiftKey; @@ -2231,7 +2232,7 @@ window.CodeMirror = (function() { // Try to detect the user choosing select-all if (display.input.selectionStart != null) { - if (!ie || ie_lt9) prepareSelectAllHack(); + if (!old_ie || ie_lt9) prepareSelectAllHack(); clearTimeout(detectingSelectAll); var i = 0, poll = function(){ if (display.prevInput == "\u200b" && display.input.selectionStart == 0) @@ -2243,7 +2244,7 @@ window.CodeMirror = (function() { } } - if (ie && !ie_lt9) prepareSelectAllHack(); + if (old_ie && !ie_lt9) prepareSelectAllHack(); if (captureMiddleClick) { e_stop(e); var mouseup = function() { @@ -4494,7 +4495,7 @@ window.CodeMirror = (function() { builder.measure = line == realLine && measure; builder.pos = 0; builder.addToken = builder.measure ? buildTokenMeasure : buildToken; - if ((ie || webkit) && cm.getOption("lineWrapping")) + if ((old_ie || webkit) && cm.getOption("lineWrapping")) builder.addToken = buildTokenSplitSpaces(builder.addToken); var next = insertLineContent(line, builder, getLineStyles(cm, line)); if (measure && line == realLine && !builder.measuredSomething) { @@ -4513,7 +4514,7 @@ window.CodeMirror = (function() { // Work around problem with the reported dimensions of single-char // direction spans on IE (issue #1129). See also the comment in // cursorCoords. - if (measure && (ie || ie_gt10) && (order = getOrder(line))) { + if (measure && ie && (order = getOrder(line))) { var l = order.length - 1; if (order[l].from == order[l].to) --l; var last = order[l], prev = order[l - 1]; @@ -4595,7 +4596,7 @@ window.CodeMirror = (function() { // In IE single-space nodes wrap differently than spaces // embedded in larger text nodes, except when set to // white-space: normal (issue #1268). - if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && + if (old_ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && i < text.length - 1 && !/\s/.test(text.charAt(i + 1))) span.style.whiteSpace = "normal"; builder.pos += ch.length; From d84acac4f92b14b46c16f157bdf8243ce314b2b5 Mon Sep 17 00:00:00 2001 From: zziuni Date: Thu, 19 Dec 2013 17:22:48 +0900 Subject: [PATCH 130/155] [sql hint] support multi-queries on the table alias Now, Sql-hint assume one query to search for table of alias. It support multi-queries for alias. --- addon/hint/sql-hint.js | 75 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/addon/hint/sql-hint.js b/addon/hint/sql-hint.js index 95f6b50a15..d00781cff9 100644 --- a/addon/hint/sql-hint.js +++ b/addon/hint/sql-hint.js @@ -3,6 +3,10 @@ var tables; var keywords; + var CONS = { + QUERY_DIV: ";", + ALIAS_KEYWORD: "AS" + }; function getKeywords(editor) { var mode = editor.doc.modeOption; @@ -34,11 +38,10 @@ var string = token.string.substr(1); var prevCur = CodeMirror.Pos(cur.line, token.start); var table = editor.getTokenAt(prevCur).string; - var columns = tables[table]; - if(!columns) { + if( !tables.hasOwnProperty( table ) ){ table = findTableByAlias(table, editor); } - columns = tables[table]; + var columns = tables[table]; if(!columns) { return; } @@ -46,32 +49,72 @@ function(w) {return "." + w;}); } - function eachWord(line, f) { - var words = line.text.split(" "); - for(var i = 0; i < words.length; i++) { - f(words[i]); + function eachWord(lineText, f) { + if( !lineText ){return;} + var excepted = /[,;]/g; + var words = lineText.split( " " ); + for( var i = 0; i < words.length; i++ ){ + f( words[i]?words[i].replace( excepted, '' ) : '' ); } } - // Tries to find possible table name from alias. + function convertCurToNumber( cur ){ + // max characters of a line is 999,999. + return cur.line + cur.ch / Math.pow( 10, 6 ); + } + + function convertNumberToCur( num ){ + return CodeMirror.Pos(Math.floor( num ), +num.toString().split( '.' ).pop()); + } + function findTableByAlias(alias, editor) { + var doc = editor.doc; + var fullQuery = doc.getValue(); var aliasUpperCase = alias.toUpperCase(); var previousWord = ""; var table = ""; + var separator = []; + var validRange = { + start: CodeMirror.Pos( 0, 0 ), + end: CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length ) + }; - editor.eachLine(function(line) { - eachWord(line, function(word) { + //add separator + var indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV ); + while( indexOfSeparator != -1 ){ + separator.push( doc.posFromIndex(indexOfSeparator)); + indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV, indexOfSeparator+1); + } + separator.unshift( CodeMirror.Pos( 0, 0 ) ); + separator.push( CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) ); + + //find valieRange + var prevItem = 0; + var current = convertCurToNumber( editor.getCursor() ); + for( var i=0; i< separator.length; i++){ + var _v = convertCurToNumber( separator[i] ); + if( current > prevItem && current <= _v ){ + validRange = { start: convertNumberToCur( prevItem ), end: convertNumberToCur( _v ) }; + break; + } + prevItem = _v; + } + + var query = doc.getRange(validRange.start, validRange.end, false); + + for(var i=0; i < query.length; i++){ + var lineText = query[i]; + eachWord( lineText, function( word ){ var wordUpperCase = word.toUpperCase(); - if(wordUpperCase === aliasUpperCase) { - if(tables.hasOwnProperty(previousWord)) { + if( wordUpperCase === aliasUpperCase && tables.hasOwnProperty( previousWord ) ){ table = previousWord; - } } - if(wordUpperCase !== "AS") { + if( wordUpperCase !== CONS.ALIAS_KEYWORD ){ previousWord = word; } }); - }); + if( table ){ break; } + } return table; } @@ -80,9 +123,7 @@ keywords = keywords || getKeywords(editor); var cur = editor.getCursor(); var token = editor.getTokenAt(cur); - var result = []; - var search = token.string.trim(); addMatches(result, search, keywords, From eacf20f8bf113c546cf06ec9d1dea40bb3818dd3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 9 Jan 2014 23:36:36 +0100 Subject: [PATCH 131/155] Fix measuring of extending characters, include all extending code points Issue #2125 Issue #2115 --- lib/codemirror.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 728b7faef4..ad29f175a8 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1294,7 +1294,7 @@ window.CodeMirror = (function() { if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { var ch = x < fromX || x - fromX <= toX - x ? from : to; var xDiff = x - (ch == from ? fromX : toX); - while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, xDiff < 0 ? -1 : xDiff ? 1 : 0); return pos; @@ -4581,13 +4581,12 @@ window.CodeMirror = (function() { function buildTokenMeasure(builder, text, style, startStyle, endStyle) { var wrapping = builder.cm.options.lineWrapping; for (var i = 0; i < text.length; ++i) { - var ch = text.charAt(i), start = i == 0; - if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { - ch = text.slice(i, i + 2); - ++i; - } else if (i && wrapping && spanAffectsWrapping(text, i)) { + var start = i == 0, to = i + 1; + while (to < text.length && isExtendingChar(text.charAt(to))) ++to; + var ch = text.slice(i, to); + i = to - 1; + if (i && wrapping && spanAffectsWrapping(text, i)) builder.pre.appendChild(elt("wbr")); - } var old = builder.measure[builder.pos]; var span = builder.measure[builder.pos] = buildToken(builder, ch, style, @@ -5604,7 +5603,8 @@ window.CodeMirror = (function() { return true; } - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0-\u1DFF\u20D0-\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20-\uFE2F]/; + 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\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } // DOM UTILITIES @@ -5831,7 +5831,7 @@ window.CodeMirror = (function() { function moveInLine(line, pos, dir, byUnit) { if (!byUnit) return pos + dir; do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); return pos; } @@ -5866,7 +5866,7 @@ window.CodeMirror = (function() { function moveLogically(line, start, dir, byUnit) { var target = start + dir; - if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; return target < 0 || target > line.text.length ? null : target; } From 51f61299a8d5649e35be7812a2db586f3777c1e7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 10 Jan 2014 09:22:06 +0100 Subject: [PATCH 132/155] Re-add continuing surrogate pair codes to extendingChars --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index ad29f175a8..d2930ed270 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5603,7 +5603,7 @@ window.CodeMirror = (function() { return true; } - 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\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + 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]/; function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } // DOM UTILITIES From 95eeb53e2a5bd91c36b0e086be64102bcb45f45d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 10 Jan 2014 09:57:32 +0100 Subject: [PATCH 133/155] [javascript mode] Style operators Closes #2123 --- mode/javascript/javascript.js | 6 +++--- mode/javascript/test.js | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index c113fcb433..1985c6bbf5 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -83,7 +83,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { return ret(ch); } else if (ch == "=" && stream.eat(">")) { - return ret("=>"); + return ret("=>", "operator"); } else if (ch == "0" && stream.eat(/x/i)) { stream.eatWhile(/[\da-f]/i); return ret("number", "number"); @@ -104,7 +104,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return ret("regexp", "string-2"); } else { stream.eatWhile(isOperatorChar); - return ret("operator", null, stream.current()); + return ret("operator", "operator", stream.current()); } } else if (ch == "`") { state.tokenize = tokenQuasi; @@ -114,7 +114,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return ret("error", "error"); } else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); - return ret("operator", null, stream.current()); + return ret("operator", "operator", stream.current()); } else { stream.eatWhile(/[\w\$_]/); var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; diff --git a/mode/javascript/test.js b/mode/javascript/test.js index 8abd633542..cbb936d963 100644 --- a/mode/javascript/test.js +++ b/mode/javascript/test.js @@ -3,15 +3,15 @@ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("locals", - "[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] = [number 10]; [keyword return] [variable-2 a] + [variable-2 c] + [variable d]; }"); + "[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }"); MT("comma-and-binop", - "[keyword function](){ [keyword var] [def x] = [number 1] + [number 2], [def y]; }"); + "[keyword function](){ [keyword var] [def x] [operator =] [number 1] [operator +] [number 2], [def y]; }"); MT("destructuring", "([keyword function]([def a], [[[def b], [def c] ]]) {", - " [keyword let] {[def d], [property foo]: [def c]=[number 10], [def x]} = [variable foo]([variable-2 a]);", - " [[[variable-2 c], [variable y] ]] = [variable-2 c];", + " [keyword let] {[def d], [property foo]: [def c][operator =][number 10], [def x]} [operator =] [variable foo]([variable-2 a]);", + " [[[variable-2 c], [variable y] ]] [operator =] [variable-2 c];", "})();"); MT("class", @@ -19,13 +19,13 @@ " [[ [string-2 /expr/] ]]: [number 24],", " [property constructor]([def x], [def y]) {", " [keyword super]([string 'something']);", - " [keyword this].[property x] = [variable-2 x];", + " [keyword this].[property x] [operator =] [variable-2 x];", " }", "}"); MT("module", "[keyword module] [string 'foo'] {", - " [keyword export] [keyword let] [def x] = [number 42];", + " [keyword export] [keyword let] [def x] [operator =] [number 42];", " [keyword export] [keyword *] [keyword from] [string 'somewhere'];", "}"); @@ -38,7 +38,7 @@ MT("const", "[keyword function] [variable f]() {", - " [keyword const] [[ [def a], [def b] ]] = [[ [number 1], [number 2] ]];", + " [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];", "}"); MT("for/of", @@ -46,14 +46,14 @@ MT("generator", "[keyword function*] [variable repeat]([def n]) {", - " [keyword for]([keyword var] [def i] = [number 0]; [variable-2 i] < [variable-2 n]; ++[variable-2 i])", + " [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])", " [keyword yield] [variable-2 i];", "}"); MT("fatArrow", - "[variable array].[property filter]([def a] => [variable-2 a] + [number 1]);", + "[variable array].[property filter]([def a] [operator =>] [variable-2 a] [operator +] [number 1]);", "[variable a];", // No longer in scope - "[keyword let] [variable f] = ([[ [def a], [def b] ]], [def c]) => [variable-2 a] + [variable-2 c];", + "[keyword let] [variable f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];", "[variable c];"); MT("spread", @@ -63,16 +63,16 @@ MT("comprehension", "[keyword function] [variable f]() {", - " [[([variable x] + [number 1]) [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", - " ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] === [string 'blue']));", + " [[([variable x] [operator +] [number 1]) [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", + " ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] [operator ===] [string 'blue']));", "}"); MT("quasi", - "[variable re][string-2 `fofdlakj${][variable x] + ([variable re][string-2 `foo`]) + [number 1][string-2 }fdsa`] + [number 2]"); + "[variable re][string-2 `fofdlakj${][variable x] [operator +] ([variable re][string-2 `foo`]) [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); MT("indent_statement", - "[keyword var] [variable x] = [number 10]", - "[variable x] += [variable y] +", + "[keyword var] [variable x] [operator =] [number 10]", + "[variable x] [operator +=] [variable y] [operator +]", " [atom Infinity]", "[keyword debugger];"); @@ -92,9 +92,9 @@ "}"); MT("indent_for", - "[keyword for] ([keyword var] [variable i] = [number 0];", - " [variable i] < [number 100];", - " [variable i]++)", + "[keyword for] ([keyword var] [variable i] [operator =] [number 0];", + " [variable i] [operator <] [number 100];", + " [variable i][operator ++])", " [variable doSomething]([variable i]);", "[keyword debugger];"); From 8a9142ac6dfd34e6cfc16dc6075dd4f1672ecc8a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 14 Jan 2014 14:14:12 +0100 Subject: [PATCH 134/155] [markdown mode] Fix bug in innerMode Closes #2139 --- mode/markdown/markdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 2a5e71fdd3..da2ba93fea 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -732,7 +732,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { innerMode: function(state) { if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode}; - if (state.block == local) return {state: state.localState, mode: state.localMode}; + if (state.localState) return {state: state.localState, mode: state.localMode}; return {state: state, mode: mode}; }, From b8beead7d21e761263fd89d8c2adfc3b3a5ba2a6 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Wed, 15 Jan 2014 23:28:40 -0500 Subject: [PATCH 135/155] [vim] Expose context (or mode) to the Vim API and add :nmap + :vmap. Also fixed a bug in context matching. --- keymap/vim.js | 55 +++++++++++++++++++++++++++++++++++-------------------- test/vim_test.js | 20 ++++++++++++++++++++ 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 78b83e5116..75b4e454d0 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -572,9 +572,9 @@ maybeInitVimState_: maybeInitVimState, InsertModeKey: InsertModeKey, - map: function(lhs, rhs) { + map: function(lhs, rhs, ctx) { // Add user defined key bindings. - exCommandDispatcher.map(lhs, rhs); + exCommandDispatcher.map(lhs, rhs, ctx); }, defineEx: function(name, prefix, func){ if (name.indexOf(prefix) !== 0) { @@ -856,11 +856,17 @@ } else { // Find the best match in the list of matchedCommands. var context = vim.visualMode ? 'visual' : 'normal'; - var bestMatch = matchedCommands[0]; // Default to first in the list. + var bestMatch; // Default to first in the list. for (var i = 0; i < matchedCommands.length; i++) { - if (matchedCommands[i].context == context) { - bestMatch = matchedCommands[i]; + var current = matchedCommands[i]; + if (current.context == context) { + bestMatch = current; break; + } else if (!bestMatch && !current.context) { + // Only set an imperfect match to best match if no best match is + // set and the imperfect match is not restricted to another + // context. + bestMatch = current; } } return getFullyMatchedCommandOrNull(bestMatch); @@ -2957,14 +2963,16 @@ // pair of commands that have a shared prefix, at least one of their // shortNames must not match the prefix of the other command. var defaultExCommandMap = [ - { name: 'map', type: 'builtIn' }, - { name: 'write', shortName: 'w', type: 'builtIn' }, - { name: 'undo', shortName: 'u', type: 'builtIn' }, - { name: 'redo', shortName: 'red', type: 'builtIn' }, - { name: 'sort', shortName: 'sor', type: 'builtIn'}, - { name: 'substitute', shortName: 's', type: 'builtIn'}, - { name: 'nohlsearch', shortName: 'noh', type: 'builtIn'}, - { name: 'delmarks', shortName: 'delm', type: 'builtin'} + { name: 'map' }, + { name: 'nmap', shortName: 'nm' }, + { name: 'vmap', shortName: 'vm' }, + { name: 'write', shortName: 'w' }, + { name: 'undo', shortName: 'u' }, + { name: 'redo', shortName: 'red' }, + { name: 'sort', shortName: 'sor' }, + { name: 'substitute', shortName: 's' }, + { name: 'nohlsearch', shortName: 'noh' }, + { name: 'delmarks', shortName: 'delm' } ]; Vim.ExCommandDispatcher = function() { this.buildCommandMap_(); @@ -3099,8 +3107,9 @@ this.commandMap_[key] = command; } }, - map: function(lhs, rhs) { + map: function(lhs, rhs, ctx) { if (lhs != ':' && lhs.charAt(0) == ':') { + if (ctx) { throw Error('Mode not supported for ex mappings'); } var commandName = lhs.substring(1); if (rhs != ':' && rhs.charAt(0) == ':') { // Ex to Ex mapping @@ -3120,17 +3129,21 @@ } else { if (rhs != ':' && rhs.charAt(0) == ':') { // Key to Ex mapping. - defaultKeymap.unshift({ + var mapping = { keys: parseKeyString(lhs), type: 'keyToEx', - exArgs: { input: rhs.substring(1) }}); + exArgs: { input: rhs.substring(1) }}; + if (ctx) { mapping.context = ctx; } + defaultKeymap.unshift(mapping); } else { // Key to key mapping - defaultKeymap.unshift({ + var mapping = { keys: parseKeyString(lhs), type: 'keyToKey', toKeys: parseKeyString(rhs) - }); + }; + if (ctx) { mapping.context = ctx; } + defaultKeymap.unshift(mapping); } } } @@ -3152,7 +3165,7 @@ } var exCommands = { - map: function(cm, params) { + map: function(cm, params, ctx) { var mapArgs = params.args; if (!mapArgs || mapArgs.length < 2) { if (cm) { @@ -3160,8 +3173,10 @@ } return; } - exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm); + exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx); }, + nmap: function(cm, params) { this.map(cm, params, 'normal'); }, + vmap: function(cm, params) { this.map(cm, params, 'visual'); }, move: function(cm, params) { commandDispatcher.processCommand(cm, cm.state.vim, { type: 'motion', diff --git a/test/vim_test.js b/test/vim_test.js index 28d492178c..7184cb25ab 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -2401,6 +2401,26 @@ testVim('ex_map_key2ex', function(cm, vim, helpers) { eq(written, true); eq(actualCm, cm); }); +testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) { + CodeMirror.Vim.map('b', ':w', 'visual'); + var tmp = CodeMirror.commands.save; + var written = false; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + // Mapping should not work in normal mode. + helpers.doKeys('b'); + eq(written, false); + // Mapping should work in visual mode. + helpers.doKeys('v', 'b'); + eq(written, true); + eq(actualCm, cm); + + CodeMirror.commands.save = tmp; +}); + // Testing registration of functions as ex-commands and mapping to -keys testVim('ex_api_test', function(cm, vim, helpers) { var res=false; From 54e25895f8b3d3b9253b5649080d5c5c18dfb8be Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 12:48:09 +0100 Subject: [PATCH 136/155] [css mode] Properly parse @font-face declarations Closes #2131 --- mode/css/css.js | 31 ++++++++++++++++++++++++++++++- mode/css/test.js | 8 ++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/mode/css/css.js b/mode/css/css.js index 2af14db55d..369bb385ea 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -10,6 +10,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { propertyKeywords = parserConfig.propertyKeywords || {}, colorKeywords = parserConfig.colorKeywords || {}, valueKeywords = parserConfig.valueKeywords || {}, + fontProperties = parserConfig.fontProperties || {}, allowNested = parserConfig.allowNested; var type, override; @@ -137,6 +138,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return popContext(state); } else if (type == "@media") { return pushContext(state, stream, "media"); + } else if (type == "@font-face") { + return "font_face_before"; } else if (type && type.charAt(0) == "@") { return pushContext(state, stream, "at"); } else if (type == "hash") { @@ -243,6 +246,24 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return states.media(type, stream, state); }; + states.font_face_before = function(type, stream, state) { + if (type == "{") + return pushContext(state, stream, "font_face"); + return pass(type, stream, state); + }; + + states.font_face = function(type, stream, state) { + if (type == "}") return popContext(state); + if (type == "word") { + if (!fontProperties.hasOwnProperty(stream.current().toLowerCase())) + override = "error"; + else + override = "property"; + return "maybeprop"; + } + return "font_face"; + }; + states.at = function(type, stream, state) { if (type == ";") return popContext(state); if (type == "{" || type == "}") return popAndPass(type, stream, state); @@ -288,7 +309,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { var cx = state.context, ch = textAfter && textAfter.charAt(0); var indent = cx.indent; if (cx.prev && - (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation") || + (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "font_face") || ch == ")" && (cx.type == "parens" || cx.type == "params" || cx.type == "media_parens") || ch == "{" && (cx.type == "at" || cx.type == "media"))) { indent = cx.indent - indentUnit; @@ -533,6 +554,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "xx-large", "xx-small" ], valueKeywords = keySet(valueKeywords_); + var fontProperties_ = [ + "font-family", "src", "unicode-range", "font-variant", "font-feature-settings", + "font-stretch", "font-weight", "font-style" + ], fontProperties = keySet(fontProperties_); + var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_).concat(colorKeywords_).concat(valueKeywords_); CodeMirror.registerHelper("hintWords", "css", allWords); @@ -564,6 +590,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { propertyKeywords: propertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, + fontProperties: fontProperties, tokenHooks: { "<": function(stream, state) { if (!stream.match("!--")) return false; @@ -585,6 +612,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { propertyKeywords: propertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, + fontProperties: fontProperties, allowNested: true, tokenHooks: { "/": function(stream, state) { @@ -624,6 +652,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { propertyKeywords: propertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, + fontProperties: fontProperties, allowNested: true, tokenHooks: { "/": function(stream, state) { diff --git a/mode/css/test.js b/mode/css/test.js index 425c26ed91..b3f47767e6 100644 --- a/mode/css/test.js +++ b/mode/css/test.js @@ -108,4 +108,12 @@ "[string etc]", "[string ]) [keyword !important];", "}"); + + MT("font_face", + "[def @font-face] {", + " [property font-family]: [string 'myfont'];", + " [error nonsense]: [string 'abc'];", + " [property src]: [atom url]([string http://blah]),", + " [atom url]([string http://foo]);", + "}"); })(); From 086e985bd63a55561498678ab5d3233b457de8e8 Mon Sep 17 00:00:00 2001 From: Miraculix87 Date: Fri, 10 Jan 2014 20:52:52 +0100 Subject: [PATCH 137/155] Update midnight.css Added missing semicolon --- theme/midnight.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/midnight.css b/theme/midnight.css index 66126322bd..468d87daf9 100644 --- a/theme/midnight.css +++ b/theme/midnight.css @@ -1,7 +1,7 @@ /* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ /**/ -.cm-s-midnight span.CodeMirror-matchhighlight { background: #494949 } +.cm-s-midnight span.CodeMirror-matchhighlight { background: #494949; } .cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67 !important; } /**/ From 31b884c12701c04c27093c0875c7450c497ab3bb Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 12:57:57 +0100 Subject: [PATCH 138/155] [hardwrap addon] Return wrapped range, rather than a boolean --- addon/wrap/hardwrap.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addon/wrap/hardwrap.js b/addon/wrap/hardwrap.js index 3023ca5b8e..b56ebe2c39 100644 --- a/addon/wrap/hardwrap.js +++ b/addon/wrap/hardwrap.js @@ -36,7 +36,7 @@ var killTrailing = options.killTrailingSpace !== false; var changes = [], curLine = "", curNo = from.line; var lines = cm.getRange(from, to, false); - if (!lines.length) return false; + if (!lines.length) return null; var leadingSpace = lines[0].match(/^[ \t]*/)[0]; for (var i = 0; i < lines.length; ++i) { @@ -79,7 +79,7 @@ cm.replaceRange(change.text, change.from, change.to); } }); - return changes.length > 0; + return {from: from, to: cm.clipPos(Pos(curNo))}; } CodeMirror.defineExtension("wrapParagraph", function(pos, options) { From 1cb34abb860a4f534fb61d150b7f489183c10a5a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 13:03:25 +0100 Subject: [PATCH 139/155] [hardwrap addon] Return more precise range info --- addon/wrap/hardwrap.js | 6 +++--- demo/hardwrap.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addon/wrap/hardwrap.js b/addon/wrap/hardwrap.js index b56ebe2c39..f6d99212a8 100644 --- a/addon/wrap/hardwrap.js +++ b/addon/wrap/hardwrap.js @@ -56,7 +56,7 @@ findBreakPoint(curLine, column, wrapOn, killTrailing); // If this isn't broken, or is broken at a different point, remove old break if (!firstBreak || firstBreak.from != oldLen || firstBreak.to != oldLen + spaceInserted) { - changes.push({text: spaceInserted ? " " : "", + changes.push({text: [spaceInserted ? " " : ""], from: Pos(curNo, oldLen), to: Pos(curNo + 1, spaceTrimmed.length)}); } else { @@ -66,7 +66,7 @@ } while (curLine.length > column) { var bp = findBreakPoint(curLine, column, wrapOn, killTrailing); - changes.push({text: "\n" + leadingSpace, + changes.push({text: ["", leadingSpace], from: Pos(curNo, bp.from), to: Pos(curNo, bp.to)}); curLine = leadingSpace + curLine.slice(bp.to); @@ -79,7 +79,7 @@ cm.replaceRange(change.text, change.from, change.to); } }); - return {from: from, to: cm.clipPos(Pos(curNo))}; + return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null; } CodeMirror.defineExtension("wrapParagraph", function(pos, options) { diff --git a/demo/hardwrap.html b/demo/hardwrap.html index d3f085ad43..598bf957df 100644 --- a/demo/hardwrap.html +++ b/demo/hardwrap.html @@ -64,7 +64,7 @@ editor.on("change", function(cm, change) { clearTimeout(wait); wait = setTimeout(function() { - cm.wrapParagraphsInRange(change.from, CodeMirror.changeEnd(change), options); + console.log(cm.wrapParagraphsInRange(change.from, CodeMirror.changeEnd(change), options)); }, 200); }); From 32ae45d862d71efb9dfde934b29cf8fe99fe3d04 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 13:05:55 +0100 Subject: [PATCH 140/155] [less mode] Don't treat @font-face as a variable Issue #2131 --- mode/css/css.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/css/css.js b/mode/css/css.js index 369bb385ea..c28d3e0a3b 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -667,7 +667,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } }, "@": function(stream) { - if (stream.match(/^(media|import)\b/, false)) return false; + if (stream.match(/^(font-face|media|import)\b/, false)) return false; stream.eatWhile(/[\w\\\-]/); if (stream.match(/^\s*:/, false)) return ["variable-2", "variable-definition"]; From c3c4d4801ca61e1db351f97770a3a9a22f81d277 Mon Sep 17 00:00:00 2001 From: mats cronqvist Date: Sat, 11 Jan 2014 12:19:24 +0100 Subject: [PATCH 141/155] [erlang] - better indenter --- mode/erlang/erlang.js | 481 +++++++++++++++++++++++++++++++------------------ mode/erlang/index.html | 11 +- 2 files changed, 308 insertions(+), 184 deletions(-) diff --git a/mode/erlang/erlang.js b/mode/erlang/erlang.js index af8953c33b..bc3bdce860 100644 --- a/mode/erlang/erlang.js +++ b/mode/erlang/erlang.js @@ -1,45 +1,24 @@ -// block; "begin", "case", "fun", "if", "receive", "try": closed by "end" -// block internal; "after", "catch", "of" -// guard; "when", closed by "->" -// "->" opens a clause, closed by ";" or "." -// "<<" opens a binary, closed by ">>" -// "," appears in arglists, lists, tuples and terminates lines of code -// "." resets indentation to 0 -// obsolete; "cond", "let", "query" +/*jshint unused:true, eqnull:true, curly:true, bitwise:true */ +/*jshint undef:true, latedef:true, trailing:true */ +/*global CodeMirror:true */ + +// erlang mode. +// tokenizer -> token types -> CodeMirror styles +// tokenizer maintains a parse stack +// indenter uses the parse stack + +// TODO indenter: +// bit syntax +// old guard/bif/conversion clashes (e.g. "float/1") +// type/spec/opaque CodeMirror.defineMIME("text/x-erlang", "erlang"); CodeMirror.defineMode("erlang", function(cmCfg) { + "use strict"; - function rval(state,_stream,type) { - // distinguish between "." as terminator and record field operator - state.in_record = (type == "record"); - - // erlang -> CodeMirror tag - switch (type) { - case "atom": return "atom"; - case "attribute": return "attribute"; - case "boolean": return "special"; - case "builtin": return "builtin"; - case "comment": return "comment"; - case "fun": return "meta"; - case "function": return "tag"; - case "guard": return "property"; - case "keyword": return "keyword"; - case "macro": return "variable-2"; - case "number": return "number"; - case "operator": return "operator"; - case "record": return "bracket"; - case "string": return "string"; - case "type": return "def"; - case "variable": return "variable"; - case "error": return "error"; - case "separator": return null; - case "open_paren": return null; - case "close_paren": return null; - default: return null; - } - } +///////////////////////////////////////////////////////////////////////////// +// constants var typeWords = [ "-type", "-spec", "-export_type", "-opaque"]; @@ -48,23 +27,23 @@ CodeMirror.defineMode("erlang", function(cmCfg) { "after","begin","catch","case","cond","end","fun","if", "let","of","query","receive","try","when"]; - var separatorRE = /[\->\.,:;]/; + var separatorRE = /[\->,;]/; var separatorWords = [ - "->",";",":",".",","]; + "->",";",","]; - var operatorWords = [ + var operatorAtomWords = [ "and","andalso","band","bnot","bor","bsl","bsr","bxor", "div","not","or","orelse","rem","xor"]; - var symbolRE = /[\+\-\*\/<>=\|:!]/; - var symbolWords = [ - "+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-","!"]; + var operatorSymbolRE = /[\+\-\*\/<>=\|:!]/; + var operatorSymbolWords = [ + "=","+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-","!"]; - var openParenRE = /[<\(\[\{]/; + var openParenRE = /[<\(\[\{]/; var openParenWords = [ "<<","(","[","{"]; - var closeParenRE = /[>\)\]\}]/; + var closeParenRE = /[>\)\]\}]/; var closeParenWords = [ "}","]",")",">>"]; @@ -99,23 +78,25 @@ CodeMirror.defineMode("erlang", function(cmCfg) { "term_to_binary","time","throw","tl","trunc","tuple_size", "tuple_to_list","unlink","unregister","whereis"]; -// [Ø-Þ] [À-Ö] -// [ß-ö] [ø-ÿ] +// upper case: [A-Z] [Ø-Þ] [À-Ö] +// lower case: [a-z] [ß-ö] [ø-ÿ] var anumRE = /[\w@Ø-ÞÀ-Öß-öø-ÿ]/; var escapesRE = /[0-7]{1,3}|[bdefnrstv\\"']|\^[a-zA-Z]|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}/; - function tokenize(stream, state) { +///////////////////////////////////////////////////////////////////////////// +// tokenizer + function tokenizer(stream,state) { // in multi-line string if (state.in_string) { - state.in_string = (!doubleQuote(stream)); + state.in_string = (!doubleQuote(stream)); return rval(state,stream,"string"); } // in multi-line atom if (state.in_atom) { - state.in_atom = (!singleQuote(stream)); + state.in_atom = (!singleQuote(stream)); return rval(state,stream,"atom"); } @@ -125,9 +106,9 @@ CodeMirror.defineMode("erlang", function(cmCfg) { } // attributes and type specs - if ((peekToken(state).token == "") && + if (!peekToken(state) && stream.match(/-\s*[a-zß-öø-ÿ][\wØ-ÞÀ-Öß-öø-ÿ]*/)) { - if (isMember(stream.current(),typeWords)) { + if (is_member(stream.current(),typeWords)) { return rval(state,stream,"type"); }else{ return rval(state,stream,"attribute"); @@ -142,32 +123,43 @@ CodeMirror.defineMode("erlang", function(cmCfg) { return rval(state,stream,"comment"); } + // colon + if (ch == ":") { + return rval(state,stream,"colon"); + } + // macro if (ch == '?') { + stream.eatSpace(); stream.eatWhile(anumRE); return rval(state,stream,"macro"); } // record if (ch == "#") { + stream.eatSpace(); stream.eatWhile(anumRE); return rval(state,stream,"record"); } // dollar escape - if ( ch == "$" ) { + if (ch == "$") { if (stream.next() == "\\" && !stream.match(escapesRE)) { return rval(state,stream,"error"); } return rval(state,stream,"number"); } + // dot + if (ch == ".") { + return rval(state,stream,"dot"); + } + // quoted atom if (ch == '\'') { if (!(state.in_atom = (!singleQuote(stream)))) { if (stream.match(/\s*\/\s*[0-9]/,false)) { stream.match(/\s*\/\s*[0-9]/,true); - popToken(state); return rval(state,stream,"fun"); // 'f'/0 style fun } if (stream.match(/\s*\(/,false) || stream.match(/\s*:/,false)) { @@ -195,34 +187,37 @@ CodeMirror.defineMode("erlang", function(cmCfg) { if (stream.match(/\s*\/\s*[0-9]/,false)) { stream.match(/\s*\/\s*[0-9]/,true); - popToken(state); return rval(state,stream,"fun"); // f/0 style fun } var w = stream.current(); - if (isMember(w,keywordWords)) { - pushToken(state,stream); + if (is_member(w,keywordWords)) { return rval(state,stream,"keyword"); + }else if (is_member(w,operatorAtomWords)) { + return rval(state,stream,"operator"); }else if (stream.match(/\s*\(/,false)) { // 'put' and 'erlang:put' are bifs, 'foo:put' is not - if (isMember(w,bifWords) && - (!isPrev(stream,":") || isPrev(stream,"erlang:"))) { + if (is_member(w,bifWords) && + ((peekToken(state).token != ":") || + (peekToken(state,2).token == "erlang"))) { return rval(state,stream,"builtin"); - }else if (isMember(w,guardWords)) { + }else if (is_member(w,guardWords)) { return rval(state,stream,"guard"); }else{ return rval(state,stream,"function"); } - }else if (isMember(w,operatorWords)) { + }else if (is_member(w,operatorAtomWords)) { return rval(state,stream,"operator"); - }else if (stream.match(/\s*:/,false)) { + }else if (lookahead(stream) == ":") { if (w == "erlang") { return rval(state,stream,"builtin"); } else { return rval(state,stream,"function"); } - }else if (isMember(w,["true","false"])) { + }else if (is_member(w,["true","false"])) { + return rval(state,stream,"boolean"); + }else if (is_member(w,["true","false"])) { return rval(state,stream,"boolean"); }else{ return rval(state,stream,"atom"); @@ -234,15 +229,25 @@ CodeMirror.defineMode("erlang", function(cmCfg) { var radixRE = /[0-9a-zA-Z]/; // 36#zZ style int if (digitRE.test(ch)) { stream.eatWhile(digitRE); - if (stream.eat('#')) { - stream.eatWhile(radixRE); // 36#aZ style integer - } else { - if (stream.eat('.')) { // float - stream.eatWhile(digitRE); + if (stream.eat('#')) { // 36#aZ style integer + if (!stream.eatWhile(radixRE)) { + stream.backUp(1); //"36#" - syntax error } - if (stream.eat(/[eE]/)) { - stream.eat(/[-+]/); // float with exponent - stream.eatWhile(digitRE); + } else if (stream.eat('.')) { // float + if (!stream.eatWhile(digitRE)) { + stream.backUp(1); // "3." - probably end of function + } else { + if (stream.eat(/[eE]/)) { // float with exponent + if (stream.eat(/[-+]/)) { + if (!stream.eatWhile(digitRE)) { + stream.backUp(2); // "2e-" - syntax error + } + } else { + if (!stream.eatWhile(digitRE)) { + stream.backUp(1); // "2e" - syntax error + } + } + } } } return rval(state,stream,"number"); // normal integer @@ -250,50 +255,35 @@ CodeMirror.defineMode("erlang", function(cmCfg) { // open parens if (nongreedy(stream,openParenRE,openParenWords)) { - pushToken(state,stream); return rval(state,stream,"open_paren"); } // close parens if (nongreedy(stream,closeParenRE,closeParenWords)) { - pushToken(state,stream); return rval(state,stream,"close_paren"); } // separators if (greedy(stream,separatorRE,separatorWords)) { - // distinguish between "." as terminator and record field operator - if (!state.in_record) { - pushToken(state,stream); - } return rval(state,stream,"separator"); } // operators - if (greedy(stream,symbolRE,symbolWords)) { + if (greedy(stream,operatorSymbolRE,operatorSymbolWords)) { return rval(state,stream,"operator"); } return rval(state,stream,null); } - function isPrev(stream,string) { - var start = stream.start; - var len = string.length; - if (len <= start) { - var word = stream.string.slice(start-len,start); - return word == string; - }else{ - return false; - } - } - +///////////////////////////////////////////////////////////////////////////// +// utilities function nongreedy(stream,re,words) { if (stream.current().length == 1 && re.test(stream.current())) { stream.backUp(1); while (re.test(stream.peek())) { stream.next(); - if (isMember(stream.current(),words)) { + if (is_member(stream.current(),words)) { return true; } } @@ -308,7 +298,7 @@ CodeMirror.defineMode("erlang", function(cmCfg) { stream.next(); } while (0 < stream.current().length) { - if (isMember(stream.current(),words)) { + if (is_member(stream.current(),words)) { return true; }else{ stream.backUp(1); @@ -339,144 +329,277 @@ CodeMirror.defineMode("erlang", function(cmCfg) { return false; } - function isMember(element,list) { + function lookahead(stream) { + var m = stream.match(/([\n\s]+|%[^\n]*\n)*(.)/,false); + return m ? m.pop() : ""; + } + + function is_member(element,list) { return (-1 < list.indexOf(element)); } -///////////////////////////////////////////////////////////////////////////// - function myIndent(state,textAfter) { - var indent = cmCfg.indentUnit; - var token = (peekToken(state)).token; - var wordAfter = takewhile(textAfter,/[^a-z]/); + function rval(state,stream,type) { - if (state.in_string || state.in_atom) { - return CodeMirror.Pass; - }else if (token == "") { - return 0; - }else if (isMember(token,openParenWords)) { - return (peekToken(state)).column+token.length; - }else if (token == "when") { - return (peekToken(state)).column+token.length+1; - }else if (token == "fun" && wordAfter == "") { - return (peekToken(state)).column+token.length; - }else if (token == "->") { - if (isMember(wordAfter,["end","after","catch"])) { - return peekToken(state,2).column; - }else if (peekToken(state,2).token == "fun") { - return peekToken(state,2).column+indent; - }else if (peekToken(state,2).token == "") { - return indent; - }else{ - return (peekToken(state)).indent+indent; - } - }else if (isMember(wordAfter,["after","catch","of"])) { - return (peekToken(state)).indent; - }else{ - return (peekToken(state)).column+indent; + // parse stack + pushToken(state,realToken(type,stream)); + + // map erlang token type to CodeMirror style class + // erlang -> CodeMirror tag + switch (type) { + case "atom": return "atom"; + case "attribute": return "attribute"; + case "boolean": return "special"; + case "builtin": return "builtin"; + case "close_paren": return null; + case "colon": return null; + case "comment": return "comment"; + case "dot": return null; + case "error": return "error"; + case "fun": return "meta"; + case "function": return "tag"; + case "guard": return "property"; + case "keyword": return "keyword"; + case "macro": return "variable-2"; + case "number": return "number"; + case "open_paren": return null; + case "operator": return "operator"; + case "record": return "bracket"; + case "separator": return null; + case "string": return "string"; + case "type": return "def"; + case "variable": return "variable"; + default: return null; } } - function takewhile(str,re) { - var m = str.match(re); - return m ? str.slice(0,m.index) : str; + function aToken(tok,col,ind,typ) { + return {token: tok, + column: col, + indent: ind, + type: typ}; } - function Token(stream) { - this.token = stream ? stream.current() : ""; - this.column = stream ? stream.column() : 0; - this.indent = stream ? stream.indentation() : 0; + function realToken(type,stream) { + return aToken(stream.current(), + stream.column(), + stream.indentation(), + type); } - function popToken(state) { - return state.tokenStack.pop(); + function fakeToken(type) { + return aToken(type,0,0,type); } function peekToken(state,depth) { var len = state.tokenStack.length; var dep = (depth ? depth : 1); + if (len < dep) { - return new Token; + return false; }else{ return state.tokenStack[len-dep]; } } - function pushToken(state,stream) { - var token = stream.current(); - var prev_token = peekToken(state).token; + function pushToken(state,token) { - if (token == ".") { - state.tokenStack = []; - return false; - }else if(isMember(token,[",", ":", "of", "cond", "let", "query"])) { - return false; - }else if (drop_last(prev_token,token)) { - return false; - }else if (drop_both(prev_token,token)) { - popToken(state); - return false; - }else if (drop_first(prev_token,token)) { - popToken(state); - return pushToken(state,stream); - }else if (isMember(token,["after","catch"])) { - return false; + if (!(token.type == "comment" || token.type == "whitespace")) { + state.tokenStack = maybe_drop_pre(state.tokenStack,token); + state.tokenStack = maybe_drop_post(state.tokenStack); + } + } + + function maybe_drop_pre(s,token) { + var last = s.length-1; + + if (0 < last && s[last].type === "record" && token.type === "dot") { + s.pop(); + }else if (0 < last && s[last].type === "group") { + s.pop(); + s.push(token); }else{ - state.tokenStack.push(new Token(stream)); - return true; + s.push(token); } + return s; } - function drop_last(open, close) { - switch(open+" "+close) { - case "when ;": return true; - default: return false; + function maybe_drop_post(s) { + var last = s.length-1; + + if (s[last].type === "dot") { + return []; + } + if (s[last].type === "fun" && s[last-1].token === "fun") { + return s.slice(0,last-1); + } + switch (s[s.length-1].token) { + case "}": return d(s,{g:["{"]}); + case "]": return d(s,{i:["["]}); + case ")": return d(s,{i:["("]}); + case ">>": return d(s,{i:["<<"]}); + case "end": return d(s,{i:["begin","case","fun","if","receive","try"]}); + case ",": return d(s,{e:["begin","try","when","->", + ",","(","[","{","<<"]}); + case "->": return d(s,{r:["when"], + m:["try","if","case","receive"]}); + case ";": return d(s,{E:["case","fun","if","receive","try","when"]}); + case "catch":return d(s,{e:["try"]}); + case "of": return d(s,{e:["case"]}); + case "after":return d(s,{e:["receive","try"]}); + default: return s; } } - function drop_first(open, close) { - switch (open+" "+close) { - case "when ->": return true; - case "-> end": return true; - default: return false; + function d(stack,tt) { + // stack is a stack of Token objects. + // tt is an object; {type:tokens} + // type is a char, tokens is a list of token strings. + // The function returns (possibly truncated) stack. + // It will descend the stack, looking for a Token such that Token.token + // is a member of tokens. If it does not find that, it will normally (but + // see "E" below) return stack. If it does find a match, it will remove + // all the Tokens between the top and the matched Token. + // If type is "m", that is all it does. + // If type is "i", it will also remove the matched Token and the top Token. + // If type is "g", like "i", but add a fake "group" token at the top. + // If type is "r", it will remove the matched Token, but not the top Token. + // If type is "e", it will keep the matched Token but not the top Token. + // If type is "E", it behaves as for type "e", except if there is no match, + // in which case it will return an empty stack. + + for (var type in tt) { + var len = stack.length-1; + var tokens = tt[type]; + for (var i = len-1; -1 < i ; i--) { + if (is_member(stack[i].token,tokens)) { + var ss = stack.slice(0,i); + switch (type) { + case "m": return ss.concat(stack[i]).concat(stack[len]); + case "r": return ss.concat(stack[len]); + case "i": return ss; + case "g": return ss.concat(fakeToken("group")); + case "E": return ss.concat(stack[i]); + case "e": return ss.concat(stack[i]); + } + } + } } + return (type == "E" ? [] : stack); } - function drop_both(open, close) { - switch (open+" "+close) { - case "( )": return true; - case "[ ]": return true; - case "{ }": return true; - case "<< >>": return true; - case "begin end": return true; - case "case end": return true; - case "fun end": return true; - case "if end": return true; - case "receive end": return true; - case "try end": return true; - case "-> catch": return true; - case "-> after": return true; - case "-> ;": return true; - default: return false; +///////////////////////////////////////////////////////////////////////////// +// indenter + + function indenter(state,textAfter) { + var t; + var unit = cmCfg.indentUnit; + var wordAfter = wordafter(textAfter); + var currT = peekToken(state,1); + var prevT = peekToken(state,2); + + if (state.in_string || state.in_atom) { + return CodeMirror.Pass; + }else if (!prevT) { + return 0; + }else if (currT.token == "when") { + return currT.column+unit; + }else if (wordAfter === "when" && prevT.type === "function") { + return prevT.indent+unit; + }else if (wordAfter === "(" && currT.token === "fun") { + return currT.column+3; + }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) { + return t.column; + }else if (is_member(wordAfter,["end","after","of"])) { + t = getToken(state,["begin","case","fun","if","receive","try"]); + return t ? t.column : CodeMirror.Pass; + }else if (is_member(wordAfter,closeParenWords)) { + t = getToken(state,openParenWords); + return t ? t.column : CodeMirror.Pass; + }else if (is_member(currT.token,[",","|","||"]) || + is_member(wordAfter,[",","|","||"])) { + t = postcommaToken(state); + return t ? t.column+t.token.length : unit; + }else if (currT.token == "->") { + if (is_member(prevT.token, ["receive","case","if","try"])) { + return prevT.column+unit+unit; + }else{ + return prevT.column+unit; + } + }else if (is_member(currT.token,openParenWords)) { + return currT.column+currT.token.length; + }else{ + t = defaultToken(state); + return truthy(t) ? t.column+unit : 0; } } + function wordafter(str) { + var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/); + + return truthy(m) && (m.index === 0) ? m[0] : ""; + } + + function postcommaToken(state) { + var objs = state.tokenStack.slice(0,-1); + var i = getTokenIndex(objs,"type",["open_paren"]); + + return truthy(objs[i]) ? objs[i] : false; + } + + function defaultToken(state) { + var objs = state.tokenStack; + var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]); + var oper = getTokenIndex(objs,"type",["operator"]); + + if (truthy(stop) && truthy(oper) && stop < oper) { + return objs[stop+1]; + } else if (truthy(stop)) { + return objs[stop]; + } else { + return false; + } + } + + function getToken(state,tokens) { + var objs = state.tokenStack; + var i = getTokenIndex(objs,"token",tokens); + + return truthy(objs[i]) ? objs[i] : false; + } + + function getTokenIndex(objs,propname,propvals) { + + for (var i = objs.length-1; -1 < i ; i--) { + if (is_member(objs[i][propname],propvals)) { + return i; + } + } + return false; + } + + function truthy(x) { + return (x !== false) && (x != null); + } + +///////////////////////////////////////////////////////////////////////////// +// this object defines the mode + return { startState: function() { return {tokenStack: [], - in_record: false, in_string: false, in_atom: false}; }, token: function(stream, state) { - return tokenize(stream, state); + return tokenizer(stream, state); }, indent: function(state, textAfter) { - return myIndent(state,textAfter); + return indenter(state,textAfter); }, lineComment: "%" diff --git a/mode/erlang/index.html b/mode/erlang/index.html index e63e231201..9cacb759f6 100644 --- a/mode/erlang/index.html +++ b/mode/erlang/index.html @@ -51,11 +51,12 @@ case tuple_size(Tup) of L when L < 1 -> Tup; L -> - try Fields = M:rec_info(element(1,Tup)), - L = length(Fields)+1, - lists:zip(Fields,expand_recs(M,tl(tuple_to_list(Tup)))) - catch _:_ -> - list_to_tuple(expand_recs(M,tuple_to_list(Tup))) + try + Fields = M:rec_info(element(1,Tup)), + L = length(Fields)+1, + lists:zip(Fields,expand_recs(M,tl(tuple_to_list(Tup)))) + catch + _:_ -> list_to_tuple(expand_recs(M,tuple_to_list(Tup))) end end; expand_recs(_,Term) -> From cbce0ffe5778a663c7a52f6cb9c888a5cbee0a67 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 13:12:28 +0100 Subject: [PATCH 142/155] [xml mode] Return Pass when indenting cdata/pre content Issue #2135 --- mode/xml/xml.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mode/xml/xml.js b/mode/xml/xml.js index c75200ec7b..96b51ff970 100644 --- a/mode/xml/xml.js +++ b/mode/xml/xml.js @@ -298,8 +298,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { if (state.tokenize.isInAttribute) { return state.stringStartCol + 1; } - if ((state.tokenize != inTag && state.tokenize != inText) || - context && context.noIndent) + if (context && context.noIndent) return CodeMirror.Pass; + if (state.tokenize != inTag && state.tokenize != inText) return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; // Indent the starts of attribute names. if (state.tagName) { From 8f10e2fb124240c3dbab66bd9b5f0ddca7558b02 Mon Sep 17 00:00:00 2001 From: Peter Kroon Date: Thu, 16 Jan 2014 13:13:57 +0100 Subject: [PATCH 143/155] [css mode] More careful categorizing of @-names as variables https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule --- mode/css/css.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/css/css.js b/mode/css/css.js index c28d3e0a3b..8f6fe7df4d 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -667,7 +667,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } }, "@": function(stream) { - if (stream.match(/^(font-face|media|import)\b/, false)) return false; + if (stream.match(/^(charset|document|font-face|import|keyframes|media|namespace|page|supports)\b/, false)) return false; stream.eatWhile(/[\w\\\-]/); if (stream.match(/^\s*:/, false)) return ["variable-2", "variable-definition"]; From 4c07e0b7793f6e701f14a72d5d496cfd8740b961 Mon Sep 17 00:00:00 2001 From: Wesley Wiser Date: Sat, 11 Jan 2014 13:13:10 -0500 Subject: [PATCH 144/155] [mllike mode] Rename OCaml mode and extend with F# support Add support for the F# programming language. Highlighting is provided for all keywords, number literals, strings, ML-style comments and select built-ins. C-style comments not currently supported. --- mode/{ocaml => mllike}/index.html | 49 +++++++++++++--- mode/{ocaml/ocaml.js => mllike/mllike.js} | 93 +++++++++++++++++++++++++++---- 2 files changed, 122 insertions(+), 20 deletions(-) rename mode/{ocaml => mllike}/index.html (80%) rename mode/{ocaml/ocaml.js => mllike/mllike.js} (59%) diff --git a/mode/ocaml/index.html b/mode/mllike/index.html similarity index 80% rename from mode/ocaml/index.html rename to mode/mllike/index.html index 5d39fadd97..67bfef8581 100644 --- a/mode/ocaml/index.html +++ b/mode/mllike/index.html @@ -1,13 +1,13 @@ -CodeMirror: OCaml mode +CodeMirror: ML-like mode - + @@ -21,7 +21,7 @@ @@ -29,7 +29,7 @@

    OCaml mode

    - +

    F# mode

    + + + -

    MIME types defined: text/x-ocaml.

    +

    MIME types defined: text/x-ocaml (OCaml) and text/x-fsharp (F#).

    diff --git a/mode/ocaml/ocaml.js b/mode/mllike/mllike.js similarity index 59% rename from mode/ocaml/ocaml.js rename to mode/mllike/mllike.js index 32cbc0b7ba..ea75ffa214 100644 --- a/mode/ocaml/ocaml.js +++ b/mode/mllike/mllike.js @@ -1,14 +1,11 @@ -CodeMirror.defineMode('ocaml', function() { +CodeMirror.defineMode('mllike', function(_config, parserConfig) { var words = { - 'true': 'atom', - 'false': 'atom', 'let': 'keyword', 'rec': 'keyword', 'in': 'keyword', 'of': 'keyword', 'and': 'keyword', - 'succ': 'keyword', 'if': 'keyword', 'then': 'keyword', 'else': 'keyword', @@ -25,17 +22,19 @@ CodeMirror.defineMode('ocaml', function() { 'match': 'keyword', 'with': 'keyword', 'try': 'keyword', - 'raise': 'keyword', - 'begin': 'keyword', - 'end': 'keyword', 'open': 'builtin', - 'trace': 'builtin', 'ignore': 'builtin', - 'exit': 'builtin', - 'print_string': 'builtin', - 'print_endline': 'builtin' + 'begin': 'keyword', + 'end': 'keyword' }; + var extraWords = parserConfig.extraWords || {}; + for (var prop in extraWords) { + if (extraWords.hasOwnProperty(prop)) { + words[prop] = parserConfig.extraWords[prop]; + } + } + function tokenBase(stream, state) { var ch = stream.next(); @@ -113,4 +112,74 @@ CodeMirror.defineMode('ocaml', function() { }; }); -CodeMirror.defineMIME('text/x-ocaml', 'ocaml'); +CodeMirror.defineMIME('text/x-ocaml', { + name: 'mllike', + extraWords: { + 'succ': 'keyword', + 'trace': 'builtin', + 'exit': 'builtin', + 'print_string': 'builtin', + 'print_endline': 'builtin', + 'true': 'atom', + 'false': 'atom', + 'raise': 'keyword' + } +}); + +CodeMirror.defineMIME('text/x-fsharp', { + name: 'mllike', + extraWords: { + 'abstract': 'keyword', + 'as': 'keyword', + 'assert': 'keyword', + 'base': 'keyword', + 'class': 'keyword', + 'default': 'keyword', + 'delegate': 'keyword', + 'downcast': 'keyword', + 'downto': 'keyword', + 'elif': 'keyword', + 'exception': 'keyword', + 'extern': 'keyword', + 'finally': 'keyword', + 'global': 'keyword', + 'inherit': 'keyword', + 'inline': 'keyword', + 'interface': 'keyword', + 'internal': 'keyword', + 'lazy': 'keyword', + 'let!': 'keyword', + 'member' : 'keyword', + 'module': 'keyword', + 'namespace': 'keyword', + 'new': 'keyword', + 'null': 'keyword', + 'override': 'keyword', + 'private': 'keyword', + 'public': 'keyword', + 'return': 'keyword', + 'return!': 'keyword', + 'select': 'keyword', + 'static': 'keyword', + 'struct': 'keyword', + 'upcast': 'keyword', + 'use': 'keyword', + 'use!': 'keyword', + 'val': 'keyword', + 'when': 'keyword', + 'yield': 'keyword', + 'yield!': 'keyword', + + 'List': 'builtin', + 'Seq': 'builtin', + 'Map': 'builtin', + 'Set': 'builtin', + 'int': 'builtin', + 'string': 'builtin', + 'raise': 'builtin', + 'failwith': 'builtin', + 'not': 'builtin', + 'true': 'builtin', + 'false': 'builtin' + } +}); \ No newline at end of file From 53f3aba5bf1f7faf0fbef0ef5522970d5f4942e6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 13:35:27 +0100 Subject: [PATCH 145/155] [mllike mode] Integrate Add //-comment support for F# Issue #2136 --- doc/compress.html | 2 +- mode/index.html | 3 ++- mode/mllike/mllike.js | 12 +++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/compress.html b/doc/compress.html index 3f3bfb0465..205f7839ba 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -114,9 +114,9 @@ + - diff --git a/mode/index.html b/mode/index.html index e332f84f70..7c1f740615 100644 --- a/mode/index.html +++ b/mode/index.html @@ -46,6 +46,7 @@
  • Eiffel
  • Erlang
  • Fortran
  • +
  • F#
  • Gas (AT&T-style assembly)
  • Gherkin
  • Go
  • @@ -68,7 +69,7 @@
  • mIRC
  • Nginx
  • NTriples
  • -
  • OCaml
  • +
  • OCaml
  • Octave (MATLAB)
  • Pascal
  • PEG.js
  • diff --git a/mode/mllike/mllike.js b/mode/mllike/mllike.js index ea75ffa214..1d64789a19 100644 --- a/mode/mllike/mllike.js +++ b/mode/mllike/mllike.js @@ -57,6 +57,10 @@ CodeMirror.defineMode('mllike', function(_config, parserConfig) { stream.eatWhile(/\w/); return 'quote'; } + if (ch === '/' && parserConfig.slashComments && stream.eat('/')) { + stream.skipToEnd(); + return 'comment'; + } if (/\d/.test(ch)) { stream.eatWhile(/[\d]/); if (stream.eat('.')) { @@ -108,7 +112,8 @@ CodeMirror.defineMode('mllike', function(_config, parserConfig) { }, blockCommentStart: "(*", - blockCommentEnd: "*)" + blockCommentEnd: "*)", + lineComment: parserConfig.slashComments ? "//" : null }; }); @@ -181,5 +186,6 @@ CodeMirror.defineMIME('text/x-fsharp', { 'not': 'builtin', 'true': 'builtin', 'false': 'builtin' - } -}); \ No newline at end of file + }, + slashComments: true +}); From 40a973a45f8059770dae169599f90343425d7668 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 13:36:42 +0100 Subject: [PATCH 146/155] [mllike mode] Update mode/meta.js Issue #2136 --- mode/meta.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mode/meta.js b/mode/meta.js index 226cff12e5..f37cea55b5 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -18,6 +18,7 @@ CodeMirror.modeInfo = [ {name: 'Eiffel', mime: 'text/x-eiffel', mode: 'eiffel'}, {name: 'Erlang', mime: 'text/x-erlang', mode: 'erlang'}, {name: 'Fortran', mime: 'text/x-fortran', mode: 'fortran'}, + {name: 'F#', mime: 'text/x-fsharp', mode: 'mllike'}, {name: 'Gas', mime: 'text/x-gas', mode: 'gas'}, {name: 'Gherkin', mime: 'text/x-feature', mode: 'gherkin'}, {name: 'GitHub Flavored Markdown', mime: 'text/x-gfm', mode: 'gfm'}, @@ -45,7 +46,7 @@ CodeMirror.modeInfo = [ {name: 'mIRC', mime: 'text/mirc', mode: 'mirc'}, {name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx'}, {name: 'NTriples', mime: 'text/n-triples', mode: 'ntriples'}, - {name: 'OCaml', mime: 'text/x-ocaml', mode: 'ocaml'}, + {name: 'OCaml', mime: 'text/x-ocaml', mode: 'mllike'}, {name: 'Octave', mime: 'text/x-octave', mode: 'octave'}, {name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'}, {name: 'PEG.js', mime: null, mode: 'pegjs'}, From cc23d768069bff93f8ad2bdda3e162e06fe945e1 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Mon, 13 Jan 2014 18:16:12 +0400 Subject: [PATCH 147/155] [runmode addon] Define text/plain mode in standalone script. --- addon/runmode/runmode-standalone.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addon/runmode/runmode-standalone.js b/addon/runmode/runmode-standalone.js index 9ebde8b054..ec06ba11cb 100644 --- a/addon/runmode/runmode-standalone.js +++ b/addon/runmode/runmode-standalone.js @@ -91,6 +91,10 @@ CodeMirror.getMode = function (options, spec) { return mfactory(options, spec); }; CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min; +CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; +}); +CodeMirror.defineMIME("text/plain", "null"); CodeMirror.runMode = function (string, modespec, callback, options) { var mode = CodeMirror.getMode({ indentUnit: 2 }, modespec); From 33c40af12557ad7a58ff2ca35027733c27cdb516 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 13:50:07 +0100 Subject: [PATCH 148/155] Give change events for 'cut' changes origin="cut" Issue #2140 --- lib/codemirror.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index d2930ed270..4ca2ee3889 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -62,7 +62,8 @@ window.CodeMirror = (function() { overlays: [], modeGen: 0, overwrite: false, focused: false, - suppressEdits: false, pasteIncoming: false, + suppressEdits: false, + pasteIncoming: false, cutIncoming: false, draggingText: false, highlight: new Delayed()}; @@ -1512,11 +1513,11 @@ window.CodeMirror = (function() { var updateInput = cm.curOp.updateInput; var changeEvent = {from: from, to: to, text: splitLines(inserted), - origin: cm.state.pasteIncoming ? "paste" : "+input"}; + origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; makeChange(cm.doc, changeEvent, "end"); cm.curOp.updateInput = updateInput; signalLater(cm, "inputRead", cm, changeEvent); - if (!cm.state.pasteIncoming && cm.options.electricChars && + if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && cm.options.smartIndent && sel.head.ch < 100) { var electric = cm.getModeAt(sel.head).electricChars; if (electric) for (var i = 0; i < electric.length; i++) @@ -1529,7 +1530,7 @@ window.CodeMirror = (function() { if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; else cm.display.prevInput = text; if (withOp) endOperation(cm); - cm.state.pasteIncoming = false; + cm.state.pasteIncoming = cm.state.cutIncoming = false; return true; } @@ -1670,13 +1671,14 @@ window.CodeMirror = (function() { fastPoll(cm); }); - function prepareCopy() { + function prepareCopy(e) { if (d.inaccurateSelection) { d.prevInput = ""; d.inaccurateSelection = false; d.input.value = cm.getSelection(); selectInput(d.input); } + if (e.type == "cut") cm.state.cutIncoming = true; } on(d.input, "cut", prepareCopy); on(d.input, "copy", prepareCopy); From 5befcdce0b7ad0d8dcada5eb1bfc81a5d5b6342e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 14:02:05 +0100 Subject: [PATCH 149/155] [javascript mode] Don't terminate regexp on slash inside of square brackets Closes #2141 --- mode/javascript/javascript.js | 22 ++++++++++++++-------- mode/javascript/test.js | 7 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index 1985c6bbf5..fbf574b4fe 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -54,14 +54,16 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var isOperatorChar = /[+\-*&%=<>!?|~^]/; - function nextUntilUnescaped(stream, end) { - var escaped = false, next; + function readRegexp(stream) { + var escaped = false, next, inSet = false; while ((next = stream.next()) != null) { - if (next == end && !escaped) - return false; + if (!escaped) { + if (next == "/" && !inSet) return; + if (next == "[") inSet = true; + else if (inSet && next == "]") inSet = false; + } escaped = !escaped && next == "\\"; } - return escaped; } // Used as scratch variables to communicate multiple values without @@ -99,7 +101,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return ret("comment", "comment"); } else if (state.lastType == "operator" || state.lastType == "keyword c" || state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) { - nextUntilUnescaped(stream, "/"); + readRegexp(stream); stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla return ret("regexp", "string-2"); } else { @@ -125,8 +127,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function tokenString(quote) { return function(stream, state) { - if (!nextUntilUnescaped(stream, quote)) - state.tokenize = tokenBase; + var escaped = false, next; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) break; + escaped = !escaped && next == "\\"; + } + if (!escaped) state.tokenize = tokenBase; return ret("string", "string"); }; } diff --git a/mode/javascript/test.js b/mode/javascript/test.js index cbb936d963..911bbbfd7b 100644 --- a/mode/javascript/test.js +++ b/mode/javascript/test.js @@ -103,4 +103,11 @@ "{", " [keyword debugger];", "}"); + + MT("multilinestring", + "[keyword var] [variable x] [operator =] [string 'foo\\]", + "[string bar'];"); + + MT("scary_regexp", + "[string-2 /foo[[/]]bar/];"); })(); From ba21b55c61dd07e2c294edc75cc8a1d8aeec9504 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 14:07:56 +0100 Subject: [PATCH 150/155] [emacs keymap] Make ctrl-g clear mark Closes #2142 --- keymap/emacs.js | 15 ++++++++++++--- lib/codemirror.js | 5 ++++- test/emacs_test.js | 3 +++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/keymap/emacs.js b/keymap/emacs.js index f08e2e8d48..592238bc90 100644 --- a/keymap/emacs.js +++ b/keymap/emacs.js @@ -201,6 +201,11 @@ cm.on("change", function() { cm.setExtending(false); }); } + function clearMark(cm) { + cm.setExtending(false); + cm.setCursor(cm.getCursor()); + } + function getInput(cm, msg, f) { if (cm.openDialog) cm.openDialog(msg + ": ", f, {bottom: true}); @@ -234,6 +239,11 @@ } } + function quit(cm) { + cm.execCommand("clearSearch"); + clearMark(cm); + } + // Actual keymap var keyMap = CodeMirror.keyMap.emacs = { @@ -249,8 +259,7 @@ }), "Alt-W": function(cm) { addToRing(cm.getSelection()); - cm.setExtending(false); - cm.setCursor(cm.getCursor()); + clearMark(cm); }, "Ctrl-Y": function(cm) { var start = cm.getCursor(); @@ -336,7 +345,7 @@ "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"), "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"), "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", - "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace", + "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace", "Alt-/": "autocomplete", "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto", diff --git a/lib/codemirror.js b/lib/codemirror.js index 4ca2ee3889..c450c4e1d0 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3154,7 +3154,10 @@ window.CodeMirror = (function() { triggerOnKeyDown: operation(null, onKeyDown), - execCommand: function(cmd) {return commands[cmd](this);}, + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, findPosH: function(from, amount, unit, visually) { var dir = 1; diff --git a/test/emacs_test.js b/test/emacs_test.js index ab18241dee..f21605bb37 100644 --- a/test/emacs_test.js +++ b/test/emacs_test.js @@ -125,6 +125,9 @@ sim("transposeExpr", "do foo[bar] dah", Pos(0, 6), "Ctrl-Alt-T", txt("do [bar]foo dah")); + sim("clearMark", "abcde", Pos(0, 2), "Ctrl-Space", "Ctrl-F", "Ctrl-F", + "Ctrl-G", "Ctrl-W", txt("abcde")); + testCM("save", function(cm) { var saved = false; CodeMirror.commands.save = function(cm) { saved = cm.getValue(); }; From 306f6d8d01e948009f3f6190e58986c75b01d218 Mon Sep 17 00:00:00 2001 From: Shawn A Date: Wed, 15 Jan 2014 13:34:29 -0600 Subject: [PATCH 151/155] [compression helper] Add anyword-hint --- doc/compress.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/compress.html b/doc/compress.html index 205f7839ba..beac099370 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -159,6 +159,7 @@ + From bc32d2ac4288f51919cbb06e83ce1d4f5bb27e88 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 14:24:30 +0100 Subject: [PATCH 152/155] [ruby mode] Fix 'loop do' creating two indentation contexts Closes #2146 --- mode/ruby/ruby.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js index 96cdd5f9dd..1cdc9a4e43 100644 --- a/mode/ruby/ruby.js +++ b/mode/ruby/ruby.js @@ -12,7 +12,7 @@ CodeMirror.defineMode("ruby", function(config) { "caller", "lambda", "proc", "public", "protected", "private", "require", "load", "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__" ]); - var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then", + var indentWords = wordObj(["def", "class", "case", "for", "while", "module", "then", "catch", "loop", "proc", "begin"]); var dedentWords = wordObj(["end", "until"]); var matching = {"[": "]", "{": "}", "(": ")"}; @@ -214,6 +214,8 @@ CodeMirror.defineMode("ruby", function(config) { else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) kwtype = "indent"; + else if (word == "do" && state.context.indented < state.indented) + kwtype = "indent"; } if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style; if (curPunc == "|") state.varList = !state.varList; From 0e41639bebaa0535ab913563d0031d125ab241d4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 15:03:10 +0100 Subject: [PATCH 153/155] Kludge for another IE focus issue Issue #2127 --- lib/codemirror.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index c450c4e1d0..210806aace 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1770,6 +1770,9 @@ window.CodeMirror = (function() { e_preventDefault(e2); extendSelection(cm.doc, start); focusInput(cm); + // Work around unexplainable focus problem in IE9 (#2127) + if (old_ie && !ie_lt9) + setTimeout(function() {document.body.focus(); focusInput(cm);}, 20); } }); // Let the drag handler handle this. From 81c01d196e6b0148ce3412d8f686c3039013f7c3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 19:34:52 +0100 Subject: [PATCH 154/155] Add a script to help bring out releases Does all the version bumping and such. --- bin/release | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 bin/release diff --git a/bin/release b/bin/release new file mode 100755 index 0000000000..f92ab006d5 --- /dev/null +++ b/bin/release @@ -0,0 +1,41 @@ +#!/usr/bin/env node + +var fs = require("fs"), child = require("child_process"); + +var number, bumpOnly; + +for (var i = 2; i < process.argv.length; i++) { + if (process.argv[i] == "-bump") bumpOnly = true; + else if (/^\d+\.\d+\.\d+$/.test(process.argv[i])) number = process.argv[i]; + else { console.log("Bogus command line arg: " + process.argv[i]); process.exit(1); } +} + +if (!number) { console.log("Must give a version"); process.exit(1); } + +function rewrite(file, f) { + fs.writeFileSync(file, f(fs.readFileSync(file, "utf8")), "utf8"); +} + +rewrite("lib/codemirror.js", function(lib) { + return lib.replace(/CodeMirror\.version = "\d+\.\d+\.\d+"/, + "CodeMirror.version = \"" + number + "\""); +}); +rewrite("package.json", function(pack) { + return pack.replace(/"version":"\d+\.\d+\.\d+"/, "\"version\":\"" + number + "\""); +}); + +if (bumpOnly) process.exit(0); + +child.exec("bash bin/authors.sh", function(){}); + +var simple = number.slice(0, number.lastIndexOf(".")); + +rewrite("doc/compress.html", function(cmp) { + return cmp.replace(/\n "); +}); + +rewrite("index.html", function(index) { + return index.replace(/version 3.20<\/strong>/, + "version " + simple + ""); +}); From 3a76b567e1cd5ebe7a4aca7ef139058c36f154e9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 16 Jan 2014 20:05:09 +0100 Subject: [PATCH 155/155] Mark release 3.21 --- AUTHORS | 45 +++++++++++++++++++++++++++++++++++++++++++++ doc/compress.html | 1 + doc/releases.html | 13 +++++++++++++ index.html | 2 +- lib/codemirror.js | 2 +- package.json | 2 +- 6 files changed, 62 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 260c13a864..737acedaba 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,6 +2,7 @@ List of CodeMirror contributors. Updated before every release. 4r2r Aaron Brooks +Abe Fettig Adam King adanlobato Adán Lobato @@ -10,16 +11,20 @@ Ahmad Amireh Ahmad M. Zawawi ahoward Akeksandr Motsjonov +Alberto Pose Albert Xing Alexander Pavlov Alexander Schepanovski +Alexander Solovyov alexey-k Alex Piggott Amy Ananya Sen AndersMad +Anderson Mesquita Andre von Houck Andrey Lushnikov +Andy Joslin Andy Kimball Andy Li angelozerr @@ -27,16 +32,20 @@ angelo.zerr@gmail.com Ankit Ahuja Ansel Santosa Anthony Grimes +Anton Kovalyov areos +AtomicPages LLC Atul Bhouraskar Aurelian Oancea Bastian Müller benbro +Beni Cherniavsky-Paskin Benjamin DeCoste Ben Keen boomyjee borawjm Brandon Frohs +Brandon Wamboldt Brett Zamir Brian Sletten Bruce Mitchener @@ -54,9 +63,11 @@ Dan Heberden Daniel, Dao Quang Minh Daniel Faust Daniel Huigens +Daniel KJ Daniel Neel Daniel Parnell Danny Yoo +Darius Roberts David Mignot David Pathakjee deebugger @@ -68,10 +79,13 @@ Drew Hintz Drew Khoury Dror BG duralog +eborden edsharp ekhaled +Enam Mijbah Noor Eric Allam eustas +Fabio Zendhi Nagao Fauntleroy fbuchinger feizhang365 @@ -80,6 +94,7 @@ Felix Raab Filip Noetzel flack ForbesLindesay +Forbes Lindesay Ford_Lawnmower Gabriel Nahmias galambalazs @@ -94,12 +109,15 @@ Guillaume Massé Hans Engel Hardest Hasan Karahan +hitsthings Hocdoc Ian Beck +Ian Dickinson Ian Wehrman Ian Wetherbee Ice White ICHIKAWA, Yuji +ilvalle Ingo Richter Irakli Gozalishvili Ivan Kurnosov @@ -111,6 +129,7 @@ Jamie Hill Jan Jongboom jankeromnes Jan Keromnes +Jan Odvarko Jan T. Sott Jason Jason Grout @@ -122,16 +141,21 @@ jeffkenton Jeff Pickhardt jem (graphite) Jochen Berger +Johan Ask John Connor John Lees-Miller John Snelson +John Van Der Loo jongalloway +Jon Malmaud Joost-Wim Boekesteijn Joseph Pecoraro Joshua Newman jots +jsoojeon Juan Benavides Romero Jucovschi Constantin +Juho Vuori jwallers@gmail.com kaniga Ken Newman @@ -145,11 +169,13 @@ koops ks-ifware kubelsmieci Lanny +Laszlo Vidacs leaf corcoran Leonya Khachaturov Liam Newman LM Lorenzo Stoakes +Luciano Longo lynschinzer Maksim Lin Maksym Taran @@ -158,8 +184,10 @@ Marco Aurélio Marijn Haverbeke Mario Pietsch Mark Lentczner +Marko Bonaci Martin Balek Martín Gaitán +Martin Hasoň Mason Malone Mateusz Paprocki mats cronqvist @@ -169,6 +197,7 @@ Matt McDonald Matt Pass Matt Sacks Maximilian Hils +Maxim Kraev Max Kirsch mbarkhau Metatheos @@ -183,7 +212,9 @@ Mike Diaz Mike Ivanov Mike Kadin MinRK +Miraculix87 misfo +mloginov mps Narciso Jaramillo Nathan Williams @@ -192,6 +223,7 @@ nguillaumin Niels van Groningen Nikita Beloglazov Nikita Vasilyev +Nikolay Kostov nlwillia pablo Page @@ -199,6 +231,7 @@ Patrick Strawderman Paul Garvin Paul Ivanov Pavel Feldman +Pavel Strashkin Paweł Bartkiewicz peteguhl peterkroon @@ -219,16 +252,22 @@ Sascha Peilicke satchmorun sathyamoorthi SCLINIC\jdecker +Sebastian Zaha shaund shaun gilchrist +Shawn A +Shiv Deepak Shmuel Englard +soliton4 sonson spastorelli +Stanislav Oaserele Stas Kobzar Stefan Borsje Steffen Beyer Steve O'Hara stoskov +Taha Jahangir Tarmil tfjgeorge Thaddee Tyl @@ -244,12 +283,18 @@ Tomas Varaneckas Tom Erik Støwer Tom MacWright Tony Jian +Travis Heppe Vestimir Markov vf Volker Mische +wenli +Wesley Wiser William Jamieson Wojtek Ptak Xavier Mendez +YNH Webdev Yunchi Luo Yuvi Panda Zachary Dremann +zziuni +魏鹏刚 diff --git a/doc/compress.html b/doc/compress.html index beac099370..c2ba8f7021 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -33,6 +33,7 @@

    Version:

    ' + - '' + - val.replace(/ /g,'\xb7').replace('&', '&').replace('<', '<') + + '' + + '' + + esc(val.replace(/ /g,'\xb7')) + '' + '