Bug 960704 - Upgrade CodeMirror to 3.21. r=msucan

This commit is contained in:
Anton Kovalyov 2014-01-17 13:04:00 -08:00
parent c66cff2b44
commit e37bd6d5eb
32 changed files with 1362 additions and 863 deletions

View File

@ -17,11 +17,19 @@ To confirm the functionality run mochitests for the following components:
* styleditor
* netmonitor
The sourceeditor component contains imported CodeMirror tests [3]. Some
tests were commented out because we don't use that functionality within
Firefox (for example Ruby editing mode). The search addon (search.js)
was slightly modified to make search UI localizable. Other than that,
we don't have any Mozilla-specific patches applied to CodeMirror itself.
The sourceeditor component contains imported CodeMirror tests [3].
* Some tests were commented out because we don't use that functionality
within Firefox (for example Ruby editing mode). Be careful when updating
files test/codemirror.html and test/vimemacs.html; they were modified to
co-exist with Mozilla's testing infrastructure.
* In cm_comment_test.js comment out fallbackToBlock and fallbackToLine
tests.
* The search addon (search.js) was slightly modified to make search
UI localizable.
Other than that, we don't have any Mozilla-specific patches applied to
CodeMirror itself.
# Addons
@ -37,6 +45,7 @@ in the LICENSE file:
* codemirror.css
* codemirror.js
* comment.js
* activeline.js
* dialog/dialog.css
* dialog/dialog.js
* keymap/emacs.js
@ -50,6 +59,7 @@ in the LICENSE file:
* css.js
* javascript.js
* clike.js
* htmlmixed.js
* matchbrackets.js
* closebrackets.js
* trailingspace.js
@ -62,6 +72,8 @@ in the LICENSE file:
* test/cm_mode_javascript_test.js
* test/cm_mode_test.css
* test/cm_mode_test.js
* test/cm_vim_test.js
* test/cm_emacs_test.js
* test/cm_test.js
# Footnotes

View File

@ -1,22 +1,21 @@
/* vim:set ts=2 sw=2 sts=2 et tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Because sometimes you need to style the cursor's line.
//
// Adds an option 'styleActiveLine' which, when enabled, gives the
// active line's wrapping <div> the CSS class "CodeMirror-activeline",
// and gives its background <div> the class "CodeMirror-activeline-background".
(function () {
(function() {
"use strict";
const WRAP_CLASS = "CodeMirror-activeline";
const BACK_CLASS = "CodeMirror-activeline-background";
var WRAP_CLASS = "CodeMirror-activeline";
var BACK_CLASS = "CodeMirror-activeline-background";
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("beforeSelectionChange", selectionChange);
clearActiveLine(cm);
delete cm.state.activeLine;
}
@ -29,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);
}
})();

View File

@ -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"]}
});
}());

View File

@ -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");

View File

@ -61,7 +61,7 @@
/* DEFAULT THEME */
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}

View File

@ -1,4 +1,4 @@
// CodeMirror version 3.20
// CodeMirror version 3.21
//
// CodeMirror is the only global var we claim
window.CodeMirror = (function() {
@ -12,10 +12,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);
@ -37,7 +38,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;
@ -63,7 +64,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()};
@ -77,7 +79,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
@ -197,6 +199,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;
@ -246,7 +252,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 : "");
cm.state.disableInput = map.disableInput;
}
function themeChanged(cm) {
@ -452,7 +457,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;
@ -537,6 +542,7 @@ window.CodeMirror = (function() {
}
display.showingFrom = from; display.showingTo = to;
display.gutters.style.height = "";
updateHeightsInViewport(cm);
updateViewOffset(cm);
@ -719,9 +725,9 @@ 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"),
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(
@ -1055,7 +1061,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) {
@ -1115,7 +1121,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);
@ -1291,7 +1297,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;
@ -1483,7 +1489,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;
@ -1501,22 +1507,32 @@ window.CodeMirror = (function() {
var same = 0, l = Math.min(prevInput.length, text.length);
while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
var from = sel.from, to = sel.to;
var inserted = text.slice(same);
if (same < prevInput.length)
from = Pos(from.line, from.ch - (prevInput.length - same));
else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + inserted.length));
var updateInput = cm.curOp.updateInput;
var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
origin: cm.state.pasteIncoming ? "paste" : "+input"};
var changeEvent = {from: from, to: to, text: splitLines(inserted),
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 (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++)
if (inserted.indexOf(electric.charAt(i)) > -1) {
indentLine(cm, sel.head.line, "smart");
break;
}
}
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;
}
@ -1551,7 +1567,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);
@ -1657,21 +1673,22 @@ 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);
// 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);
});
}
@ -1755,6 +1772,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.
@ -1829,7 +1849,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);
@ -1974,7 +1994,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;
@ -2128,7 +2148,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;
@ -2149,10 +2169,6 @@ window.CodeMirror = (function() {
if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
if (this.options.electricChars && this.doc.mode.electricChars &&
this.options.smartIndent && !isReadOnly(this) &&
this.doc.mode.electricChars.indexOf(ch) > -1)
setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
if (handleCharBinding(cm, e, ch)) return;
if (ie && !ie_lt9) cm.display.inputHasSelection = null;
fastPoll(cm);
@ -2201,7 +2217,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);
@ -2223,10 +2239,10 @@ 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 == " " && 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);
@ -2235,7 +2251,7 @@ window.CodeMirror = (function() {
}
}
if (ie && !ie_lt9) prepareSelectAllHack();
if (old_ie && !ie_lt9) prepareSelectAllHack();
if (captureMiddleClick) {
e_stop(e);
var mouseup = function() {
@ -2508,6 +2524,7 @@ window.CodeMirror = (function() {
function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
function cmp(a, b) {return a.line - b.line || a.ch - b.ch;}
function copyPos(x) {return Pos(x.line, x.ch);}
// SELECTION
@ -2652,14 +2669,13 @@ window.CodeMirror = (function() {
if (coords.top + box.top < 0) doScroll = true;
else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
if (doScroll != null && !phantom) {
var hidden = display.cursor.style.display == "none";
if (hidden) {
display.cursor.style.display = "";
display.cursor.style.left = coords.left + "px";
display.cursor.style.top = (coords.top - display.viewOffset) + "px";
}
display.cursor.scrollIntoView(doScroll);
if (hidden) display.cursor.style.display = "none";
var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
(coords.top - display.viewOffset) + "px; height: " +
(coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
coords.left + "px; width: 2px;");
cm.display.lineSpace.appendChild(scrollNode);
scrollNode.scrollIntoView(doScroll);
cm.display.lineSpace.removeChild(scrollNode);
}
}
@ -2742,7 +2758,10 @@ window.CodeMirror = (function() {
var tabSize = cm.options.tabSize;
var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
var curSpaceString = line.text.match(/^\s*/)[0], indentation;
if (how == "smart") {
if (!aggressive && !/\S/.test(line.text)) {
indentation = 0;
how = "not";
} else if (how == "smart") {
indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
if (indentation == Pass) {
if (!aggressive) return;
@ -2924,7 +2943,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);
}),
@ -2969,11 +2988,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) {
@ -3120,7 +3159,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;
@ -3162,14 +3204,18 @@ window.CodeMirror = (function() {
},
moveV: operation(null, function(dir, unit) {
var sel = this.doc.sel;
var pos = cursorCoords(this, sel.head, "div");
if (sel.goalColumn != null) pos.left = sel.goalColumn;
var target = findPosV(this, pos, dir, unit);
if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
var sel = this.doc.sel, target, goal;
if (sel.shift || sel.extend || posEq(sel.from, sel.to)) {
var pos = cursorCoords(this, sel.head, "div");
if (sel.goalColumn != null) pos.left = sel.goalColumn;
target = findPosV(this, pos, dir, unit);
if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
goal = pos.left;
} else {
target = dir < 0 ? sel.from : sel.to;
}
extendSelection(this.doc, target, target, dir);
sel.goalColumn = pos.left;
if (goal != null) sel.goalColumn = goal;
}),
toggleOverwrite: function(value) {
@ -3279,7 +3325,7 @@ window.CodeMirror = (function() {
option("indentWithTabs", false);
option("smartIndent", true);
option("tabSize", 4, function(cm) {
loadMode(cm);
resetModeState(cm);
clearCaches(cm);
regChange(cm);
}, true);
@ -3332,6 +3378,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);
@ -3339,12 +3386,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;
@ -3401,6 +3449,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;
};
@ -3431,9 +3482,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
@ -3538,7 +3593,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");
@ -3711,11 +3768,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)
@ -3748,9 +3806,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;};
@ -3766,7 +3827,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;
@ -3818,7 +3884,7 @@ window.CodeMirror = (function() {
if (withOp) endOperation(cm);
};
TextMarker.prototype.find = function() {
TextMarker.prototype.find = function(bothSides) {
var from, to;
for (var i = 0; i < this.lines.length; ++i) {
var line = this.lines[i];
@ -3829,7 +3895,7 @@ window.CodeMirror = (function() {
if (span.to != null) to = Pos(found, span.to);
}
}
if (this.type == "bookmark") return from;
if (this.type == "bookmark" && !bothSides) return from;
return from && {from: from, to: to};
};
@ -3866,39 +3932,40 @@ window.CodeMirror = (function() {
}
};
var nextMarkerId = 0;
function markText(doc, from, to, options, type) {
if (options && options.shared) return markTextShared(doc, from, to, options, type);
if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
var marker = new TextMarker(doc, type);
if (posLess(to, from) || posEq(from, to) && type == "range" &&
!(options.inclusiveLeft && options.inclusiveRight))
return marker;
if (options) copyObj(options, marker);
if (posLess(to, from) || posEq(from, to) && marker.clearWhenEmpty !== false)
return marker;
if (marker.replacedWith) {
marker.collapsed = true;
marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
}
if (marker.collapsed) sawCollapsedSpans = true;
if (marker.collapsed) {
if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
throw new Error("Inserting collapsed marker partially overlapping an existing one");
sawCollapsedSpans = true;
}
if (marker.addToHistory)
addToHistory(doc, {from: from, to: to, origin: "markText"},
{head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
var curLine = from.line, cm = doc.cm, updateMaxLine;
doc.iter(curLine, to.line + 1, function(line) {
if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
updateMaxLine = true;
var span = {from: null, to: null, marker: marker};
size += line.text.length;
if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
if (marker.collapsed) {
if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
else updateLineHeight(line, 0);
}
if (curLine == from.line) span.from = from.ch;
if (curLine == to.line) span.to = to.ch;
if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
addMarkedSpan(line, span);
++curLine;
});
@ -3914,9 +3981,7 @@ window.CodeMirror = (function() {
doc.clearHistory();
}
if (marker.collapsed) {
if (collapsedAtStart != collapsedAtEnd)
throw new Error("Inserting collapsed marker overlapping an existing one");
marker.size = size;
marker.id = ++nextMarkerId;
marker.atomic = true;
}
if (cm) {
@ -3989,9 +4054,7 @@ window.CodeMirror = (function() {
if (old) for (var i = 0, nw; i < old.length; ++i) {
var span = old[i], marker = span.marker;
var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
if (startsBefore ||
(marker.inclusiveLeft && marker.inclusiveRight || marker.type == "bookmark") &&
span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
(nw || (nw = [])).push({from: span.from,
to: endsAfter ? null : span.to,
@ -4005,7 +4068,7 @@ window.CodeMirror = (function() {
if (old) for (var i = 0, nw; i < old.length; ++i) {
var span = old[i], marker = span.marker;
var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
(nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
to: span.to == null ? null : span.to - endCh,
@ -4055,13 +4118,9 @@ window.CodeMirror = (function() {
}
}
}
if (sameLine && first) {
// Make sure we didn't create any zero-length spans
for (var i = 0; i < first.length; ++i)
if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark")
first.splice(i--, 1);
if (!first.length) first = null;
}
// Make sure we didn't create any zero-length spans
if (first) first = clearEmptySpans(first);
if (last && last != first) last = clearEmptySpans(last);
var newMarkers = [first];
if (!sameLine) {
@ -4078,6 +4137,16 @@ window.CodeMirror = (function() {
return newMarkers;
}
function clearEmptySpans(spans) {
for (var i = 0; i < spans.length; ++i) {
var span = spans[i];
if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
spans.splice(i--, 1);
}
if (!spans.length) return null;
return spans;
}
function mergeOldSpans(doc, change) {
var old = getOldSpans(doc, change);
var stretched = stretchSpansOverChange(doc, change);
@ -4128,20 +4197,48 @@ window.CodeMirror = (function() {
return parts;
}
function collapsedSpanAt(line, ch) {
function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
function compareCollapsedMarkers(a, b) {
var lenDiff = a.lines.length - b.lines.length;
if (lenDiff != 0) return lenDiff;
var aPos = a.find(), bPos = b.find();
var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
if (fromCmp) return -fromCmp;
var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
if (toCmp) return toCmp;
return b.id - a.id;
}
function collapsedSpanAtSide(line, start) {
var sps = sawCollapsedSpans && line.markedSpans, found;
if (sps) for (var sp, i = 0; i < sps.length; ++i) {
sp = sps[i];
if (!sp.marker.collapsed) continue;
if ((sp.from == null || sp.from < ch) &&
(sp.to == null || sp.to > ch) &&
(!found || found.width < sp.marker.width))
if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
(!found || compareCollapsedMarkers(found, sp.marker) < 0))
found = sp.marker;
}
return found;
}
function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }
function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
var line = getLine(doc, lineNo);
var sps = sawCollapsedSpans && line.markedSpans;
if (sps) for (var i = 0; i < sps.length; ++i) {
var sp = sps[i];
if (!sp.marker.collapsed) continue;
var found = sp.marker.find(true);
var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
if (fromCmp <= 0 && (cmp(found.to, from) || extraRight(sp.marker) - extraLeft(marker)) > 0 ||
fromCmp >= 0 && (cmp(found.from, to) || extraLeft(sp.marker) - extraRight(marker)) < 0)
return true;
}
}
function visualLine(doc, line) {
var merged;
@ -4171,6 +4268,7 @@ window.CodeMirror = (function() {
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 &&
(sp.to == null || sp.to != span.from) &&
(sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
lineIsHiddenInner(doc, line, sp)) return true;
}
@ -4300,6 +4398,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;
@ -4371,7 +4473,7 @@ window.CodeMirror = (function() {
}
}
var styleToClassCache = {};
var styleToClassCache = {}, styleToClassCacheWithMode = {};
function interpretTokenStyle(style, builder) {
if (!style) return null;
for (;;) {
@ -4384,8 +4486,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) {
@ -4402,7 +4505,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) {
@ -4421,7 +4524,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];
@ -4488,13 +4591,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,
@ -4503,7 +4605,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;
@ -4571,7 +4673,7 @@ window.CodeMirror = (function() {
if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
if (m.title && !title) title = m.title;
if (m.collapsed && (!collapsed || collapsed.marker.size < m.size))
if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
collapsed = sp;
} else if (sp.from > pos && nextChange > sp.from) {
nextChange = sp.from;
@ -4904,10 +5006,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) {
@ -4929,7 +5032,8 @@ window.CodeMirror = (function() {
},
setBookmark: function(pos, options) {
var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
insertLeft: options && options.insertLeft};
insertLeft: options && options.insertLeft,
clearWhenEmpty: false};
pos = clipPos(this, pos);
return markText(this, pos, pos, realOpts, "bookmark");
},
@ -5210,10 +5314,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;
@ -5509,7 +5613,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\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); }
// DOM UTILITIES
@ -5582,7 +5687,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;
@ -5650,14 +5755,14 @@ window.CodeMirror = (function() {
var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
CodeMirror.keyNames = keyNames;
(function() {
// Number keys
for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
// Alphabetic keys
for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
// Function keys
@ -5714,29 +5819,29 @@ 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;
}
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;
}
@ -5771,7 +5876,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;
}
@ -5863,7 +5968,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;
}
@ -5888,7 +5993,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;
@ -5938,7 +6043,7 @@ window.CodeMirror = (function() {
// THE END
CodeMirror.version = "3.20.0";
CodeMirror.version = "3.21.0";
return CodeMirror;
})();

View File

@ -96,8 +96,9 @@
for (var i = start; i <= end; ++i) {
var line = self.getLine(i);
var found = line.indexOf(lineString);
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
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() {
@ -124,7 +125,10 @@
endLine = self.getLine(--end);
close = endLine.lastIndexOf(endString);
}
if (open == -1 || close == -1) return false;
if (open == -1 || close == -1 ||
!/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
!/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
return false;
self.operation(function() {
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),

View File

@ -3,87 +3,80 @@ 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;
fontProperties = parserConfig.fontProperties || {},
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]) {
// result[0] is style and result[1] is type
var result = hooks[ch](stream, state);
if (tokenHooks[ch]) {
var result = tokenHooks[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 {
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);
}
}
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 (ch == quote && !escaped) {
if (quote == ")") stream.backUp(1);
break;
}
escaped = !escaped && ch == "\\";
}
if (!escaped) {
if (nonInclusive) stream.backUp(1);
state.tokenize = tokenBase;
}
if (ch == quote || !escaped && quote != ")") state.tokenize = null;
return ret("string", "string");
};
}
@ -91,218 +84,238 @@ 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 = 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, stream, type) {
state.context = new Context(type, stream.indentation() + 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
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) {
if (type == "{") {
return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) {
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") {
override = "builtin";
} else if (type == "word") {
override = "tag";
} else if (type == "variable-definition") {
return "maybeprop";
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
} else if (type == ":") {
return "pseudo";
} else if (allowNested && type == "(") {
return pushContext(state, stream, "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 = stream.match(/^\s*:/, false) ? "property" : "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, stream, "prop");
return pass(type, stream, state);
};
states.prop = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
if (type == "}" || type == "{") return popAndPass(type, stream, state);
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, stream, "interpolation");
}
return "prop";
};
states.propBlock = function(type, _stream, state) {
if (type == "}") return popContext(state);
if (type == "word") { override = "property"; return "maybeprop"; }
return state.context.type;
};
states.parens = function(type, stream, state) {
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == ")") return popContext(state);
return "parens";
};
states.pseudo = function(type, stream, state) {
if (type == "word") {
override = "variable-3";
return state.context.type;
}
return pass(type, stream, state);
};
states.media = function(type, stream, state) {
if (type == "(") return pushContext(state, stream, "media_parens");
if (type == "}") return popAndPass(type, stream, state);
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
if (type == "word") {
var word = stream.current().toLowerCase();
if (word == "only" || word == "not" || word == "and")
override = "keyword";
else if (mediaTypes.hasOwnProperty(word))
override = "attribute";
else if (mediaFeatures.hasOwnProperty(word))
override = "property";
else
override = "error";
}
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);
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);
if (type == "word") override = "tag";
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";
};
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) {
// 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.
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") {
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";
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];
}
// 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, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
if (cx.prev &&
(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;
cx = cx.prev;
}
return indent;
},
electricChars: "}",
@ -321,12 +334,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return keys;
}
var atMediaTypes = keySet([
var mediaTypes_ = [
"all", "aural", "braille", "handheld", "print", "projection", "screen",
"tty", "tv", "embossed"
]);
], mediaTypes = keySet(mediaTypes_);
var atMediaFeatures = 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",
@ -335,9 +348,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",
@ -425,9 +438,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",
@ -454,9 +467,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",
@ -539,7 +552,15 @@ 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 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);
function tokenCComment(stream, state) {
var maybeEnd = false, ch;
@ -553,67 +574,47 @@ 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: {
fontProperties: fontProperties,
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,
fontProperties: fontProperties,
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();
@ -625,15 +626,58 @@ 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"
name: "css",
helperType: "scss"
});
CodeMirror.defineMIME("text/x-less", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
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(/^(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"];
return ["variable-2", "variable"];
},
"&": function() {
return ["atom", "atom"];
}
},
name: "css",
helperType: "less"
});
})();

View File

@ -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) {

View File

@ -1,4 +1,6 @@
CodeMirror.registerHelper("fold", "comment", function(cm, start) {
CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
return mode.blockCommentStart && mode.blockCommentEnd;
}, function(cm, start) {
var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
if (!startToken || !endToken) return;
var line = start.line, lineText = cm.getLine(line);

View File

@ -3,8 +3,7 @@
function doFold(cm, pos, options, force) {
var finder = options && (options.call ? options : options.rangeFinder);
if (!finder) finder = cm.getHelper(pos, "fold");
if (!finder) return;
if (!finder) finder = CodeMirror.fold.auto;
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
var minSize = options && options.minFoldSize || 0;
@ -63,6 +62,10 @@
doFold(this, pos, options, force);
});
CodeMirror.commands.fold = function(cm) {
cm.foldCode(cm.getCursor());
};
CodeMirror.registerHelper("fold", "combine", function() {
var funcs = Array.prototype.slice.call(arguments, 0);
return function(cm, start) {
@ -72,4 +75,12 @@
}
};
});
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
var helpers = cm.getHelpers(start, "fold");
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, start);
if (cur) return cur;
}
});
})();

View File

@ -62,7 +62,7 @@
if (isFolded(cm, cur)) {
mark = marker(opts.indicatorFolded);
} else {
var pos = Pos(cur, 0), func = opts.rangeFinder || cm.getHelper(pos, "fold");
var pos = Pos(cur, 0), func = opts.rangeFinder || CodeMirror.fold.auto;
var range = func && func(cm, pos);
if (range && range.from.line + 1 < range.to.line)
mark = marker(opts.indicatorOpen);

View File

@ -164,4 +164,10 @@
if (close) return {open: open, close: close};
}
};
// Used by addon/edit/closetag.js
CodeMirror.scanForClosingTag = function(cm, pos, name, end) {
var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);
return !!findMatchingClose(iter, name);
};
})();

View File

@ -93,8 +93,6 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
return CodeMirror.Pass;
},
electricChars: "/{}:",
innerMode: function(state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}

View File

@ -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"),
@ -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
@ -83,7 +85,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");
@ -99,12 +101,12 @@ 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 {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
@ -114,7 +116,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];
@ -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");
};
}
@ -304,7 +310,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);
@ -327,7 +333,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);
}
@ -337,8 +343,8 @@ 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(commasep(objprop, "}"), maybeop);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
return cont();
}
function maybeexpression(type) {
@ -365,12 +371,11 @@ 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);
}
function quasi(value) {
if (!value) debugger;
if (value.slice(value.length - 2) != "${") return cont();
return cont(expression, continueQuasi);
}
@ -418,7 +423,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 +435,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 +458,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)) {
@ -470,7 +479,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);
@ -493,7 +502,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 +515,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,17 +531,21 @@ 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();
}
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, "]", false));
return pass(commasep(expressionNoComma, "]", false));
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(expressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]"));
}
function comprehension(type) {
if (type == "for") return cont(forspec, comprehension);

View File

@ -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 + ": <input type=\"text\" style=\"width: 10em\"/>", f, {bottom: true});
@ -234,6 +239,11 @@
}
}
function quit(cm) {
cm.execCommand("clearSearch");
clearMark(cm);
}
// Actual keymap
var keyMap = CodeMirror.keyMap.emacs = {
@ -249,6 +259,7 @@
}),
"Alt-W": function(cm) {
addToRing(cm.getSelection());
clearMark(cm);
},
"Ctrl-Y": function(cm) {
var start = cm.getCursor();
@ -334,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",

View File

@ -84,6 +84,7 @@
{ keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
{ keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
{ keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
{ keys: ['<CR>'], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' },
// Motions
{ keys: ['H'], type: 'motion',
motion: 'moveToTopLine',
@ -247,6 +248,12 @@
actionArgs: { forward: true }},
{ keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
actionArgs: { forward: false }},
{ keys: ['<C-e>'], type: 'action',
action: 'scroll',
actionArgs: { forward: true, linewise: true }},
{ keys: ['<C-y>'], 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,
@ -324,12 +331,16 @@
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;
}
});
@ -342,6 +353,18 @@
head.ch--;
}
}
function getOnPasteFn(cm) {
var vim = cm.state.vim;
if (!vim.onPasteFn) {
vim.onPasteFn = function() {
if (!vim.insertMode) {
cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
actions.enterInsertMode(cm, {}, vim);
}
};
}
return vim.onPasteFn;
}
var numberRegex = /[\d]/;
var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
@ -549,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) {
@ -833,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);
@ -1636,6 +1665,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');
@ -1691,6 +1757,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);
@ -2896,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_();
@ -2955,6 +3024,7 @@
exCommands[commandName](cm, params);
} catch(e) {
showConfirm(cm, e);
throw e;
}
},
parseInput_: function(cm, inputStream, result) {
@ -3037,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
@ -3058,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);
}
}
}
@ -3090,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) {
@ -3098,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',
@ -3115,7 +3192,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 +3201,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 +3332,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 +3475,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();
@ -3445,7 +3527,6 @@
var cmToVimKeymap = {
'nofallthrough': true,
'disableInput': true,
'style': 'fat-cursor'
};
function bindKeys(keys, modifier) {
@ -3492,6 +3573,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"});
}

View File

@ -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(); });

View File

@ -74,14 +74,6 @@ selector in floating-scrollbar-light.css across all platforms. */
font: message-box;
}
.CodeMirror-code > div > div:first-child {
top: 50%;
}
.CodeMirror-gutter-elt {
transform: translate(0,-50%);
}
.cm-trailingspace {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==");
opacity: 0.75;

View File

@ -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,13 +39,16 @@
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, 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);
@ -45,29 +58,16 @@
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
}
var queryDialog;
var queryDialog =
'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
function doSearch(cm, rev) {
if (!queryDialog) {
let doc = cm.getWrapperElement().ownerDocument;
let inp = doc.createElement("input");
let txt = doc.createTextNode(cm.l10n("findCmd.promptMessage"));
inp.type = "text";
inp.style.width = "10em";
inp.style.MozMarginStart = "1em";
queryDialog = doc.createElement("div");
queryDialog.appendChild(txt);
queryDialog.appendChild(inp);
}
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
dialog(cm, queryDialog, cm.l10n('findCmd.promptMessage'), function(query) {
dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
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();
@ -98,10 +98,10 @@
var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
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();) {

View File

@ -47,6 +47,7 @@
match: match};
};
} else { // String query
var origQuery = query;
if (caseFold) query = query.toLowerCase();
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
var target = query.split("\n");
@ -58,33 +59,45 @@
this.matches = function() {};
} else {
this.matches = function(reverse, pos) {
var line = fold(doc.getLine(pos.line)), len = query.length, match;
if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
: (match = line.indexOf(query, pos.ch)) != -1)
return {from: Pos(pos.line, match),
to: Pos(pos.line, match + len)};
if (reverse) {
var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
var match = line.lastIndexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match);
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
} else {
var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
var match = line.indexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match) + pos.ch;
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
}
};
}
} else {
var origTarget = origQuery.split("\n");
this.matches = function(reverse, pos) {
var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln));
var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
if (reverse ? offsetA > pos.ch || offsetA != match.length
: offsetA < pos.ch || offsetA != line.length - match.length)
return;
for (;;) {
if (reverse ? !ln : ln == doc.lineCount() - 1) return;
line = fold(doc.getLine(ln += reverse ? -1 : 1));
match = target[reverse ? --idx : ++idx];
if (idx > 0 && idx < target.length - 1) {
if (line != match) return;
else continue;
}
var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
return;
var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
return {from: reverse ? end : start, to: reverse ? start : end};
var last = target.length - 1;
if (reverse) {
if (pos.line - (target.length - 1) < doc.firstLine()) return;
if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
var to = Pos(pos.line, origTarget[last].length);
for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
if (target[i] != fold(doc.getLine(ln))) return;
var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
return {from: Pos(ln, cut), to: to};
} else {
if (pos.line + (target.length - 1) > doc.lastLine()) return;
var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
var from = Pos(pos.line, cut);
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
if (target[i] != fold(doc.getLine(ln))) return;
if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return;
return {from: from, to: Pos(ln, origTarget[last].length)};
}
};
}
@ -106,7 +119,6 @@
for (;;) {
if (this.pos = this.matches(reverse, pos)) {
if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
this.atOccurrence = true;
return this.pos.match || true;
}
@ -134,6 +146,18 @@
}
};
// Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos) {
if (orig.length == folded.length) return pos;
for (var pos1 = Math.min(pos, orig.length);;) {
var len1 = orig.slice(0, pos1).toLowerCase().length;
if (len1 < pos) ++pos1;
else if (len1 > pos) --pos1;
else return pos1;
}
}
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold);
});

View File

@ -1,11 +1,8 @@
(function() {
"use strict";
CodeMirror.defineOption("showTrailingSpace", true, function(cm, val, prev) {
if (prev == CodeMirror.Init) prev = false;
if (prev && !val)
CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
if (prev == CodeMirror.Init) prev = false;
if (prev && !val)
cm.removeOverlay("trailingspace");
else if (!prev && val)
else if (!prev && val)
cm.addOverlay({
token: function(stream) {
for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
@ -15,5 +12,4 @@
},
name: "trailingspace"
});
});
})();
});

View File

@ -45,7 +45,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
var tagName, type;
var tagName, type, setStyle;
function inText(stream, state) {
function chain(parser) {
@ -110,6 +110,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
return null;
} else if (ch == "<") {
state.tokenize = inText;
state.state = baseState;
state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " error" : "error";
} else if (/[\'\"]/.test(ch)) {
@ -169,139 +171,124 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
};
}
var curState, curStream, setStyle;
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
function Context(state, tagName, startOfLine) {
this.prev = state.context;
this.tagName = tagName;
this.indent = state.indented;
this.startOfLine = startOfLine;
if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
this.noIndent = true;
}
function cont() {
pass.apply(null, arguments);
return true;
function popContext(state) {
if (state.context) state.context = state.context.prev;
}
function maybePopContext(state, nextTagName) {
var parentTagName;
while (true) {
if (!state.context) {
return;
}
parentTagName = state.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext(state);
}
}
function pushContext(tagName, startOfLine) {
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
curState.context = {
prev: curState.context,
tagName: tagName,
indent: curState.indented,
startOfLine: startOfLine,
noIndent: noIndent
};
}
function popContext() {
if (curState.context) curState.context = curState.context.prev;
}
function element(type) {
function baseState(type, stream, state) {
if (type == "openTag") {
curState.tagName = tagName;
curState.tagStart = curStream.column();
return cont(attributes, endtag(curState.startOfLine));
state.tagName = tagName;
state.tagStart = stream.column();
return attrState;
} else if (type == "closeTag") {
var err = false;
if (curState.context) {
if (curState.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
popContext();
}
err = !curState.context || curState.context.tagName != tagName;
if (state.context) {
if (state.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName.toLowerCase()))
popContext(state);
err = !state.context || state.context.tagName != tagName;
}
} else {
err = true;
}
if (err) setStyle = "error";
return cont(endclosetag(err));
return err ? closeStateErr : closeState;
} else {
return baseState;
}
return cont();
}
function endtag(startOfLine) {
return function(type) {
var tagName = curState.tagName;
curState.tagName = curState.tagStart = null;
if (type == "selfcloseTag" ||
(type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
maybePopContext(tagName.toLowerCase());
return cont();
}
if (type == "endTag") {
maybePopContext(tagName.toLowerCase());
pushContext(tagName, startOfLine);
return cont();
}
return cont();
};
}
function endclosetag(err) {
return function(type) {
if (err) setStyle = "error";
if (type == "endTag") { popContext(); return cont(); }
function closeState(type, _stream, state) {
if (type != "endTag") {
setStyle = "error";
return cont(arguments.callee);
};
}
function maybePopContext(nextTagName) {
var parentTagName;
while (true) {
if (!curState.context) {
return;
}
parentTagName = curState.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext();
return closeState;
}
popContext(state);
return baseState;
}
function closeStateErr(type, stream, state) {
setStyle = "error";
return closeState(type, stream, state);
}
function attributes(type) {
if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
if (type == "endTag" || type == "selfcloseTag") return pass();
function attrState(type, _stream, state) {
if (type == "word") {
setStyle = "attribute";
return attrEqState;
} else if (type == "endTag" || type == "selfcloseTag") {
var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null;
if (type == "selfcloseTag" ||
Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase())) {
maybePopContext(state, tagName.toLowerCase());
} else {
maybePopContext(state, tagName.toLowerCase());
state.context = new Context(state, tagName, tagStart == state.indented);
}
return baseState;
}
setStyle = "error";
return cont(attributes);
return attrState;
}
function attribute(type) {
if (type == "equals") return cont(attvalue, attributes);
function attrEqState(type, stream, state) {
if (type == "equals") return attrValueState;
if (!Kludges.allowMissing) setStyle = "error";
else if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
return attrState(type, stream, state);
}
function attvalue(type) {
if (type == "string") return cont(attvaluemaybe);
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
function attrValueState(type, stream, state) {
if (type == "string") return attrContinuedState;
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;}
setStyle = "error";
return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
return attrState(type, stream, state);
}
function attvaluemaybe(type) {
if (type == "string") return cont(attvaluemaybe);
else return pass();
function attrContinuedState(type, stream, state) {
if (type == "string") return attrContinuedState;
return attrState(type, stream, state);
}
return {
startState: function() {
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, tagStart: null, context: null};
return {tokenize: inText,
state: baseState,
indented: 0,
tagName: null, tagStart: null,
context: null};
},
token: function(stream, state) {
if (!state.tagName && stream.sol()) {
state.startOfLine = true;
if (!state.tagName && stream.sol())
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
setStyle = type = tagName = null;
if (stream.eatSpace()) return null;
tagName = type = null;
var style = state.tokenize(stream, state);
state.type = type;
if ((style || type) && style != "comment") {
curState = state; curStream = stream;
while (true) {
var comb = state.cc.pop() || element;
if (comb(type || style)) break;
}
setStyle = null;
state.state = state.state(type || style, stream, state);
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
}
state.startOfLine = false;
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
return style;
},
@ -311,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) {

View File

@ -29,11 +29,11 @@ namespace = "comment_";
}, simpleProg, simpleProg);
// test("fallbackToBlock", "css", function(cm) {
// cm.lineComment(Pos(0, 0), Pos(2, 1));
// cm.lineComment(Pos(0, 0), Pos(2, 1));
// }, "html {\n border: none;\n}", "/* html {\n border: none;\n} */");
// test("fallbackToLine", "ruby", function(cm) {
// cm.blockComment(Pos(0, 0), Pos(1));
// cm.blockComment(Pos(0, 0), Pos(1));
// }, "def blah()\n return hah\n", "# def blah()\n# return hah\n");
test("commentRange", "javascript", function(cm) {
@ -48,4 +48,16 @@ namespace = "comment_";
cm.setCursor(1);
cm.execCommand("toggleComment");
}, "a;\n\nb;", "a;\n// \nb;");
test("dontMessWithStrings", "javascript", function(cm) {
cm.execCommand("toggleComment");
}, "console.log(\"/*string*/\");", "// console.log(\"/*string*/\");");
test("dontMessWithStrings2", "javascript", function(cm) {
cm.execCommand("toggleComment");
}, "console.log(\"// string\");", "// console.log(\"// string\");");
test("dontMessWithStrings3", "javascript", function(cm) {
cm.execCommand("toggleComment");
}, "// console.log(\"// string\");", "console.log(\"// string\");");
})();

View File

@ -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 {
@ -84,7 +68,7 @@ function runTests(callback) {
if (expFail) callback("expected", test.name);
else if (e instanceof Failure) callback("fail", test.name, e.message);
else {
var pos = /\bat .*?([^\/:]+):(\d+):/.exec(e.stack);
var pos = /(?:\bat |@).*?([^\/:]+):(\d+)/.exec(e.stack);
callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : ""));
}
}
@ -127,13 +111,21 @@ 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;
}
function parseTestFilter(s) {
if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i");
else return new RegExp(s, "i");
}

View File

@ -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(); };

View File

@ -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,10 +63,51 @@
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] [operator =] [number 10]",
"[variable x] [operator +=] [variable y] [operator +]",
" [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] [operator =] [number 0];",
" [variable i] [operator <] [number 100];",
" [variable i][operator ++])",
" [variable doSomething]([variable i]);",
"[keyword debugger];");
MT("indent_c_style",
"[keyword function] [variable foo]()",
"{",
" [keyword debugger];",
"}");
MT("multilinestring",
"[keyword var] [variable x] [operator =] [string 'foo\\]",
"[string bar'];");
MT("scary_regexp",
"[string-2 /foo[[/]]bar/];");
})();

View File

@ -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('&', '&amp;').replace('<', '&lt;');
}
function compare(text, expected, mode) {
var expectedOutput = [];
for (var i = 0; i < expected.length; i += 2) {
@ -82,61 +79,49 @@
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 += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + text.replace('&', '&amp;').replace('<', '&lt;') + '</pre>';
s += '<div class="cm-s-default">';
s += prettyPrintOutputTable(observedOutput);
s += '</div>';
s += '</div>';
return s;
} else {
s += '<div class="mt-test ' + passStyle + '">';
s += '<pre>' + text.replace('&', '&amp;').replace('<', '&lt;') + '</pre>';
var s = "";
var diff = highlightOutputsDifferent(expectedOutput, observedOutput);
if (diff != null) {
s += '<div class="mt-test mt-fail">';
s += '<pre>' + esc(text) + '</pre>';
s += '<div class="cm-s-default">';
s += 'expected:';
s += prettyPrintOutputTable(expectedOutput);
s += prettyPrintOutputTable(expectedOutput, diff);
s += 'observed:';
s += prettyPrintOutputTable(observedOutput);
s += prettyPrintOutputTable(observedOutput, diff);
s += '</div>';
s += '</div>';
throw s;
}
if (observedOutput.indentFailures) {
for (var i = 0; i < observedOutput.indentFailures.length; i++)
s += "<div class='mt-test mt-fail'>" + esc(observedOutput.indentFailures[i]) + "</div>";
}
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(' ');
stream.start = stream.pos;
var compare = mode.token(stream, state), substr = stream.current();
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;
} else if (substr) {
@ -154,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 (&middot;).
*
* @param output array of [style, token] pairs
*
* @return html string
*/
function prettyPrintOutputTable(output) {
function prettyPrintOutputTable(output, diffAt) {
var s = '<table class="mt-output">';
s += '<tr>';
for (var i = 0; i < output.length; i += 2) {
var style = output[i], val = output[i+1];
s +=
'<td class="mt-token">' +
'<span class="cm-' + String(style).replace(/ +/g, " cm-") + '">' +
val.replace(/ /g,'\xb7').replace('&', '&amp;').replace('<', '&lt;') +
'<td class="mt-token"' + (i == diffAt * 2 ? " style='background: pink'" : "") + '>' +
'<span class="cm-' + esc(String(style)) + '">' +
esc(val.replace(/ /g,'\xb7')) +
'</span>' +
'</td>';
}

View File

@ -436,6 +436,36 @@ testCM("markTextStayGone", function(cm) {
eq(m1.find(), null);
}, {value: "hello"});
testCM("markTextAllowEmpty", function(cm) {
var m1 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false});
is(m1.find());
cm.replaceRange("x", Pos(0, 0));
is(m1.find());
cm.replaceRange("y", Pos(0, 2));
is(m1.find());
cm.replaceRange("z", Pos(0, 3), Pos(0, 4));
is(!m1.find());
var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {clearWhenEmpty: false,
inclusiveLeft: true,
inclusiveRight: true});
cm.replaceRange("q", Pos(0, 1), Pos(0, 2));
is(m2.find());
cm.replaceRange("", Pos(0, 0), Pos(0, 3));
is(!m2.find());
var m3 = cm.markText(Pos(0, 1), Pos(0, 1), {clearWhenEmpty: false});
cm.replaceRange("a", Pos(0, 3));
is(m3.find());
cm.replaceRange("b", Pos(0, 1));
is(!m3.find());
}, {value: "abcde"});
testCM("markTextStacked", function(cm) {
var m1 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
var m2 = cm.markText(Pos(0, 0), Pos(0, 0), {clearWhenEmpty: false});
cm.replaceRange("B", Pos(0, 1));
is(m1.find() && m2.find());
}, {value: "A"});
testCM("undoPreservesNewMarks", function(cm) {
cm.markText(Pos(0, 3), Pos(0, 4));
cm.markText(Pos(1, 1), Pos(1, 3));
@ -712,8 +742,8 @@ testCM("collapsedRangeCoordsChar", function(cm) {
var m1 = cm.markText(Pos(0, 0), Pos(2, 0), opts);
eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
m1.clear();
var m1 = cm.markText(Pos(0, 0), Pos(1, 1), opts);
var m2 = cm.markText(Pos(1, 1), Pos(2, 0), opts);
var m1 = cm.markText(Pos(0, 0), Pos(1, 1), {collapsed: true, inclusiveLeft: true});
var m2 = cm.markText(Pos(1, 1), Pos(2, 0), {collapsed: true, inclusiveRight: true});
eqPos(cm.coordsChar(pos_1_3), Pos(3, 3));
m1.clear(); m2.clear();
var m1 = cm.markText(Pos(0, 0), Pos(1, 6), opts);
@ -841,6 +871,23 @@ testCM("badNestedFold", function(cm) {
is(/overlap/i.test(caught.message), "wrong error");
});
testCM("nestedFoldOnSide", function(cm) {
var m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true, inclusiveRight: true});
var m2 = cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true});
cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true}).clear();
try { cm.markText(Pos(0, 1), Pos(0, 2), {collapsed: true, inclusiveLeft: true}); }
catch(e) { var caught = e; }
is(caught && /overlap/i.test(caught.message));
var m3 = cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true});
var m4 = cm.markText(Pos(2, 0), Pos(2, 1), {collapse: true, inclusiveRight: true});
m1.clear(); m4.clear();
m1 = cm.markText(Pos(0, 1), Pos(2, 1), {collapsed: true});
cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true}).clear();
try { cm.markText(Pos(2, 0), Pos(2, 1), {collapsed: true, inclusiveRight: true}); }
catch(e) { var caught = e; }
is(caught && /overlap/i.test(caught.message));
}, {value: "ab\ncd\ef"});
testCM("wrappingInlineWidget", function(cm) {
cm.setSize("11em");
var w = document.createElement("span");
@ -973,6 +1020,24 @@ testCM("moveVstuck", function(cm) {
eqPos(cm.getCursor(), Pos(0, 26));
}, {lineWrapping: true}, ie_lt8 || opera_lt10);
testCM("collapseOnMove", function(cm) {
cm.setSelection(Pos(0, 1), Pos(2, 4));
cm.execCommand("goLineUp");
is(!cm.somethingSelected());
eqPos(cm.getCursor(), Pos(0, 1));
cm.setSelection(Pos(0, 1), Pos(2, 4));
cm.execCommand("goPageDown");
is(!cm.somethingSelected());
eqPos(cm.getCursor(), Pos(2, 4));
cm.execCommand("goLineUp");
cm.execCommand("goLineUp");
eqPos(cm.getCursor(), Pos(0, 4));
cm.setSelection(Pos(0, 1), Pos(2, 4));
cm.execCommand("goCharLeft");
is(!cm.somethingSelected());
eqPos(cm.getCursor(), Pos(0, 1));
}, {value: "aaaaa\nb\nccccc"});
testCM("clickTab", function(cm) {
var p0 = cm.charCoords(Pos(0, 0));
eqPos(cm.coordsChar({left: p0.left + 5, top: p0.top + 5}), Pos(0, 0));
@ -1141,7 +1206,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ر", "خ ة ق",
"<img src=\"/בדיקה3.jpg\">"], function(line) {
var inv = line.charAt(0) == "خ";
cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart");
@ -1414,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,
@ -1560,3 +1640,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("/"), "");
});

View File

@ -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,
@ -189,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());
@ -1047,7 +1053,7 @@ testVim('ctrl-x', function(cm, vim, helpers) {
eq('-3', cm.getValue());
}, {value: '0'});
testVim('<C-x>/<C-a> search forward', function(cm, vim, helpers) {
['<C-x>', '<C-a>'].forEach(function(key) {
forEach(['<C-x>', '<C-a>'], function(key) {
cm.setCursor(0, 0);
helpers.doKeys(key);
helpers.assertCursorAt(0, 5);
@ -1997,6 +2003,33 @@ testVim('zt==z<CR>', 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('<C-y>');
eq(0, cm.getCursor().line);
prevScrollInfo = cm.getScrollInfo();
helpers.doKeys('<C-e>');
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('<C-e>');
eq(prevCursor.line, cm.getCursor().line);
prevScrollInfo = cm.getScrollInfo();
helpers.doKeys('<C-y>');
eq(prevCursor.line - 1, cm.getCursor().line);
eq(true, prevScrollInfo.top > cm.getScrollInfo().top);
}, { value: scrollMotionSandbox});
var squareBracketMotionSandbox = ''+
'({\n'+//0
' ({\n'+//11
@ -2080,7 +2113,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);
@ -2368,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 <Key>-keys
testVim('ex_api_test', function(cm, vim, helpers) {
var res=false;

View File

@ -100,12 +100,15 @@
progress = document.getElementById("progress"),
progressRan = document.getElementById("progress_ran").childNodes[0],
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
phantom = false;
function runHarness(){
if (running) {
@ -114,19 +117,25 @@
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(parseTestFilter(decodeURIComponent(s)));
}
}
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;
@ -159,12 +168,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;
@ -182,15 +195,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;

View File

@ -100,8 +100,10 @@
progress = document.getElementById("progress"),
progressRan = document.getElementById("progress_ran").childNodes[0],
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
@ -115,19 +117,25 @@
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(parseTestFilter(decodeURIComponent(s)));
}
}
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;
@ -160,12 +168,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;
@ -183,15 +195,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;