diff --git a/AUTHORS b/AUTHORS
index 9d62d48e63..a4f2b94b79 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -87,6 +87,7 @@ Christian Petrov
Christopher Brown
ciaranj
CodeAnimal
+coderaiser
ComFreek
Curtis Gagliardi
dagsta
@@ -230,6 +231,7 @@ jwallers@gmail.com
kaniga
Ken Newman
Ken Rockot
+Kevin Earls
Kevin Sawicki
Kevin Ushey
Klaus Silveira
@@ -351,6 +353,7 @@ Randall Mason
Randy Burden
Randy Edmunds
Rasmus Erik Voel Jensen
+ray ratchup
Ray Ratchup
Richard van der Meer
Richard Z.H. Wang
diff --git a/README.md b/README.md
index bc6e7f5c71..38156a7427 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# CodeMirror
[](https://travis-ci.org/codemirror/CodeMirror)
[](https://www.npmjs.org/package/codemirror)
-[Funding status: ](https://marijnhaverbeke.nl/fund/)
+[Funding status: ](https://marijnhaverbeke.nl/fund/)
CodeMirror is a JavaScript component that provides a code editor in
the browser. When a mode is available for the language you are coding
diff --git a/addon/dialog/dialog.js b/addon/dialog/dialog.js
index e0e8ad4eb7..323b20078e 100644
--- a/addon/dialog/dialog.js
+++ b/addon/dialog/dialog.js
@@ -58,7 +58,9 @@
if (inp) {
if (options.value) {
inp.value = options.value;
- inp.select();
+ if (options.selectValueOnOpen !== false) {
+ inp.select();
+ }
}
if (options.onInput)
diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js
index ff4bb3f733..2bbc6f2a38 100644
--- a/addon/edit/closebrackets.js
+++ b/addon/edit/closebrackets.js
@@ -9,29 +9,157 @@
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
- var DEFAULT_BRACKETS = "()[]{}''\"\"";
- var DEFAULT_TRIPLES = "'\"";
- var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
- var SPACE_CHAR_REGEX = /\s/;
+ var defaults = {
+ pairs: "()[]{}''\"\"",
+ triples: "",
+ explode: "[]{}"
+ };
var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
- if (old != CodeMirror.Init && old)
- cm.removeKeyMap("autoCloseBrackets");
- if (!val) return;
- var pairs = DEFAULT_BRACKETS, triples = DEFAULT_TRIPLES, explode = DEFAULT_EXPLODE_ON_ENTER;
- if (typeof val == "string") pairs = val;
- else if (typeof val == "object") {
- if (val.pairs != null) pairs = val.pairs;
- if (val.triples != null) triples = val.triples;
- if (val.explode != null) explode = val.explode;
+ if (old && old != CodeMirror.Init) {
+ cm.removeKeyMap(keyMap);
+ cm.state.closeBrackets = null;
+ }
+ if (val) {
+ cm.state.closeBrackets = val;
+ cm.addKeyMap(keyMap);
}
- var map = buildKeymap(pairs, triples);
- if (explode) map.Enter = buildExplodeHandler(explode);
- cm.addKeyMap(map);
});
+ function getOption(conf, name) {
+ if (name == "pairs" && typeof conf == "string") return conf;
+ if (typeof conf == "object" && conf[name] != null) return conf[name];
+ return defaults[name];
+ }
+
+ var bind = defaults.pairs + "`";
+ var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
+ for (var i = 0; i < bind.length; i++)
+ keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i));
+
+ function handler(ch) {
+ return function(cm) { return handleChar(cm, ch); };
+ }
+
+ function getConfig(cm) {
+ var deflt = cm.state.closeBrackets;
+ if (!deflt) return null;
+ var mode = cm.getModeAt(cm.getCursor());
+ return mode.closeBrackets || deflt;
+ }
+
+ function handleBackspace(cm) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var cur = ranges[i].head;
+ cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
+ }
+ }
+
+ function handleEnter(cm) {
+ var conf = getConfig(cm);
+ var explode = conf && getOption(conf, "explode");
+ if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ cm.operation(function() {
+ cm.replaceSelection("\n\n", null);
+ cm.execCommand("goCharLeft");
+ ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var line = ranges[i].head.line;
+ cm.indentLine(line, null, true);
+ cm.indentLine(line + 1, null, true);
+ }
+ });
+ }
+
+ function handleChar(cm, ch) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var pos = pairs.indexOf(ch);
+ if (pos == -1) return CodeMirror.Pass;
+ var triples = getOption(conf, "triples");
+
+ var identical = pairs.charAt(pos + 1) == ch;
+ var ranges = cm.listSelections();
+ var opening = pos % 2 == 0;
+
+ var type, next;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], cur = range.head, curType;
+ var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
+ if (opening && !range.empty()) {
+ curType = "surround";
+ } else if ((identical || !opening) && next == ch) {
+ if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
+ curType = "skipThree";
+ else
+ curType = "skip";
+ } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
+ cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch &&
+ (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) {
+ curType = "addFour";
+ } else if (identical) {
+ if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both";
+ else return CodeMirror.Pass;
+ } else if (opening && (cm.getLine(cur.line).length == cur.ch ||
+ isClosingBracket(next, pairs) ||
+ /\s/.test(next))) {
+ curType = "both";
+ } else {
+ return CodeMirror.Pass;
+ }
+ if (!type) type = curType;
+ else if (type != curType) return CodeMirror.Pass;
+ }
+
+ var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
+ var right = pos % 2 ? ch : pairs.charAt(pos + 1);
+ cm.operation(function() {
+ if (type == "skip") {
+ cm.execCommand("goCharRight");
+ } else if (type == "skipThree") {
+ for (var i = 0; i < 3; i++)
+ cm.execCommand("goCharRight");
+ } else if (type == "surround") {
+ var sels = cm.getSelections();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = left + sels[i] + right;
+ cm.replaceSelections(sels, "around");
+ } else if (type == "both") {
+ cm.replaceSelection(left + right, null);
+ cm.execCommand("goCharLeft");
+ } else if (type == "addFour") {
+ cm.replaceSelection(left + left + left + left, "before");
+ cm.execCommand("goCharRight");
+ }
+ });
+ }
+
+ function isClosingBracket(ch, pairs) {
+ var pos = pairs.lastIndexOf(ch);
+ return pos > -1 && pos % 2 == 1;
+ }
+
function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1));
@@ -53,109 +181,4 @@
stream.start = stream.pos;
}
}
-
- function buildKeymap(pairs, triples) {
- var map = {
- name : "autoCloseBrackets",
- Backspace: function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- if (!ranges[i].empty()) return CodeMirror.Pass;
- var around = charsAround(cm, ranges[i].head);
- if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
- }
- for (var i = ranges.length - 1; i >= 0; i--) {
- var cur = ranges[i].head;
- cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
- }
- }
- };
- var closingBrackets = "";
- for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
- closingBrackets += right;
- map["'" + left + "'"] = function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections(), type, next;
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i], cur = range.head, curType;
- var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
- if (!range.empty()) {
- curType = "surround";
- } else if (left == right && next == right) {
- if (cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == left + left + left)
- curType = "skipThree";
- else
- curType = "skip";
- } else if (left == right && cur.ch > 1 && triples.indexOf(left) >= 0 &&
- cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left &&
- (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left)) {
- curType = "addFour";
- } else if (left == '"' || left == "'") {
- if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both";
- else return CodeMirror.Pass;
- } else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) {
- curType = "both";
- } else {
- return CodeMirror.Pass;
- }
- if (!type) type = curType;
- else if (type != curType) return CodeMirror.Pass;
- }
-
- cm.operation(function() {
- if (type == "skip") {
- cm.execCommand("goCharRight");
- } else if (type == "skipThree") {
- for (var i = 0; i < 3; i++)
- cm.execCommand("goCharRight");
- } else if (type == "surround") {
- var sels = cm.getSelections();
- for (var i = 0; i < sels.length; i++)
- sels[i] = left + sels[i] + right;
- cm.replaceSelections(sels, "around");
- } else if (type == "both") {
- cm.replaceSelection(left + right, null);
- cm.execCommand("goCharLeft");
- } else if (type == "addFour") {
- cm.replaceSelection(left + left + left + left, "before");
- cm.execCommand("goCharRight");
- }
- });
- };
- if (left != right) map["'" + right + "'"] = function(cm) {
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- if (!range.empty() ||
- cm.getRange(range.head, Pos(range.head.line, range.head.ch + 1)) != right)
- return CodeMirror.Pass;
- }
- cm.execCommand("goCharRight");
- };
- })(pairs.charAt(i), pairs.charAt(i + 1));
- return map;
- }
-
- function buildExplodeHandler(pairs) {
- return function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- if (!ranges[i].empty()) return CodeMirror.Pass;
- var around = charsAround(cm, ranges[i].head);
- if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
- }
- cm.operation(function() {
- cm.replaceSelection("\n\n", null);
- cm.execCommand("goCharLeft");
- ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var line = ranges[i].head.line;
- cm.indentLine(line, null, true);
- cm.indentLine(line + 1, null, true);
- }
- });
- };
- }
});
diff --git a/addon/fold/foldgutter.js b/addon/fold/foldgutter.js
index 199120c726..ed7bd87d34 100644
--- a/addon/fold/foldgutter.js
+++ b/addon/fold/foldgutter.js
@@ -52,7 +52,7 @@
function isFolded(cm, line) {
var marks = cm.findMarksAt(Pos(line));
for (var i = 0; i < marks.length; ++i)
- if (marks[i].__isFold && marks[i].find().from.line == line) return true;
+ if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
}
function marker(spec) {
@@ -98,7 +98,9 @@
if (!state) return;
var opts = state.options;
if (gutter != opts.gutter) return;
- cm.foldCode(Pos(line, 0), opts.rangeFinder);
+ var folded = isFolded(cm, line);
+ if (folded) folded.clear();
+ else cm.foldCode(Pos(line, 0), opts.rangeFinder);
}
function onChange(cm) {
diff --git a/addon/hint/css-hint.js b/addon/hint/css-hint.js
index 488da3449e..22642727cf 100644
--- a/addon/hint/css-hint.js
+++ b/addon/hint/css-hint.js
@@ -20,6 +20,10 @@
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
if (inner.mode.name != "css") return;
+ if (token.type == "keyword" && "!important".indexOf(token.string) == 0)
+ return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start),
+ to: CodeMirror.Pos(cur.line, token.end)};
+
var start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
if (/[^\w$_-]/.test(word)) {
word = ""; start = end = cur.ch;
diff --git a/addon/hint/sql-hint.js b/addon/hint/sql-hint.js
index 5b0cc7696f..cba27a0b2c 100644
--- a/addon/hint/sql-hint.js
+++ b/addon/hint/sql-hint.js
@@ -52,12 +52,9 @@
function addMatches(result, search, wordlist, formatter) {
for (var word in wordlist) {
if (!wordlist.hasOwnProperty(word)) continue;
- if (Array.isArray(wordlist)) {
- word = wordlist[word];
- }
- if (match(search, word)) {
- result.push(formatter(word));
- }
+ if (wordlist.slice) word = wordlist[word];
+
+ if (match(search, word)) result.push(formatter(word));
}
}
@@ -120,7 +117,7 @@
table = findTableByAlias(table, editor);
var columns = getItem(tables, table);
- if (columns && Array.isArray(tables) && columns.columns)
+ if (columns && columns.columns)
columns = columns.columns;
if (columns) {
@@ -208,9 +205,17 @@
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
tables = (options && options.tables) || {};
var defaultTableName = options && options.defaultTable;
- defaultTable = (defaultTableName && getItem(tables, defaultTableName)) || [];
+ defaultTable = defaultTableName && getItem(tables, defaultTableName);
keywords = keywords || getKeywords(editor);
+ if (defaultTableName && !defaultTable)
+ defaultTable = findTableByAlias(defaultTableName, editor);
+
+ defaultTable = defaultTable || [];
+
+ if (defaultTable.columns)
+ defaultTable = defaultTable.columns;
+
var cur = editor.getCursor();
var result = [];
var token = editor.getTokenAt(cur), start, end, search;
diff --git a/addon/lint/lint.js b/addon/lint/lint.js
index 18eb709016..c7e093960e 100644
--- a/addon/lint/lint.js
+++ b/addon/lint/lint.js
@@ -163,6 +163,7 @@
function onChange(cm) {
var state = cm.state.lint;
+ if (!state) return;
clearTimeout(state.timeout);
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
}
@@ -188,6 +189,7 @@
clearMarks(cm);
cm.off("change", onChange);
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
+ clearTimeout(cm.state.lint.timeout);
delete cm.state.lint;
}
diff --git a/addon/merge/merge.js b/addon/merge/merge.js
index f1f3aafc49..5b04b03292 100644
--- a/addon/merge/merge.js
+++ b/addon/merge/merge.js
@@ -37,7 +37,9 @@
constructor: DiffView,
init: function(pane, orig, options) {
this.edit = this.mv.edit;
+ (this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
+ this.orig.state.diffViews = [this];
this.diff = getDiff(asString(orig), asString(options.value));
this.chunks = getChunks(this.diff);
@@ -732,4 +734,42 @@
function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
+
+ function findPrevDiff(chunks, start, isOrig) {
+ for (var i = chunks.length - 1; i >= 0; i--) {
+ var chunk = chunks[i];
+ var to = (isOrig ? chunk.origTo : chunk.editTo) - 1;
+ if (to < start) return to;
+ }
+ }
+
+ function findNextDiff(chunks, start, isOrig) {
+ for (var i = 0; i < chunks.length; i++) {
+ var chunk = chunks[i];
+ var from = (isOrig ? chunk.origFrom : chunk.editFrom);
+ if (from > start) return from;
+ }
+ }
+
+ function goNearbyDiff(cm, dir) {
+ var found = null, views = cm.state.diffViews, line = cm.getCursor().line;
+ if (views) for (var i = 0; i < views.length; i++) {
+ var dv = views[i], isOrig = cm == dv.orig;
+ ensureDiff(dv);
+ var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig);
+ if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found)))
+ found = pos;
+ }
+ if (found != null)
+ cm.setCursor(found, 0);
+ else
+ return CodeMirror.Pass;
+ }
+
+ CodeMirror.commands.goNextDiff = function(cm) {
+ return goNearbyDiff(cm, 1);
+ };
+ CodeMirror.commands.goPrevDiff = function(cm) {
+ return goNearbyDiff(cm, -1);
+ };
});
diff --git a/addon/scroll/simplescrollbars.js b/addon/scroll/simplescrollbars.js
index bb06adb86a..f78353a130 100644
--- a/addon/scroll/simplescrollbars.js
+++ b/addon/scroll/simplescrollbars.js
@@ -69,14 +69,20 @@
if (update !== false) this.scroll(pos, this.orientation);
};
+ var minButtonSize = 10;
+
Bar.prototype.update = function(scrollSize, clientSize, barSize) {
this.screen = clientSize;
this.total = scrollSize;
this.size = barSize;
- // FIXME clip to min size?
+ var buttonSize = this.screen * (this.size / this.total);
+ if (buttonSize < minButtonSize) {
+ this.size -= minButtonSize - buttonSize;
+ buttonSize = minButtonSize;
+ }
this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
- this.screen * (this.size / this.total) + "px";
+ buttonSize + "px";
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
this.pos * (this.size / this.total) + "px";
};
diff --git a/addon/tern/tern.js b/addon/tern/tern.js
index b049549d35..99b8a64682 100644
--- a/addon/tern/tern.js
+++ b/addon/tern/tern.js
@@ -443,7 +443,7 @@
function atInterestingExpression(cm) {
var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
- return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
+ return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
}
// Variable renaming
diff --git a/bower.json b/bower.json
index c59f2d9d27..334447e56d 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "codemirror",
- "version":"5.0.0",
+ "version":"5.1.0",
"main": ["lib/codemirror.js", "lib/codemirror.css"],
"ignore": [
"**/.*",
diff --git a/doc/compress.html b/doc/compress.html
index a376c86d2d..65df6483f5 100644
--- a/doc/compress.html
+++ b/doc/compress.html
@@ -36,6 +36,7 @@
Overhaul of paste-handling (less fragile), fixes for several
serious IE8 issues (cursor jumping, end-of-document bugs) and a number
of small problems.
-
- Get the current version:
5.0.
+ Get the current version:
5.1.
You can see the
code or
read the
release notes.
There is a
minification helper.
diff --git a/keymap/sublime.js b/keymap/sublime.js
index 45936c3628..cf18573605 100644
--- a/keymap/sublime.js
+++ b/keymap/sublime.js
@@ -409,6 +409,19 @@
map[cK + ctrl + "Backspace"] = "delLineLeft";
+ cmds[map["Backspace"] = "smartBackspace"] = function(cm) {
+ if (cm.somethingSelected()) return CodeMirror.Pass;
+
+ var cursor = cm.getCursor();
+ var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);
+ var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize"));
+
+ if (!/\S/.test(toStartOfLine) && column % cm.getOption("indentUnit") == 0)
+ return cm.indentSelection("subtract");
+ else
+ return CodeMirror.Pass;
+ };
+
cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
cm.operation(function() {
var ranges = cm.listSelections();
diff --git a/keymap/vim.js b/keymap/vim.js
index 682eb7a8c4..f5f8cafbc7 100644
--- a/keymap/vim.js
+++ b/keymap/vim.js
@@ -336,7 +336,11 @@
}
var numberRegex = /[\d]/;
- var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
+ var wordCharTest = [CodeMirror.isWordChar, function(ch) {
+ return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch);
+ }], bigWordCharTest = [function(ch) {
+ return /\S/.test(ch);
+ }];
function makeKeyRange(start, size) {
var keys = [];
for (var i = start; i < start + size; i++) {
@@ -1035,12 +1039,10 @@
break;
case 'search':
this.processSearch(cm, vim, command);
- clearInputState(cm);
break;
case 'ex':
case 'keyToEx':
this.processEx(cm, vim, command);
- clearInputState(cm);
break;
default:
break;
@@ -1133,6 +1135,7 @@
updateSearchQuery(cm, query, ignoreCase, smartCase);
} catch (e) {
showConfirm(cm, 'Invalid regex: ' + query);
+ clearInputState(cm);
return;
}
commandDispatcher.processMotion(cm, vim, {
@@ -1182,6 +1185,7 @@
clearSearchHighlight(cm);
cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
CodeMirror.e_stop(e);
+ clearInputState(cm);
close();
cm.focus();
}
@@ -1248,6 +1252,7 @@
vimGlobalState.exCommandHistoryController.pushInput(input);
vimGlobalState.exCommandHistoryController.reset();
CodeMirror.e_stop(e);
+ clearInputState(cm);
close();
cm.focus();
}
@@ -2628,9 +2633,6 @@
function lineLength(cm, lineNum) {
return cm.getLine(lineNum).length;
}
- function reverse(s){
- return s.split('').reverse().join('');
- }
function trim(s) {
if (s.trim) {
return s.trim();
@@ -2944,59 +2946,38 @@
// Seek to first word or non-whitespace character, depending on if
// noSymbol is true.
- var textAfterIdx = line.substring(idx);
- var firstMatchedChar;
- if (noSymbol) {
- firstMatchedChar = textAfterIdx.search(/\w/);
- } else {
- firstMatchedChar = textAfterIdx.search(/\S/);
+ var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0];
+ while (!test(line.charAt(idx))) {
+ idx++;
+ if (idx >= line.length) { return null; }
}
- if (firstMatchedChar == -1) {
- return null;
- }
- idx += firstMatchedChar;
- textAfterIdx = line.substring(idx);
- var textBeforeIdx = line.substring(0, idx);
- var matchRegex;
- // Greedy matchers for the "word" we are trying to expand.
if (bigWord) {
- matchRegex = /^\S+/;
+ test = bigWordCharTest[0];
} else {
- if ((/\w/).test(line.charAt(idx))) {
- matchRegex = /^\w+/;
- } else {
- matchRegex = /^[^\w\s]+/;
+ test = wordCharTest[0];
+ if (!test(line.charAt(idx))) {
+ test = wordCharTest[1];
}
}
- var wordAfterRegex = matchRegex.exec(textAfterIdx);
- var wordStart = idx;
- var wordEnd = idx + wordAfterRegex[0].length;
- // TODO: Find a better way to do this. It will be slow on very long lines.
- var revTextBeforeIdx = reverse(textBeforeIdx);
- var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx);
- if (wordBeforeRegex) {
- wordStart -= wordBeforeRegex[0].length;
- }
+ var end = idx, start = idx;
+ while (test(line.charAt(end)) && end < line.length) { end++; }
+ while (test(line.charAt(start)) && start >= 0) { start--; }
+ start++;
if (inclusive) {
- // If present, trim all whitespace after word.
- // Otherwise, trim all whitespace before word.
- var textAfterWordEnd = line.substring(wordEnd);
- var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length;
- if (whitespacesAfterWord > 0) {
- wordEnd += whitespacesAfterWord;
- } else {
- var revTrim = revTextBeforeIdx.length - wordStart;
- var textBeforeWordStart = revTextBeforeIdx.substring(revTrim);
- var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length;
- wordStart -= whitespacesBeforeWord;
+ // If present, include all whitespace after word.
+ // Otherwise, include all whitespace before word, except indentation.
+ var wordEnd = end;
+ while (/\s/.test(line.charAt(end)) && end < line.length) { end++; }
+ if (wordEnd == end) {
+ var wordStart = start;
+ while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; }
+ if (!start) { start = wordStart; }
}
}
-
- return { start: Pos(cur.line, wordStart),
- end: Pos(cur.line, wordEnd) };
+ return { start: Pos(cur.line, start), end: Pos(cur.line, end) };
}
function recordJumpPosition(cm, oldCur, newCur) {
@@ -3154,7 +3135,7 @@
var pos = cur.ch;
var line = cm.getLine(lineNum);
var dir = forward ? 1 : -1;
- var regexps = bigWord ? bigWordRegexp : wordRegexp;
+ var charTests = bigWord ? bigWordCharTest: wordCharTest;
if (emptyLineIsWord && line == '') {
lineNum += dir;
@@ -3174,11 +3155,11 @@
// Find bounds of next word.
while (pos != stop) {
var foundWord = false;
- for (var i = 0; i < regexps.length && !foundWord; ++i) {
- if (regexps[i].test(line.charAt(pos))) {
+ for (var i = 0; i < charTests.length && !foundWord; ++i) {
+ if (charTests[i](line.charAt(pos))) {
wordStart = pos;
// Advance to end of word.
- while (pos != stop && regexps[i].test(line.charAt(pos))) {
+ while (pos != stop && charTests[i](line.charAt(pos))) {
pos += dir;
}
wordEnd = pos;
@@ -3510,7 +3491,8 @@
function dialog(cm, template, shortText, onClose, options) {
if (cm.openDialog) {
cm.openDialog(template, onClose, { bottom: true, value: options.value,
- onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
+ onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp,
+ selectValueOnOpen: false});
}
else {
onClose(prompt(shortText, ''));
@@ -3888,6 +3870,13 @@
};
ExCommandDispatcher.prototype = {
processCommand: function(cm, input, opt_params) {
+ var that = this;
+ cm.operation(function () {
+ cm.curOp.isVimOp = true;
+ that._processCommand(cm, input, opt_params);
+ });
+ },
+ _processCommand: function(cm, input, opt_params) {
var vim = cm.state.vim;
var commandHistoryRegister = vimGlobalState.registerController.getRegister(':');
var previousCommand = commandHistoryRegister.toString();
@@ -4806,7 +4795,7 @@
var anchor = cm.getCursor('anchor');
var head = cm.getCursor('head');
// Enter or exit visual mode to match mouse selection.
- if (vim.visualMode && cursorEqual(head, anchor) && lineLength(cm, head.line) > head.ch) {
+ if (vim.visualMode && !cm.somethingSelected()) {
exitVisualMode(cm, false);
} else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
vim.visualMode = true;
diff --git a/lib/codemirror.css b/lib/codemirror.css
index 1902ba4abd..e531d296e2 100644
--- a/lib/codemirror.css
+++ b/lib/codemirror.css
@@ -33,8 +33,7 @@
min-width: 20px;
text-align: right;
color: #999;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
+ white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
@@ -154,14 +153,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
@@ -196,8 +191,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
display: inline-block;
margin-bottom: -30px;
/* Hack to make IE7 behave */
@@ -265,6 +258,16 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
outline: none;
}
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
.CodeMirror-measure {
position: absolute;
width: 100%;
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 53b35329fc..33f8f8f1a5 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -82,12 +82,15 @@
keyMaps: [], // stores maps added by addKeyMap
overlays: [], // highlighting overlays, as added by addOverlay
modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
- overwrite: false, focused: false,
+ overwrite: false,
+ delayingBlurEvent: false,
+ focused: false,
suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
draggingText: false,
highlight: new Delayed(), // stores highlight worker timeout
- keySeq: null // Unfinished key sequence
+ keySeq: null, // Unfinished key sequence
+ specialChars: null
};
var cm = this;
@@ -591,7 +594,7 @@
"CodeMirror-linenumber CodeMirror-gutter-elt"));
var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
display.lineGutter.style.width = "";
- display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
+ display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;
display.lineNumWidth = display.lineNumInnerWidth + padding;
display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
display.lineGutter.style.width = display.lineNumWidth + "px";
@@ -1381,8 +1384,14 @@
return false;
}
- if (text.charCodeAt(0) == 0x200b && cm.doc.sel == cm.display.selForContextMenu && !prevInput)
- prevInput = "\u200b";
+ if (cm.doc.sel == cm.display.selForContextMenu) {
+ if (text.charCodeAt(0) == 0x200b) {
+ if (!prevInput) prevInput = "\u200b";
+ } else if (prevInput == "\u200b") {
+ text = text.slice(1);
+ prevInput = "";
+ }
+ }
// Find the part of the input that is actually new
var same = 0, l = Math.min(prevInput.length, text.length);
while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
@@ -1458,7 +1467,7 @@
if (te.selectionStart != null) {
if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
var i = 0, poll = function() {
- if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0)
+ if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && input.prevInput == "\u200b")
operation(cm, commands.selectAll)(cm);
else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
else display.input.reset();
@@ -1491,6 +1500,7 @@
this.cm = cm;
this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
this.polling = new Delayed();
+ this.gracePeriod = false;
}
ContentEditableInput.prototype = copyObj({
@@ -1625,10 +1635,21 @@
sel.removeAllRanges();
sel.addRange(rng);
if (old && sel.anchorNode == null) sel.addRange(old);
+ else if (gecko) this.startGracePeriod();
}
this.rememberSelection();
},
+ startGracePeriod: function() {
+ var input = this;
+ clearTimeout(this.gracePeriod);
+ this.gracePeriod = setTimeout(function() {
+ input.gracePeriod = false;
+ if (input.selectionChanged())
+ input.cm.operation(function() { input.cm.curOp.selectionChanged = true; });
+ }, 20);
+ },
+
showMultipleSelections: function(info) {
removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
@@ -1671,12 +1692,15 @@
this.polling.set(this.cm.options.pollInterval, poll);
},
- pollSelection: function() {
- if (this.composing) return;
+ selectionChanged: function() {
+ var sel = window.getSelection();
+ return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
+ sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset;
+ },
- var sel = window.getSelection(), cm = this.cm;
- if (sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
- sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset) {
+ pollSelection: function() {
+ if (!this.composing && !this.gracePeriod && this.selectionChanged()) {
+ var sel = window.getSelection(), cm = this.cm;
this.rememberSelection();
var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
var head = domToPos(cm, sel.focusNode, sel.focusOffset);
@@ -3475,6 +3499,7 @@
break;
case 3:
if (captureRightClick) onContextMenu(cm, e);
+ else delayBlurEvent(cm);
break;
}
}
@@ -3517,10 +3542,11 @@
e_preventDefault(e2);
if (!modifier)
extendSelection(cm.doc, start);
- display.input.focus();
- // Work around unexplainable focus problem in IE9 (#2127)
- if (ie && ie_version == 9)
+ // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
+ if (webkit || ie && ie_version == 9)
setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
+ else
+ display.input.focus();
}
});
// Let the drag handler handle this.
@@ -3546,6 +3572,7 @@
ourRange = new Range(start, start);
} else {
ourRange = doc.sel.primary();
+ ourIndex = doc.sel.primIndex;
}
if (e.altKey) {
@@ -3577,7 +3604,7 @@
ourIndex = ranges.length;
setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex),
{scroll: false, origin: "*mouse"});
- } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single") {
+ } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) {
setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0));
startSel = doc.sel;
} else {
@@ -4050,7 +4077,19 @@
// FOCUS/BLUR EVENTS
+ function delayBlurEvent(cm) {
+ cm.state.delayingBlurEvent = true;
+ setTimeout(function() {
+ if (cm.state.delayingBlurEvent) {
+ cm.state.delayingBlurEvent = false;
+ onBlur(cm);
+ }
+ }, 100);
+ }
+
function onFocus(cm) {
+ if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false;
+
if (cm.options.readOnly == "nocursor") return;
if (!cm.state.focused) {
signal(cm, "focus", cm);
@@ -4068,6 +4107,8 @@
restartBlink(cm);
}
function onBlur(cm) {
+ if (cm.state.delayingBlurEvent) return;
+
if (cm.state.focused) {
signal(cm, "blur", cm);
cm.state.focused = false;
@@ -4824,7 +4865,7 @@
getHelpers: function(pos, type) {
var found = [];
- if (!helpers.hasOwnProperty(type)) return helpers;
+ if (!helpers.hasOwnProperty(type)) return found;
var help = helpers[type], mode = this.getModeAt(pos);
if (typeof mode[type] == "string") {
if (help[mode[type]]) found.push(help[mode[type]]);
@@ -4906,12 +4947,6 @@
});
}),
- addLineWidget: methodOp(function(handle, node, options) {
- return addLineWidget(this, handle, node, options);
- }),
-
- removeLineWidget: function(widget) { widget.clear(); },
-
lineInfo: function(line) {
if (typeof line == "number") {
if (!isLine(this.doc, line)) return null;
@@ -5186,10 +5221,10 @@
clearCaches(cm);
regChange(cm);
}, true);
- option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) {
- cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
- cm.refresh();
- }, true);
+ option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) {
+ cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
+ if (old != CodeMirror.Init) cm.refresh();
+ });
option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
option("electricChars", true);
option("inputStyle", mobile ? "contenteditable" : "textarea", function() {
@@ -6431,10 +6466,10 @@
// Line widgets are block elements displayed above or below a line.
- var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
+ var LineWidget = CodeMirror.LineWidget = function(doc, node, options) {
if (options) for (var opt in options) if (options.hasOwnProperty(opt))
this[opt] = options[opt];
- this.cm = cm;
+ this.doc = doc;
this.node = node;
};
eventMixin(LineWidget);
@@ -6445,52 +6480,55 @@
}
LineWidget.prototype.clear = function() {
- var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
+ var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
if (no == null || !ws) return;
for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
if (!ws.length) line.widgets = null;
var height = widgetHeight(this);
- runInOp(cm, function() {
+ updateLineHeight(line, Math.max(0, line.height - height));
+ if (cm) runInOp(cm, function() {
adjustScrollWhenAboveVisible(cm, line, -height);
regLineChange(cm, no, "widget");
- updateLineHeight(line, Math.max(0, line.height - height));
});
};
LineWidget.prototype.changed = function() {
- var oldH = this.height, cm = this.cm, line = this.line;
+ var oldH = this.height, cm = this.doc.cm, line = this.line;
this.height = null;
var diff = widgetHeight(this) - oldH;
if (!diff) return;
- runInOp(cm, function() {
+ updateLineHeight(line, line.height + diff);
+ if (cm) runInOp(cm, function() {
cm.curOp.forceUpdate = true;
adjustScrollWhenAboveVisible(cm, line, diff);
- updateLineHeight(line, line.height + diff);
});
};
function widgetHeight(widget) {
if (widget.height != null) return widget.height;
+ var cm = widget.doc.cm;
+ if (!cm) return 0;
if (!contains(document.body, widget.node)) {
var parentStyle = "position: relative;";
if (widget.coverGutter)
- parentStyle += "margin-left: -" + widget.cm.display.gutters.offsetWidth + "px;";
+ parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;";
if (widget.noHScroll)
- parentStyle += "width: " + widget.cm.display.wrapper.clientWidth + "px;";
- removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
+ parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;";
+ removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
}
return widget.height = widget.node.offsetHeight;
}
- function addLineWidget(cm, handle, node, options) {
- var widget = new LineWidget(cm, node, options);
- if (widget.noHScroll) cm.display.alignWidgets = true;
- changeLine(cm.doc, handle, "widget", function(line) {
+ function addLineWidget(doc, handle, node, options) {
+ var widget = new LineWidget(doc, node, options);
+ var cm = doc.cm;
+ if (cm && widget.noHScroll) cm.display.alignWidgets = true;
+ changeLine(doc, handle, "widget", function(line) {
var widgets = line.widgets || (line.widgets = []);
if (widget.insertAt == null) widgets.push(widget);
else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
widget.line = line;
- if (!lineIsHidden(cm.doc, line)) {
- var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
+ if (cm && !lineIsHidden(doc, line)) {
+ var aboveVisible = heightAtLine(line) < doc.scrollTop;
updateLineHeight(line, line.height + widgetHeight(widget));
if (aboveVisible) addToScrollPos(cm, null, widget.height);
cm.curOp.forceUpdate = true;
@@ -6710,7 +6748,9 @@
// is needed on Webkit to be able to get line-level bounding
// rectangles for it (in measureChar).
var content = elt("span", null, null, webkit ? "padding-right: .1px" : null);
- var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm};
+ var builder = {pre: elt("pre", [content]), content: content,
+ col: 0, pos: 0, cm: cm,
+ splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")};
lineView.measure = {};
// Iterate over the logical lines that make up this visual line.
@@ -6720,8 +6760,6 @@
builder.addToken = buildToken;
// Optionally wire in some hacks into the token-rendering
// algorithm, to deal with browser quirks.
- if ((ie || webkit) && cm.getOption("lineWrapping"))
- builder.addToken = buildTokenSplitSpaces(builder.addToken);
if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
builder.addToken = buildTokenBadBidi(builder.addToken, order);
builder.map = [];
@@ -6770,10 +6808,11 @@
// the line map. Takes care to render special characters separately.
function buildToken(builder, text, style, startStyle, endStyle, title, css) {
if (!text) return;
- var special = builder.cm.options.specialChars, mustWrap = false;
+ var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text;
+ var special = builder.cm.state.specialChars, mustWrap = false;
if (!special.test(text)) {
builder.col += text.length;
- var content = document.createTextNode(text);
+ var content = document.createTextNode(displayText);
builder.map.push(builder.pos, builder.pos + text.length, content);
if (ie && ie_version < 9) mustWrap = true;
builder.pos += text.length;
@@ -6784,7 +6823,7 @@
var m = special.exec(text);
var skipped = m ? m.index - pos : text.length - pos;
if (skipped) {
- var txt = document.createTextNode(text.slice(pos, pos + skipped));
+ var txt = document.createTextNode(displayText.slice(pos, pos + skipped));
if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
else content.appendChild(txt);
builder.map.push(builder.pos, builder.pos + skipped, txt);
@@ -6821,22 +6860,17 @@
builder.content.appendChild(content);
}
- function buildTokenSplitSpaces(inner) {
- function split(old) {
- var out = " ";
- for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
- out += " ";
- return out;
- }
- return function(builder, text, style, startStyle, endStyle, title) {
- inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
- };
+ function splitSpaces(old) {
+ var out = " ";
+ for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
+ out += " ";
+ return out;
}
// Work around nonsense dimensions being reported for stretches of
// right-to-left text.
function buildTokenBadBidi(inner, order) {
- return function(builder, text, style, startStyle, endStyle, title) {
+ return function(builder, text, style, startStyle, endStyle, title, css) {
style = style ? style + " cm-force-border" : "cm-force-border";
var start = builder.pos, end = start + text.length;
for (;;) {
@@ -6845,8 +6879,8 @@
var part = order[i];
if (part.to > start && part.from <= start) break;
}
- if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title);
- inner(builder, text.slice(0, part.to - start), style, startStyle, null, title);
+ if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css);
+ inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css);
startStyle = null;
text = text.slice(part.to - start);
start = part.to;
@@ -7368,13 +7402,19 @@
});
}),
+ addLineWidget: docMethodOp(function(handle, node, options) {
+ return addLineWidget(this, handle, node, options);
+ }),
+ removeLineWidget: function(widget) { widget.clear(); },
+
markText: function(from, to, options) {
return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
},
setBookmark: function(pos, options) {
var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
insertLeft: options && options.insertLeft,
- clearWhenEmpty: false, shared: options && options.shared};
+ clearWhenEmpty: false, shared: options && options.shared,
+ handleMouseEvents: options && options.handleMouseEvents};
pos = clipPos(this, pos);
return markText(this, pos, pos, realOpts, "bookmark");
},
@@ -8639,7 +8679,7 @@
// THE END
- CodeMirror.version = "5.0.0";
+ CodeMirror.version = "5.1.0";
return CodeMirror;
});
diff --git a/mode/asciiarmor/asciiarmor.js b/mode/asciiarmor/asciiarmor.js
new file mode 100644
index 0000000000..d830903767
--- /dev/null
+++ b/mode/asciiarmor/asciiarmor.js
@@ -0,0 +1,73 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ function errorIfNotEmpty(stream) {
+ var nonWS = stream.match(/^\s*\S/);
+ stream.skipToEnd();
+ return nonWS ? "error" : null;
+ }
+
+ CodeMirror.defineMode("asciiarmor", function() {
+ return {
+ token: function(stream, state) {
+ var m;
+ if (state.state == "top") {
+ if (stream.sol() && (m = stream.match(/^-----BEGIN (.*)?-----\s*$/))) {
+ state.state = "headers";
+ state.type = m[1];
+ return "tag";
+ }
+ return errorIfNotEmpty(stream);
+ } else if (state.state == "headers") {
+ if (stream.sol() && stream.match(/^\w+:/)) {
+ state.state = "header";
+ return "atom";
+ } else {
+ var result = errorIfNotEmpty(stream);
+ if (result) state.state = "body";
+ return result;
+ }
+ } else if (state.state == "header") {
+ stream.skipToEnd();
+ state.state = "headers";
+ return "string";
+ } else if (state.state == "body") {
+ if (stream.sol() && (m = stream.match(/^-----END (.*)?-----\s*$/))) {
+ if (m[1] != state.type) return "error";
+ state.state = "end";
+ return "tag";
+ } else {
+ if (stream.eatWhile(/[A-Za-z0-9+\/=]/)) {
+ return null;
+ } else {
+ stream.next();
+ return "error";
+ }
+ }
+ } else if (state.state == "end") {
+ return errorIfNotEmpty(stream);
+ }
+ },
+ blankLine: function(state) {
+ if (state.state == "headers") state.state = "body";
+ },
+ startState: function() {
+ return {state: "top", type: null};
+ }
+ };
+ });
+
+ CodeMirror.defineMIME("application/pgp", "asciiarmor");
+ CodeMirror.defineMIME("application/pgp-keys", "asciiarmor");
+ CodeMirror.defineMIME("application/pgp-signature", "asciiarmor");
+});
diff --git a/mode/asciiarmor/index.html b/mode/asciiarmor/index.html
new file mode 100644
index 0000000000..8ba1b5c76c
--- /dev/null
+++ b/mode/asciiarmor/index.html
@@ -0,0 +1,46 @@
+
+
+
CodeMirror: ASCII Armor (PGP) mode
+
+
+
+
+
+
+
+
+
+
+ASCII Armor (PGP) mode
+
+
+
+
+MIME types
+defined: application/pgp, application/pgp-keys, application/pgp-signature
+
+
diff --git a/mode/clike/clike.js b/mode/clike/clike.js
index e2223ccd15..f996f019dc 100644
--- a/mode/clike/clike.js
+++ b/mode/clike/clike.js
@@ -403,7 +403,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
return "atom";
}
- }
+ },
+ modeProps: {closeBrackets: {triples: '"'}}
});
def(["x-shader/x-vertex", "x-shader/x-fragment"], {
diff --git a/mode/clojure/clojure.js b/mode/clojure/clojure.js
index c334de7300..d531022a2e 100644
--- a/mode/clojure/clojure.js
+++ b/mode/clojure/clojure.js
@@ -234,6 +234,7 @@ CodeMirror.defineMode("clojure", function (options) {
return state.indentStack.indent;
},
+ closeBrackets: {pairs: "()[]{}\"\""},
lineComment: ";;"
};
});
diff --git a/mode/cmake/cmake.js b/mode/cmake/cmake.js
new file mode 100644
index 0000000000..9f9eda5417
--- /dev/null
+++ b/mode/cmake/cmake.js
@@ -0,0 +1,97 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object")
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd)
+ define(["../../lib/codemirror"], mod);
+ else
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.defineMode("cmake", function () {
+ var variable_regex = /({)?[a-zA-Z0-9_]+(})?/;
+
+ function tokenString(stream, state) {
+ var current, prev, found_var = false;
+ while (!stream.eol() && (current = stream.next()) != state.pending) {
+ if (current === '$' && prev != '\\' && state.pending == '"') {
+ found_var = true;
+ break;
+ }
+ prev = current;
+ }
+ if (found_var) {
+ stream.backUp(1);
+ }
+ if (current == state.pending) {
+ state.continueString = false;
+ } else {
+ state.continueString = true;
+ }
+ return "string";
+ }
+
+ function tokenize(stream, state) {
+ var ch = stream.next();
+
+ // Have we found a variable?
+ if (ch === '$') {
+ if (stream.match(variable_regex)) {
+ return 'variable-2';
+ }
+ return 'variable';
+ }
+ // Should we still be looking for the end of a string?
+ if (state.continueString) {
+ // If so, go through the loop again
+ stream.backUp(1);
+ return tokenString(stream, state);
+ }
+ // Do we just have a function on our hands?
+ // In 'cmake_minimum_required (VERSION 2.8.8)', 'cmake_minimum_required' is matched
+ if (stream.match(/(\s+)?\w+\(/) || stream.match(/(\s+)?\w+\ \(/)) {
+ stream.backUp(1);
+ return 'def';
+ }
+ if (ch == "#") {
+ stream.skipToEnd();
+ return "comment";
+ }
+ // Have we found a string?
+ if (ch == "'" || ch == '"') {
+ // Store the type (single or double)
+ state.pending = ch;
+ // Perform the looping function to find the end
+ return tokenString(stream, state);
+ }
+ if (ch == '(' || ch == ')') {
+ return 'bracket';
+ }
+ if (ch.match(/[0-9]/)) {
+ return 'number';
+ }
+ stream.eatWhile(/[\w-]/);
+ return null;
+ }
+ return {
+ startState: function () {
+ var state = {};
+ state.inDefinition = false;
+ state.inInclude = false;
+ state.continueString = false;
+ state.pending = false;
+ return state;
+ },
+ token: function (stream, state) {
+ if (stream.eatSpace()) return null;
+ return tokenize(stream, state);
+ }
+ };
+});
+
+CodeMirror.defineMIME("text/x-cmake", "cmake");
+
+});
diff --git a/mode/cmake/index.html b/mode/cmake/index.html
new file mode 100644
index 0000000000..ed114fece5
--- /dev/null
+++ b/mode/cmake/index.html
@@ -0,0 +1,129 @@
+
+
+
CodeMirror: CMake mode
+
+
+
+
+
+
+
+
+
+
+
+CMake mode
+
+
+
+ MIME types defined: text/x-cmake.
+
+
diff --git a/mode/commonlisp/commonlisp.js b/mode/commonlisp/commonlisp.js
index 5f50b352de..fb1f99c631 100644
--- a/mode/commonlisp/commonlisp.js
+++ b/mode/commonlisp/commonlisp.js
@@ -111,6 +111,7 @@ CodeMirror.defineMode("commonlisp", function (config) {
return typeof i == "number" ? i : state.ctx.start + 1;
},
+ closeBrackets: {pairs: "()[]{}\"\""},
lineComment: ";;",
blockCommentStart: "#|",
blockCommentEnd: "|#"
diff --git a/mode/css/css.js b/mode/css/css.js
index 34355aaa99..bed1517e8c 100644
--- a/mode/css/css.js
+++ b/mode/css/css.js
@@ -239,6 +239,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == ")") return popContext(state);
if (type == "(") return pushContext(state, stream, "parens");
+ if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") wordAsValue(stream);
return "parens";
};
diff --git a/mode/css/less.html b/mode/css/less.html
index 6ccb721e63..adf7427d30 100644
--- a/mode/css/less.html
+++ b/mode/css/less.html
@@ -146,7 +146,7 @@
});
-
The LESS mode is a sub-mode of the CSS mode (defined in css.js.
+
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/scss.html b/mode/css/scss.html
index 21f20e0d16..f8e4d37368 100644
--- a/mode/css/scss.html
+++ b/mode/css/scss.html
@@ -150,7 +150,7 @@
});
-
The SCSS mode is a sub-mode of the CSS mode (defined in css.js.
+
The SCSS mode is a sub-mode of the CSS mode (defined in css.js).
Parsing/Highlighting Tests: normal, verbose.
diff --git a/mode/groovy/groovy.js b/mode/groovy/groovy.js
index 89b8224cf5..e3a1db869b 100644
--- a/mode/groovy/groovy.js
+++ b/mode/groovy/groovy.js
@@ -217,6 +217,7 @@ CodeMirror.defineMode("groovy", function(config) {
},
electricChars: "{}",
+ closeBrackets: {triples: "'\""},
fold: "brace"
};
});
diff --git a/mode/htmlembedded/htmlembedded.js b/mode/htmlembedded/htmlembedded.js
index e8f7ba803f..464dc57f83 100644
--- a/mode/htmlembedded/htmlembedded.js
+++ b/mode/htmlembedded/htmlembedded.js
@@ -3,84 +3,26 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"));
+ mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"),
+ require("../../addon/mode/multiplex"));
else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod);
+ define(["../../lib/codemirror", "../htmlmixed/htmlmixed",
+ "../../addon/mode/multiplex"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
-
- //config settings
- var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i,
- scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i;
-
- //inner modes
- var scriptingMode, htmlMixedMode;
-
- //tokenizer when in html mode
- function htmlDispatch(stream, state) {
- if (stream.match(scriptStartRegex, false)) {
- state.token=scriptingDispatch;
- return scriptingMode.token(stream, state.scriptState);
- }
- else
- return htmlMixedMode.token(stream, state.htmlState);
- }
-
- //tokenizer when in scripting mode
- function scriptingDispatch(stream, state) {
- if (stream.match(scriptEndRegex, false)) {
- state.token=htmlDispatch;
- return htmlMixedMode.token(stream, state.htmlState);
- }
- else
- return scriptingMode.token(stream, state.scriptState);
- }
-
-
- return {
- startState: function() {
- scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec);
- htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed");
- return {
- token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch,
- htmlState : CodeMirror.startState(htmlMixedMode),
- scriptState : CodeMirror.startState(scriptingMode)
- };
- },
-
- token: function(stream, state) {
- return state.token(stream, state);
- },
-
- indent: function(state, textAfter) {
- if (state.token == htmlDispatch)
- return htmlMixedMode.indent(state.htmlState, textAfter);
- else if (scriptingMode.indent)
- return scriptingMode.indent(state.scriptState, textAfter);
- },
-
- copyState: function(state) {
- return {
- token : state.token,
- htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState),
- scriptState : CodeMirror.copyState(scriptingMode, state.scriptState)
- };
- },
-
- innerMode: function(state) {
- if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode};
- else return {state: state.htmlState, mode: htmlMixedMode};
- }
- };
-}, "htmlmixed");
-
-CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"});
-CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
-CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"});
-CodeMirror.defineMIME("application/x-erb", { name: "htmlembedded", scriptingModeSpec:"ruby"});
-
+ "use strict";
+
+ CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
+ return CodeMirror.multiplexingMode(CodeMirror.getMode(config, "htmlmixed"), {
+ open: parserConfig.open || parserConfig.scriptStartRegex || "<%",
+ close: parserConfig.close || parserConfig.scriptEndRegex || "%>",
+ mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec)
+ });
+ }, "htmlmixed");
+
+ CodeMirror.defineMIME("application/x-ejs", {name: "htmlembedded", scriptingModeSpec:"javascript"});
+ CodeMirror.defineMIME("application/x-aspx", {name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
+ CodeMirror.defineMIME("application/x-jsp", {name: "htmlembedded", scriptingModeSpec:"text/x-java"});
+ CodeMirror.defineMIME("application/x-erb", {name: "htmlembedded", scriptingModeSpec:"ruby"});
});
diff --git a/mode/htmlembedded/index.html b/mode/htmlembedded/index.html
index 93d01c4512..365ef8f366 100644
--- a/mode/htmlembedded/index.html
+++ b/mode/htmlembedded/index.html
@@ -10,6 +10,7 @@
+
diff --git a/mode/index.html b/mode/index.html
index 04167a5a54..7b62a33cf2 100644
--- a/mode/index.html
+++ b/mode/index.html
@@ -34,6 +34,7 @@
Asterisk dialplan
C, C++, C#
Clojure
+
CMake
COBOL
CoffeeScript
Common Lisp
@@ -61,7 +62,6 @@
HAML
Haskell
Haxe
-
HTML embedded scripts
HTML mixed-mode
HTTP
IDL
@@ -85,6 +85,7 @@
Pascal
PEG.js
Perl
+
PGP (ASCII armor)
PHP
Pig Latin
Properties files
@@ -106,7 +107,6 @@
Slim
Smalltalk
Smarty
-
Smarty/HTML mixed
Solr
Soy
Stylus
@@ -119,6 +119,7 @@
Tiki wiki
TOML
Tornado (templating language)
+
troff (for manpages)
Turtle
VB.NET
VBScript
diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js
index 3f05ac46c3..bee88997be 100644
--- a/mode/javascript/javascript.js
+++ b/mode/javascript/javascript.js
@@ -549,6 +549,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") {
+ if (value == "static") {
+ cx.marked = "keyword";
+ return cont(classBody);
+ }
cx.marked = "property";
if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
return cont(functiondef, classBody);
@@ -669,6 +673,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
fold: "brace",
+ closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
diff --git a/mode/kotlin/kotlin.js b/mode/kotlin/kotlin.js
index 73c84f6c4f..1efec85dff 100644
--- a/mode/kotlin/kotlin.js
+++ b/mode/kotlin/kotlin.js
@@ -271,6 +271,7 @@ CodeMirror.defineMode("kotlin", function (config, parserConfig) {
else return ctx.indented + (closing ? 0 : config.indentUnit);
},
+ closeBrackets: {triples: "'\""},
electricChars: "{}"
};
});
diff --git a/mode/meta.js b/mode/meta.js
index e110288afc..d138809179 100644
--- a/mode/meta.js
+++ b/mode/meta.js
@@ -13,12 +13,14 @@
CodeMirror.modeInfo = [
{name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]},
+ {name: "PGP", mimes: ["application/pgp", "application/pgp-keys", "application/pgp-signature"], mode: "asciiarmor", ext: ["pgp"]},
{name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk", file: /^extensions\.conf$/i},
{name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h"]},
{name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"], alias: ["cpp"]},
{name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy"]},
{name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"], alias: ["csharp"]},
{name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj"]},
+ {name: "CMake", mime: "text/x-cmake", mode: "cmake", ext: ["cmake", "cmake.in"], file: /^CMakeLists.txt$/},
{name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript", ext: ["coffee"], alias: ["coffee", "coffee-script"]},
{name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]},
{name: "Cypher", mime: "application/x-cypher-query", mode: "cypher", ext: ["cyp", "cypher"]},
@@ -104,7 +106,6 @@
{name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim", ext: ["slim"]},
{name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]},
{name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]},
- {name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"},
{name: "Solr", mime: "text/x-solr", mode: "solr"},
{name: "Soy", mime: "text/x-soy", mode: "soy", ext: ["soy"], alias: ["closure template"]},
{name: "SPARQL", mime: "application/sparql-query", mode: "sparql", ext: ["rq", "sparql"], alias: ["sparul"]},
@@ -120,6 +121,7 @@
{name: "Tiki wiki", mime: "text/tiki", mode: "tiki"},
{name: "TOML", mime: "text/x-toml", mode: "toml", ext: ["toml"]},
{name: "Tornado", mime: "text/x-tornado", mode: "tornado"},
+ {name: "troff", mime: "troff", mode: "troff", ext: ["1", "2", "3", "4", "5", "6", "7", "8", "9"]},
{name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]},
{name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: ["ts"], alias: ["ts"]},
{name: "VB.NET", mime: "text/x-vb", mode: "vb", ext: ["vb"]},
@@ -128,7 +130,7 @@
{name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]},
{name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd"], alias: ["rss", "wsdl", "xsd"]},
{name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]},
- {name: "YAML", mime: "text/x-yaml", mode: "yaml", ext: ["yaml"], alias: ["yml"]},
+ {name: "YAML", mime: "text/x-yaml", mode: "yaml", ext: ["yaml", "yml"], alias: ["yml"]},
{name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]}
];
// Ensure all modes have a mime property for backwards compatibility
diff --git a/mode/mllike/mllike.js b/mode/mllike/mllike.js
index 04ab1c98ec..bf0b8a674f 100644
--- a/mode/mllike/mllike.js
+++ b/mode/mllike/mllike.js
@@ -85,7 +85,7 @@ CodeMirror.defineMode('mllike', function(_config, parserConfig) {
}
stream.eatWhile(/\w/);
var cur = stream.current();
- return words[cur] || 'variable';
+ return words.hasOwnProperty(cur) ? words[cur] : 'variable';
}
function tokenString(stream, state) {
diff --git a/mode/properties/properties.js b/mode/properties/properties.js
index 0740084209..9da5baf420 100644
--- a/mode/properties/properties.js
+++ b/mode/properties/properties.js
@@ -51,7 +51,7 @@ CodeMirror.defineMode("properties", function() {
state.position = "quote";
return null;
} else if (ch === "\\" && state.position === "quote") {
- if (stream.next() !== "u") { // u = Unicode sequence \u1234
+ if (stream.eol()) { // end of line?
// Multiline value
state.nextMultiline = true;
}
diff --git a/mode/python/python.js b/mode/python/python.js
index 98c0409ae2..979e849822 100644
--- a/mode/python/python.js
+++ b/mode/python/python.js
@@ -339,6 +339,7 @@
return scope.offset;
},
+ closeBrackets: {triples: "'\""},
lineComment: "#",
fold: "indent"
};
diff --git a/mode/sass/sass.js b/mode/sass/sass.js
index 52a6682915..6973ece292 100644
--- a/mode/sass/sass.js
+++ b/mode/sass/sass.js
@@ -232,7 +232,7 @@ CodeMirror.defineMode("sass", function(config) {
if (stream.eatWhile(/[\w-]/)){
if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){
- return "propery";
+ return "property";
}
else if(stream.match(/ *:/,false)){
indent(state);
diff --git a/mode/scheme/scheme.js b/mode/scheme/scheme.js
index 979edc0963..2234645911 100644
--- a/mode/scheme/scheme.js
+++ b/mode/scheme/scheme.js
@@ -239,6 +239,7 @@ CodeMirror.defineMode("scheme", function () {
return state.indentStack.indent;
},
+ closeBrackets: {pairs: "()[]{}\"\""},
lineComment: ";;"
};
});
diff --git a/mode/smarty/index.html b/mode/smarty/index.html
index 8d88c9a302..b19c8f09b5 100644
--- a/mode/smarty/index.html
+++ b/mode/smarty/index.html
@@ -6,6 +6,7 @@
+
@@ -46,17 +47,24 @@
{/function}
{/if}
-
+
Mode for Smarty version 2 or 3, which allows for custom delimiter tags.
-
+
Several configuration parameters are supported:
-
Smarty 2, custom delimiters
-