Bug 1583278 - Upgrade CodeMirror to 5.49.0. r=bgrins

Differential Revision: https://phabricator.services.mozilla.com/D46826

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Gabriel Luong 2019-09-23 18:47:42 +00:00
parent 3d10b528b0
commit c6ffcbb611
28 changed files with 10635 additions and 10024 deletions

View File

@ -5,7 +5,7 @@ code, and optionally help with indentation.
# Upgrade
Currently used version is 5.40.0. To upgrade: download a new version of
Currently used version is 5.49.0. To upgrade: download a new version of
CodeMirror from the project's page [1] and replace all JavaScript and
CSS files inside the codemirror directory [2].
@ -226,28 +226,6 @@ diff --git a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.
advance();
};
# Middle-click pasting patch
See Bug 1482875. Not needed anymore when https://github.com/codemirror/CodeMirror/pull/5751 lands.
```diff
diff --git a/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js b/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
--- a/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
+++ b/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
@@ -9256,8 +9256,9 @@ TextareaInput.prototype.init = function
on(display.scroller, "paste", function (e) {
if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
- cm.state.pasteIncoming = true
- input.focus()
+ const event = new Event("paste");
+ event.clipboardData = e.clipboardData;
+ te.dispatchEvent(event);
})
// Prevent normal selection in the editor (we handle our own)
```
# Footnotes
[1] http://codemirror.net

View File

@ -11,6 +11,7 @@
})(function(CodeMirror) {
var defaults = {
pairs: "()[]{}''\"\"",
closeBefore: ")]}'\":;>",
triples: "",
explode: "[]{}"
};
@ -109,6 +110,9 @@
var pairs = getOption(conf, "pairs");
var pos = pairs.indexOf(ch);
if (pos == -1) return CodeMirror.Pass;
var closeBefore = getOption(conf,"closeBefore");
var triples = getOption(conf, "triples");
var identical = pairs.charAt(pos + 1) == ch;
@ -136,7 +140,7 @@
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
else return CodeMirror.Pass;
} else if (opening) {
} else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
curType = "both";
} else {
return CodeMirror.Pass;

View File

@ -21,6 +21,8 @@
* An array of tag names that should, when opened, cause a
* blank line to be added inside the tag, and the blank line and
* closing line to be indented.
* `emptyTags` (default is none)
* An array of XML tag names that should be autoclosed with '/>'.
*
* See demos/closetag.html for a usage example.
*/
@ -58,24 +60,31 @@
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
var tagInfo = inner.mode.xmlCurrentTag && inner.mode.xmlCurrentTag(state)
var tagName = tagInfo && tagInfo.name
if (!tagName) return CodeMirror.Pass
var html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
var tagName = state.tagName;
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
var lowerTagName = tagName.toLowerCase();
// Don't process the '>' at the end of an end-tag or self-closing tag
if (!tagName ||
tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
tok.type == "tag" && state.type == "closeTag" ||
tok.type == "tag" && tagInfo.close ||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
closingTagExists(cm, tagName, pos, state, true))
closingTagExists(cm, inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state) || [], tagName, pos, true))
return CodeMirror.Pass;
var emptyTags = typeof opt == "object" && opt.emptyTags;
if (emptyTags && indexOf(emptyTags, tagName) > -1) {
replacements[i] = { text: "/>", newPos: CodeMirror.Pos(pos.line, pos.ch + 2) };
continue;
}
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
replacements[i] = {indent: indent,
text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
@ -112,19 +121,16 @@
// when completing in JS/CSS snippet in htmlmixed mode. Does not
// work for other XML embedded languages (there is no general
// way to go from a mixed mode to its current XML state).
var replacement;
if (inner.mode.name != "xml") {
if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
replacement = head + "script";
else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
replacement = head + "style";
else
return CodeMirror.Pass;
var replacement, mixed = inner.mode.name != "xml" && cm.getMode().name == "htmlmixed"
if (mixed && inner.mode.name == "javascript") {
replacement = head + "script";
} else if (mixed && inner.mode.name == "css") {
replacement = head + "style";
} else {
if (!state.context || !state.context.tagName ||
closingTagExists(cm, state.context.tagName, pos, state))
var context = inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state)
if (!context || (context.length && closingTagExists(cm, context, context[context.length - 1], pos)))
return CodeMirror.Pass;
replacement = head + state.context.tagName;
replacement = head + context[context.length - 1]
}
if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
replacements[i] = replacement;
@ -154,16 +160,19 @@
// If xml-fold is loaded, we use its functionality to try and verify
// whether a given tag is actually unclosed.
function closingTagExists(cm, tagName, pos, state, newTag) {
function closingTagExists(cm, context, tagName, pos, newTag) {
if (!CodeMirror.scanForClosingTag) return false;
var end = Math.min(cm.lastLine() + 1, pos.line + 500);
var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!nextClose || nextClose.tag != tagName) return false;
var cx = state.context;
// If the immediate wrapping context contains onCx instances of
// the same tag, a closing tag only exists if there are at least
// that many closing tags of that type following.
for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx;
var onCx = newTag ? 1 : 0
for (var i = context.length - 1; i >= 0; i--) {
if (context[i] == tagName) ++onCx
else break
}
pos = nextClose.to;
for (var i = 1; i < onCx; i++) {
var next = CodeMirror.scanForClosingTag(cm, pos, null, end);

View File

@ -20,7 +20,17 @@
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head;
// If we're not in Markdown mode, fall back to normal newlineAndIndent
var eolState = cm.getStateAfter(pos.line);
var inner = CodeMirror.innerMode(cm.getMode(), eolState);
if (inner.mode.name !== "markdown") {
cm.execCommand("newlineAndIndent");
return;
} else {
eolState = inner.state;
}
var inList = eolState.list !== false;
var inQuote = eolState.quote !== 0;

View File

@ -14,20 +14,25 @@
var Pos = CodeMirror.Pos;
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<"};
function bracketRegex(config) {
return config && config.bracketRegex || /[(){}[\]]/
}
function findMatchingBracket(cm, where, config) {
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
var afterCursor = config && config.afterCursor
if (afterCursor == null)
afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)
var re = bracketRegex(config)
// A cursor is defined as between two characters, but in in vim command mode
// (i.e. not insert mode), the cursor is visually represented as a
// highlighted box on top of the 2nd character. Otherwise, we allow matches
// from before or after the cursor.
var match = (!afterCursor && pos >= 0 && matching[line.text.charAt(pos)]) ||
matching[line.text.charAt(++pos)];
var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) ||
re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)];
if (!match) return null;
var dir = match.charAt(1) == ">" ? 1 : -1;
if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;
@ -51,7 +56,7 @@
var maxScanLines = (config && config.maxScanLines) || 1000;
var stack = [];
var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
var re = bracketRegex(config)
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
@ -64,7 +69,7 @@
var ch = line.charAt(pos);
if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
var match = matching[ch];
if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
if (match && (match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
else stack.pop();
}

View File

@ -54,7 +54,7 @@ CodeMirror.registerHelper("fold", "brace", function(cm, start) {
++pos;
}
}
if (end == null || line == end && endCh == startCh) return;
if (end == null || line == end) return;
return {from: CodeMirror.Pos(line, startCh),
to: CodeMirror.Pos(end, endCh)};
});

View File

@ -16,7 +16,7 @@
cm.clearGutter(cm.state.foldGutter.options.gutter);
cm.state.foldGutter = null;
cm.off("gutterClick", onGutterClick);
cm.off("change", onChange);
cm.off("changes", onChange);
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
@ -26,7 +26,7 @@
cm.state.foldGutter = new State(parseOptions(val));
updateInViewport(cm);
cm.on("gutterClick", onGutterClick);
cm.on("change", onChange);
cm.on("changes", onChange);
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
@ -51,8 +51,13 @@
function isFolded(cm, line) {
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
for (var i = 0; i < marks.length; ++i)
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__isFold) {
var fromPos = marks[i].find(-1);
if (fromPos && fromPos.line === line)
return marks[i];
}
}
}
function marker(spec) {
@ -100,7 +105,7 @@
if (gutter != opts.gutter) return;
var folded = isFolded(cm, line);
if (folded) folded.clear();
else cm.foldCode(Pos(line, 0), opts.rangeFinder);
else cm.foldCode(Pos(line, 0), opts);
}
function onChange(cm) {

View File

@ -46,6 +46,10 @@
completion.update(true);
});
CodeMirror.defineExtension("closeHint", function() {
if (this.state.completionActive) this.state.completionActive.close()
})
function Completion(cm, options) {
this.cm = cm;
this.options = options;
@ -163,6 +167,14 @@
Tab: handle.pick,
Esc: handle.close
};
var mac = /Mac/.test(navigator.platform);
if (mac) {
baseMap["Ctrl-P"] = function() {handle.moveFocus(-1);};
baseMap["Ctrl-N"] = function() {handle.moveFocus(1);};
}
var custom = completion.options.customKeys;
var ourMap = custom ? {} : baseMap;
function addBinding(key, val) {
@ -198,31 +210,45 @@
this.data = data;
this.picked = false;
var widget = this, cm = completion.cm;
var ownerDocument = cm.getInputField().ownerDocument;
var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;
var hints = this.hints = document.createElement("ul");
var hints = this.hints = ownerDocument.createElement("ul");
var theme = completion.cm.options.theme;
hints.className = "CodeMirror-hints " + theme;
this.selectedHint = data.selectedHint || 0;
var completions = data.list;
for (var i = 0; i < completions.length; ++i) {
var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i];
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
if (cur.className != null) className = cur.className + " " + className;
elt.className = className;
if (cur.render) cur.render(elt, data, cur);
else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));
elt.hintId = i;
}
var container = completion.options.container || ownerDocument.body;
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
var left = pos.left, top = pos.bottom, below = true;
hints.style.left = left + "px";
hints.style.top = top + "px";
var offsetLeft = 0, offsetTop = 0;
if (container !== ownerDocument.body) {
// We offset the cursor position because left and top are relative to the offsetParent's top left corner.
var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;
var offsetParent = isContainerPositioned ? container : container.offsetParent;
var offsetParentPosition = offsetParent.getBoundingClientRect();
var bodyPosition = ownerDocument.body.getBoundingClientRect();
offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);
offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);
}
hints.style.left = (left - offsetLeft) + "px";
hints.style.top = (top - offsetTop) + "px";
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
(completion.options.container || document.body).appendChild(hints);
var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);
var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);
container.appendChild(hints);
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
var scrolls = hints.scrollHeight > hints.clientHeight + 1
var startScroll = cm.getScrollInfo();
@ -230,15 +256,15 @@
if (overlapY > 0) {
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor
hints.style.top = (top = pos.top - height) + "px";
hints.style.top = (top = pos.top - height - offsetTop) + "px";
below = false;
} else if (height > winH) {
hints.style.height = (winH - 5) + "px";
hints.style.top = (top = pos.bottom - box.top) + "px";
hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px";
var cursor = cm.getCursor();
if (data.from.ch != cursor.ch) {
pos = cm.cursorCoords(cursor);
hints.style.left = (left = pos.left) + "px";
hints.style.left = (left = pos.left - offsetLeft) + "px";
box = hints.getBoundingClientRect();
}
}
@ -249,7 +275,7 @@
hints.style.width = (winW - 5) + "px";
overlapX -= (box.right - box.left) - winW;
}
hints.style.left = (left = pos.left - overlapX) + "px";
hints.style.left = (left = pos.left - overlapX - offsetLeft) + "px";
}
if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
node.style.paddingRight = cm.display.nativeBarWidth + "px"
@ -273,7 +299,7 @@
cm.on("scroll", this.onScroll = function() {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
var newTop = top + startScroll.top - curScroll.top;
var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);
if (!below) point += hints.offsetHeight;
if (point <= editor.top || point >= editor.bottom) return completion.close();
hints.style.top = newTop + "px";

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Distributed under an MIT license: https://codemirror.net/LICENSE
// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.
@ -78,10 +78,12 @@
}
function parseString(string) {
return string.replace(/\\(.)/g, function(_, ch) {
return string.replace(/\\([nrt\\])/g, function(match, ch) {
if (ch == "n") return "\n"
if (ch == "r") return "\r"
return ch
if (ch == "t") return "\t"
if (ch == "\\") return "\\"
return match
})
}

File diff suppressed because one or more lines are too long

View File

@ -369,6 +369,7 @@
"Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
"Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
"Shift-Ctrl-Z": "redo",
"Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
"Ctrl-S": "findPersistentNext", "Ctrl-R": "findPersistentPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace",
"Alt-/": "autocomplete",

View File

@ -589,8 +589,8 @@
"Cmd-/": "toggleCommentIndented",
"Cmd-J": "joinLines",
"Shift-Cmd-D": "duplicateLine",
"F9": "sortLines",
"Cmd-F9": "sortLinesInsensitive",
"F5": "sortLines",
"Cmd-F5": "sortLinesInsensitive",
"F2": "nextBookmark",
"Shift-F2": "prevBookmark",
"Cmd-F2": "toggleBookmark",

View File

@ -3,7 +3,7 @@
/**
* Supported keybindings:
* Too many to list. Refer to defaultKeyMap below.
* Too many to list. Refer to defaultKeymap below.
*
* Supported Ex commands:
* Refer to defaultExCommandMap below.
@ -53,6 +53,7 @@
{ keys: '<Down>', type: 'keyToKey', toKeys: 'j' },
{ keys: '<Space>', type: 'keyToKey', toKeys: 'l' },
{ keys: '<BS>', type: 'keyToKey', toKeys: 'h', context: 'normal'},
{ keys: '<Del>', type: 'keyToKey', toKeys: 'x', context: 'normal'},
{ keys: '<C-Space>', type: 'keyToKey', toKeys: 'W' },
{ keys: '<C-BS>', type: 'keyToKey', toKeys: 'B', context: 'normal' },
{ keys: '<S-Space>', type: 'keyToKey', toKeys: 'w' },
@ -132,6 +133,7 @@
{ keys: 'd', type: 'operator', operator: 'delete' },
{ keys: 'y', type: 'operator', operator: 'yank' },
{ keys: 'c', type: 'operator', operator: 'change' },
{ keys: '=', type: 'operator', operator: 'indentAuto' },
{ keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
{ keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
{ keys: 'g~', type: 'operator', operator: 'changeCase' },
@ -151,6 +153,8 @@
{ keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
{ keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
{ keys: '<C-w>', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
//ignore C-w in normal mode
{ keys: '<C-w>', type: 'idle', context: 'normal' },
// Actions
{ keys: '<C-i>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
{ keys: '<C-o>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
@ -207,6 +211,7 @@
// Ex command
{ keys: ':', type: 'ex' }
];
var defaultKeymapLength = defaultKeymap.length;
/**
* Ex commands
@ -281,7 +286,9 @@
enterVimMode(cm);
}
function fatCursorMarks(cm) {
function updateFatCursorMark(cm) {
if (!cm.state.fatCursorMarks) return;
clearFatCursorMark(cm);
var ranges = cm.listSelections(), result = []
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i]
@ -297,25 +304,26 @@
}
}
}
return result
cm.state.fatCursorMarks = result;
}
function updateFatCursorMark(cm) {
var marks = cm.state.fatCursorMarks
if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear()
cm.state.fatCursorMarks = fatCursorMarks(cm)
function clearFatCursorMark(cm) {
var marks = cm.state.fatCursorMarks;
if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
}
function enableFatCursorMark(cm) {
cm.state.fatCursorMarks = fatCursorMarks(cm)
cm.state.fatCursorMarks = [];
updateFatCursorMark(cm)
cm.on("cursorActivity", updateFatCursorMark)
}
function disableFatCursorMark(cm) {
var marks = cm.state.fatCursorMarks
if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear()
cm.state.fatCursorMarks = null
cm.off("cursorActivity", updateFatCursorMark)
clearFatCursorMark(cm);
cm.off("cursorActivity", updateFatCursorMark);
// explicitly set fatCursorMarks to null because event listener above
// can be invoke after removing it, if off is called from operation
cm.state.fatCursorMarks = null;
}
// Deprecated, simply setting the keymap works again.
@ -742,6 +750,78 @@
unmap: function(lhs, ctx) {
exCommandDispatcher.unmap(lhs, ctx);
},
// Non-recursive map function.
// NOTE: This will not create mappings to key maps that aren't present
// in the default key map. See TODO at bottom of function.
noremap: function(lhs, rhs, ctx) {
function toCtxArray(ctx) {
return ctx ? [ctx] : ['normal', 'insert', 'visual'];
}
var ctxsToMap = toCtxArray(ctx);
// Look through all actual defaults to find a map candidate.
var actualLength = defaultKeymap.length, origLength = defaultKeymapLength;
for (var i = actualLength - origLength;
i < actualLength && ctxsToMap.length;
i++) {
var mapping = defaultKeymap[i];
// Omit mappings that operate in the wrong context(s) and those of invalid type.
if (mapping.keys == rhs &&
(!ctx || !mapping.context || mapping.context === ctx) &&
mapping.type.substr(0, 2) !== 'ex' &&
mapping.type.substr(0, 3) !== 'key') {
// Make a shallow copy of the original keymap entry.
var newMapping = {};
for (var key in mapping) {
newMapping[key] = mapping[key];
}
// Modify it point to the new mapping with the proper context.
newMapping.keys = lhs;
if (ctx && !newMapping.context) {
newMapping.context = ctx;
}
// Add it to the keymap with a higher priority than the original.
this._mapCommand(newMapping);
// Record the mapped contexts as complete.
var mappedCtxs = toCtxArray(mapping.context);
ctxsToMap = ctxsToMap.filter(function(el) { return mappedCtxs.indexOf(el) === -1; });
}
}
// TODO: Create non-recursive keyToKey mappings for the unmapped contexts once those exist.
},
// Remove all user-defined mappings for the provided context.
mapclear: function(ctx) {
// Partition the existing keymap into user-defined and true defaults.
var actualLength = defaultKeymap.length,
origLength = defaultKeymapLength;
var userKeymap = defaultKeymap.slice(0, actualLength - origLength);
defaultKeymap = defaultKeymap.slice(actualLength - origLength);
if (ctx) {
// If a specific context is being cleared, we need to keep mappings
// from all other contexts.
for (var i = userKeymap.length - 1; i >= 0; i--) {
var mapping = userKeymap[i];
if (ctx !== mapping.context) {
if (mapping.context) {
this._mapCommand(mapping);
} else {
// `mapping` applies to all contexts so create keymap copies
// for each context except the one being cleared.
var contexts = ['normal', 'insert', 'visual'];
for (var j in contexts) {
if (contexts[j] !== ctx) {
var newMapping = {};
for (var key in mapping) {
newMapping[key] = mapping[key];
}
newMapping.context = contexts[j];
this._mapCommand(newMapping);
}
}
}
}
}
}
},
// TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace
// them, or somehow make them work with the existing CodeMirror setOption/getOption API.
setOption: setOption,
@ -1637,6 +1717,7 @@
vim.lastEditActionCommand = actionCommand;
macroModeState.lastInsertModeChanges.changes = [];
macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0;
}
};
@ -1767,7 +1848,7 @@
if (line < first && cur.line == first){
return this.moveToStartOfLine(cm, head, motionArgs, vim);
}else if (line > last && cur.line == last){
return this.moveToEol(cm, head, motionArgs, vim);
return this.moveToEol(cm, head, motionArgs, vim, true);
}
if (motionArgs.toFirstChar){
endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
@ -1869,13 +1950,15 @@
vim.lastHSPos = cm.charCoords(head,'div').left;
return moveToColumn(cm, repeat);
},
moveToEol: function(cm, head, motionArgs, vim) {
moveToEol: function(cm, head, motionArgs, vim, keepHPos) {
var cur = head;
vim.lastHPos = Infinity;
var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity);
var end=cm.clipPos(retval);
end.ch--;
vim.lastHSPos = cm.charCoords(end,'div').left;
if (!keepHPos) {
vim.lastHPos = Infinity;
vim.lastHSPos = cm.charCoords(end,'div').left;
}
return retval;
},
moveToFirstNonWhiteSpaceCharacter: function(cm, head) {
@ -1901,7 +1984,9 @@
}
}
if (ch < lineText.length) {
var matched = cm.findMatchingBracket(Pos(line, ch));
// Only include angle brackets in analysis if they are being matched.
var re = (ch === '<' || ch === '>') ? /[(){}[\]<>]/ : /[(){}[\]]/;
var matched = cm.findMatchingBracket(Pos(line, ch), {bracketRegex: re});
return matched.to;
} else {
return cursor;
@ -1921,13 +2006,11 @@
textObjectManipulation: function(cm, head, motionArgs, vim) {
// TODO: lots of possible exceptions that can be thrown here. Try da(
// outside of a () block.
// TODO: adding <> >< to this map doesn't work, presumably because
// they're operators
var mirroredPairs = {'(': ')', ')': '(',
'{': '}', '}': '{',
'[': ']', ']': '['};
var selfPaired = {'\'': true, '"': true};
'[': ']', ']': '[',
'<': '>', '>': '<'};
var selfPaired = {'\'': true, '"': true, '`': true};
var character = motionArgs.selectedCharacter;
// 'b' refers to '()' block.
@ -2015,7 +2098,6 @@
change: function(cm, args, ranges) {
var finalHead, text;
var vim = cm.state.vim;
vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock;
if (!vim.visualMode) {
var anchor = ranges[0].anchor,
head = ranges[0].head;
@ -2115,6 +2197,10 @@
}
return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
},
indentAuto: function(cm, _args, ranges) {
cm.execCommand("indentAuto");
return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor);
},
changeCase: function(cm, args, ranges, oldAnchor, newHead) {
var selections = cm.getSelections();
var swapped = [];
@ -2234,6 +2320,8 @@
var macroModeState = vimGlobalState.macroModeState;
if (registerName == '@') {
registerName = macroModeState.latestRegister;
} else {
macroModeState.latestRegister = registerName;
}
while(repeat--){
executeMacroRegister(cm, vim, macroModeState, registerName);
@ -2272,6 +2360,8 @@
} else if (insertAt == 'firstNonBlank') {
head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
} else if (insertAt == 'startOfSelectedArea') {
if (!vim.visualMode)
return;
if (!vim.visualBlock) {
if (sel.head.line < sel.anchor.line) {
head = sel.head;
@ -2285,6 +2375,8 @@
height = Math.abs(sel.head.line - sel.anchor.line) + 1;
}
} else if (insertAt == 'endOfSelectedArea') {
if (!vim.visualMode)
return;
if (!vim.visualBlock) {
if (sel.head.line >= sel.anchor.line) {
head = offsetCursor(sel.head, 0, 1);
@ -2478,7 +2570,17 @@
}
var linewise = register.linewise;
var blockwise = register.blockwise;
if (linewise) {
if (blockwise) {
text = text.split('\n');
if (linewise) {
text.pop();
}
for (var i = 0; i < text.length; i++) {
text[i] = (text[i] == '') ? ' ' : text[i];
}
cur.ch += actionArgs.after ? 1 : 0;
cur.ch = Math.min(lineLength(cm, cur.line), cur.ch);
} else if (linewise) {
if(vim.visualMode) {
text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';
} else if (actionArgs.after) {
@ -2490,12 +2592,6 @@
cur.ch = 0;
}
} else {
if (blockwise) {
text = text.split('\n');
for (var i = 0; i < text.length; i++) {
text[i] = (text[i] == '') ? ' ' : text[i];
}
}
cur.ch += actionArgs.after ? 1 : 0;
}
var curPosFinal;
@ -2730,12 +2826,6 @@
}
return Pos(cur.line + offsetLine, cur.ch + offsetCh);
}
function getOffset(anchor, head) {
return {
line: head.line - anchor.line,
ch: head.line - anchor.line
};
}
function commandMatches(keys, keyMap, context, inputState) {
// Partial matches are not applied. They inform the key handler
// that the current key sequence is a subsequence of a valid key
@ -3724,11 +3814,13 @@
var bracketRegexp = ({
'(': /[()]/, ')': /[()]/,
'[': /[[\]]/, ']': /[[\]]/,
'{': /[{}]/, '}': /[{}]/})[symb];
'{': /[{}]/, '}': /[{}]/,
'<': /[<>]/, '>': /[<>]/})[symb];
var openSym = ({
'(': '(', ')': '(',
'[': '[', ']': '[',
'{': '{', '}': '{'})[symb];
'{': '{', '}': '{',
'<': '<', '>': '<'})[symb];
var curChar = cm.getLine(cur.line).charAt(cur.ch);
// Due to the behavior of scanForBracket, we need to add an offset if the
// cursor is on a matching open bracket.
@ -3981,7 +4073,7 @@
}
// Unescape \ and / in the replace part, for PCRE mode.
var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t'};
var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\&':'&'};
function unescapeRegexReplace(str) {
var stream = new CodeMirror.StringStream(str);
var output = [];
@ -4137,23 +4229,27 @@
query: query
};
}
var highlightTimeout = 0;
function highlightSearchMatches(cm, query) {
var searchState = getSearchState(cm);
var overlay = searchState.getOverlay();
if (!overlay || query != overlay.query) {
if (overlay) {
cm.removeOverlay(overlay);
}
overlay = searchOverlay(query);
cm.addOverlay(overlay);
if (cm.showMatchesOnScrollbar) {
if (searchState.getScrollbarAnnotate()) {
searchState.getScrollbarAnnotate().clear();
clearTimeout(highlightTimeout);
highlightTimeout = setTimeout(function() {
var searchState = getSearchState(cm);
var overlay = searchState.getOverlay();
if (!overlay || query != overlay.query) {
if (overlay) {
cm.removeOverlay(overlay);
}
searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
overlay = searchOverlay(query);
cm.addOverlay(overlay);
if (cm.showMatchesOnScrollbar) {
if (searchState.getScrollbarAnnotate()) {
searchState.getScrollbarAnnotate().clear();
}
searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
}
searchState.setOverlay(overlay);
}
searchState.setOverlay(overlay);
}
}, 50);
}
function findNext(cm, prev, query, repeat) {
if (repeat === undefined) { repeat = 1; }
@ -4765,6 +4861,9 @@
var global = false; // True to replace all instances on a line, false to replace only 1.
if (tokens.length) {
regexPart = tokens[0];
if (getOption('pcre') && regexPart !== '') {
regexPart = new RegExp(regexPart).source; //normalize not escaped characters
}
replacePart = tokens[1];
if (regexPart && regexPart[regexPart.length - 1] === '$') {
regexPart = regexPart.slice(0, regexPart.length - 1) + '\\n';
@ -4772,7 +4871,7 @@
}
if (replacePart !== undefined) {
if (getOption('pcre')) {
replacePart = unescapeRegexReplace(replacePart);
replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g,"$1$$&"));
} else {
replacePart = translateRegexReplace(replacePart);
}
@ -4803,7 +4902,11 @@
global = true;
flagsPart.replace('g', '');
}
regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart;
if (getOption('pcre')) {
regexPart = regexPart + '/' + flagsPart;
} else {
regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart;
}
}
}
if (regexPart) {
@ -5040,32 +5143,7 @@
var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
var isPlaying = macroModeState.isPlaying;
var lastChange = macroModeState.lastInsertModeChanges;
// In case of visual block, the insertModeChanges are not saved as a
// single word, so we convert them to a single word
// so as to update the ". register as expected in real vim.
var text = [];
if (!isPlaying) {
var selLength = lastChange.inVisualBlock && vim.lastSelection ?
vim.lastSelection.visualBlock.height : 1;
var changes = lastChange.changes;
var text = [];
var i = 0;
// In case of multiple selections in blockwise visual,
// the inserted text, for example: 'f<Backspace>oo', is stored as
// 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines).
// We push the contents of the changes array as per the following:
// 1. In case of InsertModeKey, just increment by 1.
// 2. In case of a character, jump by selLength (2 in the example).
while (i < changes.length) {
// This loop will convert 'ff<bs>oooo' to 'f<bs>oo'.
text.push(changes[i]);
if (changes[i] instanceof InsertModeKey) {
i++;
} else {
i+= selLength;
}
}
lastChange.changes = text;
cm.off('change', onChange);
CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
}
@ -5196,17 +5274,24 @@
if (!macroModeState.isPlaying) {
while(changeObj) {
lastChange.expectCursorActivityForChange = true;
if (changeObj.origin == '+input' || changeObj.origin == 'paste'
if (lastChange.ignoreCount > 1) {
lastChange.ignoreCount--;
} else if (changeObj.origin == '+input' || changeObj.origin == 'paste'
|| changeObj.origin === undefined /* only in testing */) {
var selectionCount = cm.listSelections().length;
if (selectionCount > 1)
lastChange.ignoreCount = selectionCount;
var text = changeObj.text.join('\n');
if (lastChange.maybeReset) {
lastChange.changes = [];
lastChange.maybeReset = false;
}
if (cm.state.overwrite && !/\n/.test(text)) {
if (text) {
if (cm.state.overwrite && !/\n/.test(text)) {
lastChange.changes.push([text]);
} else {
} else {
lastChange.changes.push(text);
}
}
}
// Change objects may be chained with next.
@ -5371,18 +5456,15 @@
return true;
}
var head = cm.getCursor('head');
var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock;
if (inVisualBlock) {
var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock;
if (visualBlock) {
// Set up block selection again for repeating the changes.
var vim = cm.state.vim;
var lastSel = vim.lastSelection;
var offset = getOffset(lastSel.anchor, lastSel.head);
selectForInsert(cm, head, offset.line + 1);
selectForInsert(cm, head, visualBlock + 1);
repeat = cm.listSelections().length;
cm.setCursor(head);
}
for (var i = 0; i < repeat; i++) {
if (inVisualBlock) {
if (visualBlock) {
cm.setCursor(offsetCursor(head, i, 0));
}
for (var j = 0; j < changes.length; j++) {
@ -5399,7 +5481,7 @@
}
}
}
if (inVisualBlock) {
if (visualBlock) {
cm.setCursor(offsetCursor(head, 0, 1));
}
}

View File

@ -13,7 +13,8 @@
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 4px; /* Horizontal padding of content */
}
@ -96,7 +97,7 @@
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: -20px;
left: 0; right: 0; top: -50px; bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
@ -236,7 +237,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
@ -255,7 +257,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;

File diff suppressed because it is too large Load Diff

View File

@ -65,7 +65,10 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
numberStart = parserConfig.numberStart || /[\d\.]/,
number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,
isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/,
isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/;
isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/,
// An optional function that takes a {string} token and returns true if it
// should be treated as a builtin.
isReservedIdentifier = parserConfig.isReservedIdentifier || false;
var curPunc, isDefKeyword;
@ -113,7 +116,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "keyword";
}
if (contains(types, cur)) return "type";
if (contains(builtin, cur)) {
if (contains(builtin, cur)
|| (isReservedIdentifier && isReservedIdentifier(cur))) {
if (contains(blockKeywords, cur)) curPunc = "newstatement";
return "builtin";
}
@ -263,8 +267,33 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
}
var cKeywords = "auto if break case register continue return default do sizeof " +
"static else struct switch extern typedef union for goto while enum const volatile";
var cTypes = "int long char short double float unsigned signed void size_t ptrdiff_t";
"static else struct switch extern typedef union for goto while enum const " +
"volatile inline restrict asm fortran";
// Do not use this. Use the cTypes function below. This is global just to avoid
// excessive calls when cTypes is being called multiple times during a parse.
var basicCTypes = words("int long char short double float unsigned signed " +
"void bool");
// Do not use this. Use the objCTypes function below. This is global just to avoid
// excessive calls when objCTypes is being called multiple times during a parse.
var basicObjCTypes = words("SEL instancetype id Class Protocol BOOL");
// Returns true if identifier is a "C" type.
// C type is defined as those that are reserved by the compiler (basicTypes),
// and those that end in _t (Reserved by POSIX for types)
// http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html
function cTypes(identifier) {
return contains(basicCTypes, identifier) || /.+_t$/.test(identifier);
}
// Returns true if identifier is a "Objective C" type.
function objCTypes(identifier) {
return cTypes(identifier) || contains(basicObjCTypes, identifier);
}
var cBlockKeywords = "case do else for if switch while struct enum union";
var cDefKeywords = "struct enum union";
function cppHook(stream, state) {
if (!state.startOfLine) return false
@ -286,6 +315,14 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return false;
}
// For C and C++ (and ObjC): identifiers starting with __
// or _ followed by a capital letter are reserved for the compiler.
function cIsReservedIdentifier(token) {
if (!token || token.length < 2) return false;
if (token[0] != '_') return false;
return (token[1] == '_') || (token[1] !== token[1].toLowerCase());
}
function cpp14Literal(stream) {
stream.eatWhile(/[\w\.']/);
return "number";
@ -368,31 +405,36 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
def(["text/x-csrc", "text/x-c", "text/x-chdr"], {
name: "clike",
keywords: words(cKeywords),
types: words(cTypes + " bool _Complex _Bool float_t double_t intptr_t intmax_t " +
"int8_t int16_t int32_t int64_t uintptr_t uintmax_t uint8_t uint16_t " +
"uint32_t uint64_t"),
blockKeywords: words("case do else for if switch while struct"),
defKeywords: words("struct"),
types: cTypes,
blockKeywords: words(cBlockKeywords),
defKeywords: words(cDefKeywords),
typeFirstDefinitions: true,
atoms: words("NULL true false"),
hooks: {"#": cppHook, "*": pointerHook},
isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"#": cppHook,
"*": pointerHook,
},
modeProps: {fold: ["brace", "include"]}
});
def(["text/x-c++src", "text/x-c++hdr"], {
name: "clike",
keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try explicit new " +
"static_cast typeid catch operator template typename class friend private " +
"this using const_cast inline public throw virtual delete mutable protected " +
"alignas alignof constexpr decltype nullptr noexcept thread_local final " +
"static_assert override"),
types: words(cTypes + " bool wchar_t"),
blockKeywords: words("catch class do else finally for if struct switch try while"),
defKeywords: words("class namespace struct enum union"),
// Keywords from https://en.cppreference.com/w/cpp/keyword includes C++20.
keywords: words(cKeywords + "alignas alignof and and_eq audit axiom bitand bitor catch " +
"class compl concept constexpr const_cast decltype delete dynamic_cast " +
"explicit export final friend import module mutable namespace new noexcept " +
"not not_eq operator or or_eq override private protected public " +
"reinterpret_cast requires static_assert static_cast template this " +
"thread_local throw try typeid typename using virtual xor xor_eq"),
types: cTypes,
blockKeywords: words(cBlockKeywords + " class try catch"),
defKeywords: words(cDefKeywords + " class namespace"),
typeFirstDefinitions: true,
atoms: words("true false NULL"),
atoms: words("true false NULL nullptr"),
dontIndentStatements: /^template$/,
isIdentifierChar: /[\w\$_~\xa1-\uffff]/,
isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"#": cppHook,
"*": pointerHook,
@ -425,7 +467,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
def("text/x-java", {
name: "clike",
keywords: words("abstract assert break case catch class const continue default " +
"do else enum extends final finally float for goto if implements import " +
"do else enum extends final finally for goto if implements import " +
"instanceof interface native new package private protected public " +
"return static strictfp super switch synchronized this throw throws transient " +
"try volatile while @interface"),
@ -513,7 +555,6 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
def("text/x-scala", {
name: "clike",
keywords: words(
/* scala */
"abstract case catch class def do else extends final finally for forSome if " +
"implicit import lazy match new null object override package private protected return " +
@ -573,7 +614,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return state.tokenize(stream, state)
}
},
modeProps: {closeBrackets: {triples: '"'}}
modeProps: {closeBrackets: {pairs: '()[]{}""', triples: '"'}}
});
function tokenKotlinString(tripleString){
@ -628,15 +669,23 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
stream.eatWhile(/[\w\$_]/);
return "meta";
},
'*': function(_stream, state) {
return state.prevToken == '.' ? 'variable' : 'operator';
},
'"': function(stream, state) {
state.tokenize = tokenKotlinString(stream.match('""'));
return state.tokenize(stream, state);
},
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenNestedComment(1);
return state.tokenize(stream, state)
},
indent: function(state, ctx, textAfter, indentUnit) {
var firstChar = textAfter && textAfter.charAt(0);
if ((state.prevToken == "}" || state.prevToken == ")") && textAfter == "")
return state.indented;
if (state.prevToken == "operator" && textAfter != "}" ||
if ((state.prevToken == "operator" && textAfter != "}" && state.context.type != "}") ||
state.prevToken == "variable" && firstChar == "." ||
(state.prevToken == "}" || state.prevToken == ")") && firstChar == ".")
return indentUnit * 2 + ctx.indented;
@ -708,11 +757,11 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
def("text/x-nesc", {
name: "clike",
keywords: words(cKeywords + "as atomic async call command component components configuration event generic " +
keywords: words(cKeywords + " as atomic async call command component components configuration event generic " +
"implementation includes interface module new norace nx_struct nx_union post provides " +
"signal task uses abstract extends"),
types: words(cTypes),
blockKeywords: words("case do else for if switch while struct"),
types: cTypes,
blockKeywords: words(cBlockKeywords),
atoms: words("null true false"),
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
@ -720,28 +769,34 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
def("text/x-objectivec", {
name: "clike",
keywords: words(cKeywords + "inline restrict _Bool _Complex _Imaginary BOOL Class bycopy byref id IMP in " +
"inout nil oneway out Protocol SEL self super atomic nonatomic retain copy readwrite readonly"),
types: words(cTypes),
atoms: words("YES NO NULL NILL ON OFF true false"),
keywords: words(cKeywords + " bycopy byref in inout oneway out self super atomic nonatomic retain copy " +
"readwrite readonly strong weak assign typeof nullable nonnull null_resettable _cmd " +
"@interface @implementation @end @protocol @encode @property @synthesize @dynamic @class " +
"@public @package @private @protected @required @optional @try @catch @finally @import " +
"@selector @encode @defs @synchronized @autoreleasepool @compatibility_alias @available"),
types: objCTypes,
builtin: words("FOUNDATION_EXPORT FOUNDATION_EXTERN NS_INLINE NS_FORMAT_FUNCTION NS_RETURNS_RETAINED " +
"NS_ERROR_ENUM NS_RETURNS_NOT_RETAINED NS_RETURNS_INNER_POINTER NS_DESIGNATED_INITIALIZER " +
"NS_ENUM NS_OPTIONS NS_REQUIRES_NIL_TERMINATION NS_ASSUME_NONNULL_BEGIN " +
"NS_ASSUME_NONNULL_END NS_SWIFT_NAME NS_REFINED_FOR_SWIFT"),
blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized"),
defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class"),
dontIndentStatements: /^@.*$/,
typeFirstDefinitions: true,
atoms: words("YES NO NULL Nil nil true false nullptr"),
isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$]/);
return "keyword";
},
"#": cppHook,
indent: function(_state, ctx, textAfter) {
if (ctx.type == "statement" && /^@\w/.test(textAfter)) return ctx.indented
}
"*": pointerHook,
},
modeProps: {fold: "brace"}
modeProps: {fold: ["brace", "include"]}
});
def("text/x-squirrel", {
name: "clike",
keywords: words("base break clone continue const default delete enum extends function in class" +
" foreach local resume return this throw typeof yield constructor instanceof static"),
types: words(cTypes),
types: cTypes,
blockKeywords: words("case catch class else for foreach if switch try while"),
defKeywords: words("function local class"),
typeFirstDefinitions: true,

View File

@ -1,15 +1,10 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
/**
* Author: Hans Engel
* Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
*/
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
if (typeof exports === "object" && typeof module === "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
else if (typeof define === "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
@ -17,284 +12,277 @@
"use strict";
CodeMirror.defineMode("clojure", function (options) {
var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2",
ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword", VAR = "variable";
var INDENT_WORD_SKIP = options.indentUnit || 2;
var NORMAL_INDENT_UNIT = options.indentUnit || 2;
var atoms = ["false", "nil", "true"];
var specialForms = [".", "catch", "def", "do", "if", "monitor-enter",
"monitor-exit", "new", "quote", "recur", "set!", "throw", "try", "var"];
var coreSymbols = ["*", "*'", "*1", "*2", "*3", "*agent*",
"*allow-unresolved-vars*", "*assert*", "*clojure-version*",
"*command-line-args*", "*compile-files*", "*compile-path*",
"*compiler-options*", "*data-readers*", "*default-data-reader-fn*", "*e",
"*err*", "*file*", "*flush-on-newline*", "*fn-loader*", "*in*",
"*math-context*", "*ns*", "*out*", "*print-dup*", "*print-length*",
"*print-level*", "*print-meta*", "*print-namespace-maps*",
"*print-readably*", "*read-eval*", "*reader-resolver*", "*source-path*",
"*suppress-read*", "*unchecked-math*", "*use-context-classloader*",
"*verbose-defrecords*", "*warn-on-reflection*", "+", "+'", "-", "-'",
"->", "->>", "->ArrayChunk", "->Eduction", "->Vec", "->VecNode",
"->VecSeq", "-cache-protocol-fn", "-reset-methods", "..", "/", "<", "<=",
"=", "==", ">", ">=", "EMPTY-NODE", "Inst", "StackTraceElement->vec",
"Throwable->map", "accessor", "aclone", "add-classpath", "add-watch",
"agent", "agent-error", "agent-errors", "aget", "alength", "alias",
"all-ns", "alter", "alter-meta!", "alter-var-root", "amap", "ancestors",
"and", "any?", "apply", "areduce", "array-map", "as->", "aset",
"aset-boolean", "aset-byte", "aset-char", "aset-double", "aset-float",
"aset-int", "aset-long", "aset-short", "assert", "assoc", "assoc!",
"assoc-in", "associative?", "atom", "await", "await-for", "await1",
"bases", "bean", "bigdec", "bigint", "biginteger", "binding", "bit-and",
"bit-and-not", "bit-clear", "bit-flip", "bit-not", "bit-or", "bit-set",
"bit-shift-left", "bit-shift-right", "bit-test", "bit-xor", "boolean",
"boolean-array", "boolean?", "booleans", "bound-fn", "bound-fn*",
"bound?", "bounded-count", "butlast", "byte", "byte-array", "bytes",
"bytes?", "case", "cast", "cat", "char", "char-array",
"char-escape-string", "char-name-string", "char?", "chars", "chunk",
"chunk-append", "chunk-buffer", "chunk-cons", "chunk-first", "chunk-next",
"chunk-rest", "chunked-seq?", "class", "class?", "clear-agent-errors",
"clojure-version", "coll?", "comment", "commute", "comp", "comparator",
"compare", "compare-and-set!", "compile", "complement", "completing",
"concat", "cond", "cond->", "cond->>", "condp", "conj", "conj!", "cons",
"constantly", "construct-proxy", "contains?", "count", "counted?",
"create-ns", "create-struct", "cycle", "dec", "dec'", "decimal?",
"declare", "dedupe", "default-data-readers", "definline", "definterface",
"defmacro", "defmethod", "defmulti", "defn", "defn-", "defonce",
"defprotocol", "defrecord", "defstruct", "deftype", "delay", "delay?",
"deliver", "denominator", "deref", "derive", "descendants", "destructure",
"disj", "disj!", "dissoc", "dissoc!", "distinct", "distinct?", "doall",
"dorun", "doseq", "dosync", "dotimes", "doto", "double", "double-array",
"double?", "doubles", "drop", "drop-last", "drop-while", "eduction",
"empty", "empty?", "ensure", "ensure-reduced", "enumeration-seq",
"error-handler", "error-mode", "eval", "even?", "every-pred", "every?",
"ex-data", "ex-info", "extend", "extend-protocol", "extend-type",
"extenders", "extends?", "false?", "ffirst", "file-seq", "filter",
"filterv", "find", "find-keyword", "find-ns", "find-protocol-impl",
"find-protocol-method", "find-var", "first", "flatten", "float",
"float-array", "float?", "floats", "flush", "fn", "fn?", "fnext", "fnil",
"for", "force", "format", "frequencies", "future", "future-call",
"future-cancel", "future-cancelled?", "future-done?", "future?",
"gen-class", "gen-interface", "gensym", "get", "get-in", "get-method",
"get-proxy-class", "get-thread-bindings", "get-validator", "group-by",
"halt-when", "hash", "hash-combine", "hash-map", "hash-ordered-coll",
"hash-set", "hash-unordered-coll", "ident?", "identical?", "identity",
"if-let", "if-not", "if-some", "ifn?", "import", "in-ns", "inc", "inc'",
"indexed?", "init-proxy", "inst-ms", "inst-ms*", "inst?", "instance?",
"int", "int-array", "int?", "integer?", "interleave", "intern",
"interpose", "into", "into-array", "ints", "io!", "isa?", "iterate",
"iterator-seq", "juxt", "keep", "keep-indexed", "key", "keys", "keyword",
"keyword?", "last", "lazy-cat", "lazy-seq", "let", "letfn", "line-seq",
"list", "list*", "list?", "load", "load-file", "load-reader",
"load-string", "loaded-libs", "locking", "long", "long-array", "longs",
"loop", "macroexpand", "macroexpand-1", "make-array", "make-hierarchy",
"map", "map-entry?", "map-indexed", "map?", "mapcat", "mapv", "max",
"max-key", "memfn", "memoize", "merge", "merge-with", "meta",
"method-sig", "methods", "min", "min-key", "mix-collection-hash", "mod",
"munge", "name", "namespace", "namespace-munge", "nat-int?", "neg-int?",
"neg?", "newline", "next", "nfirst", "nil?", "nnext", "not", "not-any?",
"not-empty", "not-every?", "not=", "ns", "ns-aliases", "ns-imports",
"ns-interns", "ns-map", "ns-name", "ns-publics", "ns-refers",
"ns-resolve", "ns-unalias", "ns-unmap", "nth", "nthnext", "nthrest",
"num", "number?", "numerator", "object-array", "odd?", "or", "parents",
"partial", "partition", "partition-all", "partition-by", "pcalls", "peek",
"persistent!", "pmap", "pop", "pop!", "pop-thread-bindings", "pos-int?",
"pos?", "pr", "pr-str", "prefer-method", "prefers",
"primitives-classnames", "print", "print-ctor", "print-dup",
"print-method", "print-simple", "print-str", "printf", "println",
"println-str", "prn", "prn-str", "promise", "proxy",
"proxy-call-with-super", "proxy-mappings", "proxy-name", "proxy-super",
"push-thread-bindings", "pvalues", "qualified-ident?",
"qualified-keyword?", "qualified-symbol?", "quot", "rand", "rand-int",
"rand-nth", "random-sample", "range", "ratio?", "rational?",
"rationalize", "re-find", "re-groups", "re-matcher", "re-matches",
"re-pattern", "re-seq", "read", "read-line", "read-string",
"reader-conditional", "reader-conditional?", "realized?", "record?",
"reduce", "reduce-kv", "reduced", "reduced?", "reductions", "ref",
"ref-history-count", "ref-max-history", "ref-min-history", "ref-set",
"refer", "refer-clojure", "reify", "release-pending-sends", "rem",
"remove", "remove-all-methods", "remove-method", "remove-ns",
"remove-watch", "repeat", "repeatedly", "replace", "replicate", "require",
"reset!", "reset-meta!", "reset-vals!", "resolve", "rest",
"restart-agent", "resultset-seq", "reverse", "reversible?", "rseq",
"rsubseq", "run!", "satisfies?", "second", "select-keys", "send",
"send-off", "send-via", "seq", "seq?", "seqable?", "seque", "sequence",
"sequential?", "set", "set-agent-send-executor!",
"set-agent-send-off-executor!", "set-error-handler!", "set-error-mode!",
"set-validator!", "set?", "short", "short-array", "shorts", "shuffle",
"shutdown-agents", "simple-ident?", "simple-keyword?", "simple-symbol?",
"slurp", "some", "some->", "some->>", "some-fn", "some?", "sort",
"sort-by", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by",
"sorted?", "special-symbol?", "spit", "split-at", "split-with", "str",
"string?", "struct", "struct-map", "subs", "subseq", "subvec", "supers",
"swap!", "swap-vals!", "symbol", "symbol?", "sync", "tagged-literal",
"tagged-literal?", "take", "take-last", "take-nth", "take-while", "test",
"the-ns", "thread-bound?", "time", "to-array", "to-array-2d",
"trampoline", "transduce", "transient", "tree-seq", "true?", "type",
"unchecked-add", "unchecked-add-int", "unchecked-byte", "unchecked-char",
"unchecked-dec", "unchecked-dec-int", "unchecked-divide-int",
"unchecked-double", "unchecked-float", "unchecked-inc",
"unchecked-inc-int", "unchecked-int", "unchecked-long",
"unchecked-multiply", "unchecked-multiply-int", "unchecked-negate",
"unchecked-negate-int", "unchecked-remainder-int", "unchecked-short",
"unchecked-subtract", "unchecked-subtract-int", "underive", "unquote",
"unquote-splicing", "unreduced", "unsigned-bit-shift-right", "update",
"update-in", "update-proxy", "uri?", "use", "uuid?", "val", "vals",
"var-get", "var-set", "var?", "vary-meta", "vec", "vector", "vector-of",
"vector?", "volatile!", "volatile?", "vreset!", "vswap!", "when",
"when-first", "when-let", "when-not", "when-some", "while",
"with-bindings", "with-bindings*", "with-in-str", "with-loading-context",
"with-local-vars", "with-meta", "with-open", "with-out-str",
"with-precision", "with-redefs", "with-redefs-fn", "xml-seq", "zero?",
"zipmap"];
var haveBodyParameter = [
"->", "->>", "as->", "binding", "bound-fn", "case", "catch", "comment",
"cond", "cond->", "cond->>", "condp", "def", "definterface", "defmethod",
"defn", "defmacro", "defprotocol", "defrecord", "defstruct", "deftype",
"do", "doseq", "dotimes", "doto", "extend", "extend-protocol",
"extend-type", "fn", "for", "future", "if", "if-let", "if-not", "if-some",
"let", "letfn", "locking", "loop", "ns", "proxy", "reify", "struct-map",
"some->", "some->>", "try", "when", "when-first", "when-let", "when-not",
"when-some", "while", "with-bindings", "with-bindings*", "with-in-str",
"with-loading-context", "with-local-vars", "with-meta", "with-open",
"with-out-str", "with-precision", "with-redefs", "with-redefs-fn"];
function makeKeywords(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
CodeMirror.registerHelper("hintWords", "clojure",
[].concat(atoms, specialForms, coreSymbols));
var atom = createLookupMap(atoms);
var specialForm = createLookupMap(specialForms);
var coreSymbol = createLookupMap(coreSymbols);
var hasBodyParameter = createLookupMap(haveBodyParameter);
var delimiter = /^(?:[\\\[\]\s"(),;@^`{}~]|$)/;
var numberLiteral = /^(?:[+\-]?\d+(?:(?:N|(?:[eE][+\-]?\d+))|(?:\.?\d*(?:M|(?:[eE][+\-]?\d+))?)|\/\d+|[xX][0-9a-fA-F]+|r[0-9a-zA-Z]+)?(?=[\\\[\]\s"#'(),;@^`{}~]|$))/;
var characterLiteral = /^(?:\\(?:backspace|formfeed|newline|return|space|tab|o[0-7]{3}|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{4}|.)?(?=[\\\[\]\s"(),;@^`{}~]|$))/;
// simple-namespace := /^[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*/
// simple-symbol := /^(?:\/|[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)/
// qualified-symbol := (<simple-namespace>(<.><simple-namespace>)*</>)?<simple-symbol>
var qualifiedSymbol = /^(?:(?:[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*(?:\.[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)*\/)?(?:\/|[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)*(?=[\\\[\]\s"(),;@^`{}~]|$))/;
function base(stream, state) {
if (stream.eatSpace() || stream.eat(",")) return ["space", null];
if (stream.match(numberLiteral)) return [null, "number"];
if (stream.match(characterLiteral)) return [null, "string-2"];
if (stream.eat(/^"/)) return (state.tokenize = inString)(stream, state);
if (stream.eat(/^[(\[{]/)) return ["open", "bracket"];
if (stream.eat(/^[)\]}]/)) return ["close", "bracket"];
if (stream.eat(/^;/)) {stream.skipToEnd(); return ["space", "comment"];}
if (stream.eat(/^[#'@^`~]/)) return [null, "meta"];
var matches = stream.match(qualifiedSymbol);
var symbol = matches && matches[0];
if (!symbol) {
// advance stream by at least one character so we don't get stuck.
stream.next();
stream.eatWhile(function (c) {return !is(c, delimiter);});
return [null, "error"];
}
var atoms = makeKeywords("true false nil");
if (symbol === "comment" && state.lastToken === "(")
return (state.tokenize = inComment)(stream, state);
if (is(symbol, atom) || symbol.charAt(0) === ":") return ["symbol", "atom"];
if (is(symbol, specialForm) || is(symbol, coreSymbol)) return ["symbol", "keyword"];
if (state.lastToken === "(") return ["symbol", "builtin"]; // other operator
var keywords = makeKeywords(
"defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest " +
"slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn " +
"do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync " +
"doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars " +
"binding gen-class gen-and-load-class gen-and-save-class handler-case handle");
return ["symbol", "variable"];
}
var builtins = makeKeywords(
"* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* " +
"*compile-path* *compiler-options* *data-readers* *default-data-reader-fn* *e *err* *file* *flush-on-newline* *fn-loader* *in* " +
"*math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-namespace-maps* *print-readably* *read-eval* *reader-resolver* " +
"*source-path* *suppress-read* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> " +
"->> ->ArrayChunk ->Eduction ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE Inst StackTraceElement->vec Throwable->map accessor " +
"aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! " +
"alter-var-root amap ancestors and any? apply areduce array-map as-> aset aset-boolean aset-byte aset-char aset-double " +
"aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 " +
"bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set " +
"bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array boolean? booleans bound-fn bound-fn* bound? bounded-count butlast " +
"byte byte-array bytes bytes? case cast cat char char-array char-escape-string char-name-string char? chars chunk chunk-append " +
"chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors " +
"clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement completing concat cond cond-> cond->> condp " +
"conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? " +
"declare dedupe default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol " +
"defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc " +
"dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array double? doubles drop drop-last " +
"drop-while eduction empty empty? ensure ensure-reduced enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info " +
"extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword " +
"find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? " +
"fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? " +
"gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by halt-when hash " +
"hash-combine hash-map hash-ordered-coll hash-set hash-unordered-coll ident? identical? identity if-let if-not if-some ifn? import in-ns inc inc' indexed? init-proxy inst-ms inst-ms* inst? instance? " +
"int int-array int? integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep " +
"keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file " +
"load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array " +
"make-hierarchy map map-entry? map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods " +
"min min-key mix-collection-hash mod munge name namespace namespace-munge nat-int? neg-int? neg? newline next nfirst nil? nnext not not-any? not-empty " +
"not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias " +
"ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all " +
"partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos-int? pos? pr pr-str prefer-method prefers " +
"primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str " +
"prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues qualified-ident? qualified-keyword? qualified-symbol? " +
"quot rand rand-int rand-nth random-sample range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern " +
"re-seq read read-line read-string reader-conditional reader-conditional? realized? record? reduce reduce-kv reduced reduced? reductions ref ref-history-count ref-max-history " +
"ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods " +
"remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! reset-vals! resolve rest " +
"restart-agent resultset-seq reverse reversible? rseq rsubseq run! satisfies? second select-keys send send-off send-via seq seq? seqable? " +
"seque sequence sequential? set set-agent-send-executor! set-agent-send-off-executor! set-error-handler! set-error-mode! set-validator! set? short short-array shorts " +
"shuffle shutdown-agents simple-ident? simple-keyword? simple-symbol? slurp some some-> some->> some-fn some? sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? " +
"special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! swap-vals! symbol " +
"symbol? sync tagged-literal tagged-literal? take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transduce " +
"transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec " +
"unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int " +
"unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int " +
"unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote " +
"unquote-splicing unreduced unsigned-bit-shift-right update update-in update-proxy uri? use uuid? val vals var-get var-set var? vary-meta vec vector vector-of " +
"vector? volatile! volatile? vreset! vswap! when when-first when-let when-not when-some while with-bindings with-bindings* with-in-str with-loading-context " +
"with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap");
function inString(stream, state) {
var escaped = false, next;
var indentKeys = makeKeywords(
// Built-ins
"ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto " +
"locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type " +
"try catch " +
// Binding forms
"let letfn binding loop for doseq dotimes when-let if-let " +
// Data structures
"defstruct struct-map assoc " +
// clojure.test
"testing deftest " +
// contrib
"handler-case handle dotrace deftrace");
var tests = {
digit: /\d/,
digit_or_colon: /[\d:]/,
hex: /[0-9a-f]/i,
sign: /[+-]/,
exponent: /e/i,
keyword_char: /[^\s\(\[\;\)\]]/,
symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/,
block_indent: /^(?:def|with)[^\/]+$|\/(?:def|with)/
};
function stateStack(indent, type, prev) { // represents a state stack object
this.indent = indent;
this.type = type;
this.prev = prev;
while (next = stream.next()) {
if (next === "\"" && !escaped) {state.tokenize = base; break;}
escaped = !escaped && next === "\\";
}
function pushStack(state, indent, type) {
state.indentStack = new stateStack(indent, type, state.indentStack);
return [null, "string"];
}
function inComment(stream, state) {
var parenthesisCount = 1;
var next;
while (next = stream.next()) {
if (next === ")") parenthesisCount--;
if (next === "(") parenthesisCount++;
if (parenthesisCount === 0) {
stream.backUp(1);
state.tokenize = base;
break;
}
}
function popStack(state) {
state.indentStack = state.indentStack.prev;
}
return ["space", "comment"];
}
function isNumber(ch, stream){
// hex
if ( ch === '0' && stream.eat(/x/i) ) {
stream.eatWhile(tests.hex);
return true;
function createLookupMap(words) {
var obj = {};
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
function is(value, test) {
if (test instanceof RegExp) return test.test(value);
if (test instanceof Object) return test.propertyIsEnumerable(value);
}
return {
startState: function () {
return {
ctx: {prev: null, start: 0, indentTo: 0},
lastToken: null,
tokenize: base
};
},
token: function (stream, state) {
if (stream.sol() && (typeof state.ctx.indentTo !== "number"))
state.ctx.indentTo = state.ctx.start + 1;
var typeStylePair = state.tokenize(stream, state);
var type = typeStylePair[0];
var style = typeStylePair[1];
var current = stream.current();
if (type !== "space") {
if (state.lastToken === "(" && state.ctx.indentTo === null) {
if (type === "symbol" && is(current, hasBodyParameter))
state.ctx.indentTo = state.ctx.start + options.indentUnit;
else state.ctx.indentTo = "next";
} else if (state.ctx.indentTo === "next") {
state.ctx.indentTo = stream.column();
}
// leading sign
if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
stream.eat(tests.sign);
ch = stream.next();
}
state.lastToken = current;
}
if ( tests.digit.test(ch) ) {
stream.eat(ch);
stream.eatWhile(tests.digit);
if (type === "open")
state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null};
else if (type === "close") state.ctx = state.ctx.prev || state.ctx;
if ( '.' == stream.peek() ) {
stream.eat('.');
stream.eatWhile(tests.digit);
} else if ('/' == stream.peek() ) {
stream.eat('/');
stream.eatWhile(tests.digit);
}
return style;
},
if ( stream.eat(tests.exponent) ) {
stream.eat(tests.sign);
stream.eatWhile(tests.digit);
}
indent: function (state) {
var i = state.ctx.indentTo;
return true;
}
return (typeof i === "number") ?
i :
state.ctx.start + 1;
},
return false;
}
// Eat character that starts after backslash \
function eatCharacter(stream) {
var first = stream.next();
// Read special literals: backspace, newline, space, return.
// Just read all lowercase letters.
if (first && first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
return;
}
// Read unicode character: \u1000 \uA0a1
if (first === "u") {
stream.match(/[0-9a-z]{4}/i, true);
}
}
return {
startState: function () {
return {
indentStack: null,
indentation: 0,
mode: false
};
},
token: function (stream, state) {
if (state.indentStack == null && stream.sol()) {
// update indentation, but only if indentStack is empty
state.indentation = stream.indentation();
}
// skip spaces
if (state.mode != "string" && stream.eatSpace()) {
return null;
}
var returnType = null;
switch(state.mode){
case "string": // multi-line string parsing mode
var next, escaped = false;
while ((next = stream.next()) != null) {
if (next == "\"" && !escaped) {
state.mode = false;
break;
}
escaped = !escaped && next == "\\";
}
returnType = STRING; // continue on in string mode
break;
default: // default parsing mode
var ch = stream.next();
if (ch == "\"") {
state.mode = "string";
returnType = STRING;
} else if (ch == "\\") {
eatCharacter(stream);
returnType = CHARACTER;
} else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
returnType = ATOM;
} else if (ch == ";") { // comment
stream.skipToEnd(); // rest of the line is a comment
returnType = COMMENT;
} else if (isNumber(ch,stream)){
returnType = NUMBER;
} else if (ch == "(" || ch == "[" || ch == "{" ) {
var keyWord = '', indentTemp = stream.column(), letter;
/**
Either
(indent-word ..
(non-indent-word ..
(;something else, bracket, etc.
*/
if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
keyWord += letter;
}
if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
tests.block_indent.test(keyWord))) { // indent-word
pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
} else { // non-indent word
// we continue eating the spaces
stream.eatSpace();
if (stream.eol() || stream.peek() == ";") {
// nothing significant after
// we restart indentation the user defined spaces after
pushStack(state, indentTemp + NORMAL_INDENT_UNIT, ch);
} else {
pushStack(state, indentTemp + stream.current().length, ch); // else we match
}
}
stream.backUp(stream.current().length - 1); // undo all the eating
returnType = BRACKET;
} else if (ch == ")" || ch == "]" || ch == "}") {
returnType = BRACKET;
if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) {
popStack(state);
}
} else if ( ch == ":" ) {
stream.eatWhile(tests.symbol);
return ATOM;
} else {
stream.eatWhile(tests.symbol);
if (keywords && keywords.propertyIsEnumerable(stream.current())) {
returnType = KEYWORD;
} else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
returnType = BUILTIN;
} else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
returnType = ATOM;
} else {
returnType = VAR;
}
}
}
return returnType;
},
indent: function (state) {
if (state.indentStack == null) return state.indentation;
return state.indentStack.indent;
},
closeBrackets: {pairs: "()[]{}\"\""},
lineComment: ";;"
};
closeBrackets: {pairs: "()[]{}\"\""},
lineComment: ";;"
};
});
CodeMirror.defineMIME("text/x-clojure", "clojure");

View File

@ -63,7 +63,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
if (/[\d.]/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (stream.match(/^-[\w\\\-]+/)) {
} else if (stream.match(/^-[\w\\\-]*/)) {
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ret("variable-2", "variable-definition");
@ -77,12 +77,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return ret("qualifier", "qualifier");
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch);
} else if (((ch == "u" || ch == "U") && stream.match(/rl(-prefix)?\(/i)) ||
((ch == "d" || ch == "D") && stream.match("omain(", true, true)) ||
((ch == "r" || ch == "R") && stream.match("egexp(", true, true))) {
stream.backUp(1);
state.tokenize = tokenParenthesized;
return ret("property", "word");
} else if (stream.match(/[\w-.]+(?=\()/)) {
if (/^(url(-prefix)?|domain|regexp)$/.test(stream.current().toLowerCase())) {
state.tokenize = tokenParenthesized;
}
return ret("variable callee", "variable");
} else if (/[\w\\\-]/.test(ch)) {
stream.eatWhile(/[\w\\\-]/);
return ret("property", "word");
@ -501,7 +500,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"margin-bottom", "margin-left", "margin-right", "margin-top",
"marks", "marquee-direction", "marquee-loop",
"marquee-play-count", "marquee-speed", "marquee-style", "max-height",
"max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
"max-width", "min-height", "min-width", "mix-blend-mode", "move-to", "nav-down", "nav-index",
"nav-left", "nav-right", "nav-up", "object-fit", "object-position",
"opacity", "order", "orphans", "outline",
"outline-color", "outline-offset", "outline-style", "outline-width",

View File

@ -105,7 +105,7 @@
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
};
state.localMode = mode;
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, ""));
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", ""));
} else if (state.inTag) {
state.inTag += stream.current()
if (stream.eol()) state.inTag += " "
@ -135,7 +135,7 @@
indent: function (state, textAfter, line) {
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter);
return htmlMode.indent(state.htmlState, textAfter, line);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter, line);
else

View File

@ -67,7 +67,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
@ -75,10 +75,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.match(/^(?:x[\da-f]+|o[0-7]+|b[01]+)n?/i)) {
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:n|(?:\.\d*)?(?:[eE][+\-]?\d+)?)?/);
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
@ -101,6 +101,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (ch == "<" && stream.match("!--") || ch == "-" && stream.match("->")) {
stream.skipToEnd()
return ret("comment", "comment")
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {
@ -195,8 +198,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/]/.test(ch)) {
return;
} else if (/["'\/`]/.test(ch)) {
for (;; --pos) {
if (pos == 0) return
var next = stream.string.charAt(pos - 1)
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
}
} else if (sawSomething && !depth) {
++pos;
break;
@ -365,7 +372,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), className, poplex); }
if (type == "class" || (isTS && value == "interface")) {
cx.marked = "keyword"
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
if (isTS && value == "declare") {
cx.marked = "keyword"
@ -373,11 +383,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
if (value == "enum") return cont(enumdef);
else if (value == "type") return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, block, poplex)
return cont(pushlex("form"), expression, statement, poplex)
} else if (isTS && value == "abstract") {
cx.marked = "keyword"
return cont(statement)
@ -552,6 +562,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}, proceed);
}
if (type == end || value == end) return cont();
if (sep && sep.indexOf(";") > -1) return pass(what)
return cont(expect(end));
}
return function(type, value) {
@ -574,6 +585,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (value == "?") return cont(maybetype);
}
}
function maybetypeOrIn(type, value) {
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
}
function mayberettype(type) {
if (isTS && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
@ -587,18 +601,19 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
}
function typeexpr(type, value) {
if (value == "keyof" || value == "typeof") {
if (value == "keyof" || value == "typeof" || value == "infer") {
cx.marked = "keyword"
return cont(value == "keyof" ? typeexpr : expressionNoComma)
return cont(value == "typeof" ? expressionNoComma : typeexpr)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
return cont(afterType)
}
if (value == "|" || value == "&") return cont(typeexpr)
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
}
function maybeReturnType(type) {
@ -608,24 +623,28 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?") {
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expression, maybetype, expect("]"), typeprop)
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
} else if (type == "(") {
return pass(functiondecl, typeprop)
}
}
function typearg(type, value) {
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
if (type == ":") return cont(typeexpr)
if (type == "spread") return cont(typearg)
return pass(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
if (type == "[") return cont(expect("]"), afterType)
if (type == "[") return cont(typeexpr, expect("]"), afterType)
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
}
function maybeTypeArgs(_, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
@ -644,7 +663,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(pattern, "]");
if (type == "[") return contCommasep(eltpattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
@ -655,8 +674,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
return cont(expect(":"), pattern, maybeAssign);
}
function eltpattern() {
return pass(pattern, maybeAssign)
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
@ -668,25 +691,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}
function forspec(type, value) {
if (value == "await") return cont(forspec);
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
if (type == "var") return cont(vardef, forspec2);
if (type == "variable") return cont(forspec2);
return pass(forspec2)
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
if (type == ")") return cont()
if (type == ";") return cont(forspec2)
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
return pass(expression, forspec2)
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
@ -694,10 +710,25 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
}
function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
cx.marked = "type"
return cont(typename)
} else if (value == "<") {
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
}
}
function funarg(type, value) {
if (value == "@") cont(expression, funarg)
if (type == "spread") return cont(funarg);
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
if (isTS && type == "this") return cont(maybetype, maybeAssign)
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
@ -728,13 +759,15 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
cx.marked = "property";
return cont(isTS ? classfield : functiondef, classBody);
}
if (type == "number" || type == "string") return cont(isTS ? classfield : functiondef, classBody);
if (type == "[")
return cont(expression, maybetype, expect("]"), isTS ? classfield : functiondef, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (type == ";") return cont(classBody);
if (isTS && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
@ -742,7 +775,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
return pass(functiondef)
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
return pass(isInterface ? functiondecl : functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }

View File

@ -32,7 +32,7 @@
function flatXMLIndent(state) {
var tagName = state.tagName
state.tagName = null
var result = xmlMode.indent(state, "")
var result = xmlMode.indent(state, "", "")
state.tagName = tagName
return result
}
@ -105,7 +105,7 @@
function jsToken(stream, state, cx) {
if (stream.peek() == "<" && jsMode.expressionAllowed(stream, cx.state)) {
jsMode.skipExpression(cx.state)
state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "")),
state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "", "")),
xmlMode, 0, state.context)
return null
}

View File

@ -390,6 +390,17 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
skipAttribute: function(state) {
if (state.state == attrValueState)
state.state = attrState
},
xmlCurrentTag: function(state) {
return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
},
xmlCurrentContext: function(state) {
var context = []
for (var cx = state.context; cx; cx = cx.prev)
if (cx.tagName) context.push(cx.tagName)
return context.reverse()
}
};
});

View File

@ -127,6 +127,9 @@
"[keyword let] [def f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];",
"[variable c];");
MT("fatArrow_stringDefault",
"([def a], [def b] [operator =] [string 'x\\'y']) [operator =>] [variable-2 a] [operator +] [variable-2 b]")
MT("spread",
"[keyword function] [def f]([def a], [meta ...][def b]) {",
" [variable something]([variable-2 a], [meta ...][variable-2 b]);",
@ -226,6 +229,12 @@
" [keyword return] [variable-2 x];",
"}");
MT(
"param_destructuring",
"[keyword function] [def foo]([def x] [operator =] [string-2 `foo${][number 10][string-2 }bar`]) {",
" [keyword return] [variable-2 x];",
"}");
MT("new_target",
"[keyword function] [def F]([def target]) {",
" [keyword if] ([variable-2 target] [operator &&] [keyword new].[keyword target].[property name]) {",
@ -292,6 +301,22 @@
"[keyword return]",
"{} [string-2 /5/]")
MT("numeric separator",
"[number 123_456];",
"[number 0xdead_c0de];",
"[number 0o123_456];",
"[number 0b1101_1101];",
"[number .123_456e0_1];",
"[number 1E+123_456];",
"[number 12_34_56n];")
MT("underscore property",
"[variable something].[property _property];",
"[variable something].[property _123];",
"[variable something].[property _for];",
"[variable _for];",
"[variable _123];")
var ts_mode = CodeMirror.getMode({indentUnit: 2}, "application/typescript")
function TS(name) {
test.mode(name, ts_mode, Array.prototype.slice.call(arguments, 1))
@ -442,6 +467,12 @@
TS("abstract class",
"[keyword export] [keyword abstract] [keyword class] [def Foo] {}")
TS("interface without semicolons",
"[keyword interface] [def Foo] {",
" [property greet]([def x]: [type int]): [type blah]",
" [property bar]: [type void]",
"}")
var jsonld_mode = CodeMirror.getMode(
{indentUnit: 2},
{name: "javascript", jsonld: true}

View File

@ -123,7 +123,7 @@
var line = lines[i], newLine = true;
if (mode.indent) {
var ws = line.match(/^\s*/)[0];
var indent = mode.indent(state, line.slice(ws.length));
var indent = mode.indent(state, line.slice(ws.length), line);
if (indent != CodeMirror.Pass && indent != ws.length)
(st.indentFailures || (st.indentFailures = [])).push(
"Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")");

View File

@ -282,4 +282,14 @@
cm.execCommand("redoSelection");
eq(cm.getSelection(), "1\n2\n3");
}, {value: "1 2 3"});
testCM("selectionsMayTouch", function(cm) {
select(cm, Pos(0, 0), Pos(0, 2))
cm.setExtending(true);
cm.extendSelections([Pos(0, 2), Pos(0, 4)])
hasSelections(cm, 0, 0, 0, 2,
0, 2, 0, 4)
cm.extendSelections([Pos(0, 3), Pos(0, 4)])
hasSelections(cm, 0, 0, 0, 4)
}, {selectionsMayTouch: true, value: "1234"})
})();

View File

@ -549,9 +549,22 @@ testCM("markTextCSS", function(cm) {
function present() {
var spans = cm.display.lineDiv.getElementsByTagName("span");
for (var i = 0; i < spans.length; i++)
if (spans[i].style.color == "cyan" && span[i].textContent == "cdefg") return true;
if (spans[i].style.color && spans[i].textContent == "cdef") return true;
}
var m = cm.markText(Pos(0, 2), Pos(0, 6), {css: "color: cyan"});
is(present());
m.clear();
is(!present());
}, {value: "abcdefgh"});
testCM("markTextWithAttributes", function(cm) {
function present() {
var spans = cm.display.lineDiv.getElementsByTagName("span");
for (var i = 0; i < spans.length; i++)
if (spans[i].getAttribute("label") == "label" && spans[i].textContent == "cdef") return true;
}
var m = cm.markText(Pos(0, 2), Pos(0, 6), {attributes: {label: "label"}});
is(present());
m.clear();
is(!present());
}, {value: "abcdefgh"});
@ -1795,10 +1808,21 @@ testCM("addLineClass", function(cm) {
testCM("atomicMarker", function(cm) {
addDoc(cm, 10, 10);
function atom(ll, cl, lr, cr, li, ri) {
return cm.markText(Pos(ll, cl), Pos(lr, cr),
{atomic: true, inclusiveLeft: li, inclusiveRight: ri});
function atom(ll, cl, lr, cr, li, ri, ls, rs) {
var options = {
atomic: true,
inclusiveLeft: li,
inclusiveRight: ri
};
if (ls === true || ls === false) options["selectLeft"] = ls;
if (rs === true || rs === false) options["selectRight"] = rs;
return cm.markText(Pos(ll, cl), Pos(lr, cr), options);
}
// Can cursor to the left and right of a normal marker by jumping across it
var m = atom(0, 1, 0, 5);
cm.setCursor(Pos(0, 1));
cm.execCommand("goCharRight");
@ -1806,11 +1830,55 @@ testCM("atomicMarker", function(cm) {
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 1));
m.clear();
// Can't cursor to the left of a marker when inclusiveLeft=true
m = atom(0, 0, 0, 5, true);
eqCursorPos(cm.getCursor(), Pos(0, 5), "pushed out");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 5));
m.clear();
// Can't cursor to the left of a marker when inclusiveLeft=false and selectLeft=false
m = atom(0, 0, 0, 5, false, false, false);
cm.setCursor(Pos(0, 5));
eqCursorPos(cm.getCursor(), Pos(0, 5), "pushed out");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 5));
m.clear();
// Can cursor to the left of a marker when inclusiveLeft=false and selectLeft=True
m = atom(0, 0, 0, 5, false, false, true);
cm.setCursor(Pos(0, 5));
eqCursorPos(cm.getCursor(), Pos(0, 5), "pushed out");
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(0, 0));
m.clear();
// Can't cursor to the right of a marker when inclusiveRight=true
m = atom(0, 0, 0, 5, false, true);
cm.setCursor(Pos(0, 0));
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 6));
m.clear();
// Can't cursor to the right of a marker when inclusiveRight=false and selectRight=false
m = atom(0, 0, 0, 5, false, false, true, false);
cm.setCursor(Pos(0, 0));
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 6));
m.clear();
// Can cursor to the right of a marker when inclusiveRight=false and selectRight=True
m = atom(0, 0, 0, 5, false, false, true, true);
cm.setCursor(Pos(0, 0));
eqCursorPos(cm.getCursor(), Pos(0, 0));
cm.execCommand("goCharRight");
eqCursorPos(cm.getCursor(), Pos(0, 5));
m.clear();
// Can't cursor to the right of a multiline marker when inclusiveRight=true
m = atom(8, 4, 9, 10, false, true);
cm.setCursor(Pos(9, 8));
eqCursorPos(cm.getCursor(), Pos(8, 4), "set");
@ -1821,6 +1889,9 @@ testCM("atomicMarker", function(cm) {
cm.execCommand("goCharLeft");
eqCursorPos(cm.getCursor(), Pos(8, 3, "after"));
m.clear();
// Cursor jumps across a multiline atomic marker,
// and backspace deletes the entire marker
m = atom(1, 1, 3, 8);
cm.setCursor(Pos(0, 0));
cm.setCursor(Pos(2, 0));
@ -1835,10 +1906,16 @@ testCM("atomicMarker", function(cm) {
eqCursorPos(cm.getCursor(), Pos(3, 8));
cm.execCommand("delCharBefore");
eq(cm.getValue().length, 80, "del chunk");
m.clear();
addDoc(cm, 10, 10);
// Delete before an atomic marker deletes the entire marker
m = atom(3, 0, 5, 5);
cm.setCursor(Pos(3, 0));
cm.execCommand("delWordAfter");
eq(cm.getValue().length, 53, "del chunk");
eq(cm.getValue().length, 82, "del chunk");
m.clear();
addDoc(cm, 10, 10);
});
testCM("selectionBias", function(cm) {

View File

@ -105,6 +105,15 @@ function forEach(arr, func) {
}
}
function expectFail(fn) {
try {
fn();
} catch(expected) {
return;
};
throw new Error("Expected to throw an error");
}
function testVim(name, run, opts, expectedFail) {
var vimOpts = {
lineNumbers: true,
@ -323,6 +332,8 @@ testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end);
testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4));
testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4));
testMotion('w', 'w', word1.start);
testMotion('keepHPos', ['5', 'j', 'j', '7', 'k'], makeCursor(8, 12), makeCursor(12, 12));
testMotion('keepHPosEol', ['$', '2', 'j'], makeCursor(2, 18));
testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2));
testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51));
testMotion('w_repeat', ['2', 'w'], word2.start);
@ -1054,27 +1065,45 @@ function fillArray(val, times) {
testVim('c_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'c');
var replacement = fillArray('hello', 3);
cm.replaceSelections(replacement);
helpers.doKeys('hello');
eq('1hello\n5hello\nahellofg', cm.getValue());
helpers.doKeys('<Esc>');
cm.setCursor(2, 3);
helpers.doKeys('<C-v>', '2', 'k', 'h', 'C');
replacement = fillArray('world', 3);
cm.replaceSelections(replacement);
helpers.doKeys('world');
eq('1hworld\n5hworld\nahworld', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
testVim('c_visual_block_replay', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'c');
var replacement = fillArray('fo', 3);
cm.replaceSelections(replacement);
helpers.doKeys('fo');
eq('1fo4\n5fo8\nafodefg', cm.getValue());
helpers.doKeys('<Esc>');
cm.setCursor(0, 0);
helpers.doKeys('.');
eq('foo4\nfoo8\nfoodefg', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
testVim('I_visual_block_replay', function(cm, vim, helpers) {
cm.setCursor(0, 2);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'I');
helpers.doKeys('+-')
eq('12+-34\n56+-78\nab+-cdefg\nxyz', cm.getValue());
helpers.doKeys('<Esc>');
// ensure that repeat location doesn't depend on last selection
cm.setCursor(3, 2);
helpers.doKeys('g', 'v')
eq("+-34\n+-78\n+-cd", cm.getSelection())
cm.setCursor(0, 3);
helpers.doKeys('<C-v>', '1', 'j', '2', 'l');
eq("-34\n-78", cm.getSelection());
cm.setCursor(0, 0);
eq("", cm.getSelection());
helpers.doKeys('g', 'v');
eq("-34\n-78", cm.getSelection());
cm.setCursor(1, 1);
helpers.doKeys('.');
eq('12+-34\n5+-6+-78\na+-b+-cdefg\nx+-yz', cm.getValue());
}, {value: '1234\n5678\nabcdefg\nxyz'});
testVim('d_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
@ -1090,14 +1119,12 @@ testVim('D_visual_block', function(cm, vim, helpers) {
testVim('s_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 's');
var replacement = fillArray('hello{', 3);
cm.replaceSelections(replacement);
helpers.doKeys('hello{');
eq('1hello{\n5hello{\nahello{fg\n', cm.getValue());
helpers.doKeys('<Esc>');
cm.setCursor(2, 3);
helpers.doKeys('<C-v>', '1', 'k', 'h', 'S');
replacement = fillArray('world', 1);
cm.replaceSelections(replacement);
helpers.doKeys('world');
eq('1hello{\n world\n', cm.getValue());
}, {value: '1234\n5678\nabcdefg\n'});
@ -1222,6 +1249,13 @@ testVim('<<', function(cm, vim, helpers) {
is(!register.linewise);
helpers.assertCursorAt(0, 1);
}, { value: ' word1\n word2\nword3 ', indentUnit: 2 });
testVim('=', function(cm, vim, helpers) {
cm.setCursor(0, 3);
helpers.doKeys('<C-v>', 'j', 'j');
var expectedValue = 'word1\nword2\nword3';
helpers.doKeys('=');
eq(expectedValue, cm.getValue());
}, { value: ' word1\n word2\n word3', indentUnit: 2 });
// Edit tests
function testEdit(name, before, pos, edit, after) {
@ -1287,6 +1321,10 @@ testEdit('di)_close_spc', 'foo (bAr) baz', /\)/, 'di)', 'foo () baz');
testEdit('da(_close_spc', 'foo (bAr) baz', /\)/, 'da(', 'foo baz');
testEdit('da)_close_spc', 'foo (bAr) baz', /\)/, 'da)', 'foo baz');
testEdit('di`', 'foo `bAr` baz', /`/, 'di`', 'foo `` baz');
testEdit('di>', 'foo <bAr> baz', /</, 'di>', 'foo <> baz');
testEdit('da<', 'foo <bAr> baz', /</, 'da<', 'foo baz');
// delete around and inner b.
testEdit('dab_on_(_should_delete_around_()block', 'o( in(abc) )', /\(a/, 'dab', 'o( in )');
@ -1320,6 +1358,12 @@ testEdit('di]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di]', 'a\t[]b');
testEdit('da[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da[', 'a\tb');
testEdit('da]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da]', 'a\tb');
// open and close on diff lines, open indented more than close
testEdit('di<_middle_spc', 'a\t<\n\tbar\n>b', /r/, 'di<', 'a\t<>b');
testEdit('di>_middle_spc', 'a\t<\n\tbar\n>b', /r/, 'di>', 'a\t<>b');
testEdit('da<_middle_spc', 'a\t<\n\tbar\n>b', /r/, 'da<', 'a\tb');
testEdit('da>_middle_spc', 'a\t<\n\tbar\n>b', /r/, 'da>', 'a\tb');
function testSelection(name, before, pos, keys, sel) {
return testVim(name, function(cm, vim, helpers) {
var ch = before.search(pos)
@ -1384,6 +1428,21 @@ testVim('Y', function(cm, vim, helpers) {
is(register.linewise);
helpers.assertCursorAt(0, 3);
}, { value: ' word1\nword2\n word3' });
testVim('Yy_blockwise', function(cm, vim, helpers) {
helpers.doKeys('<C-v>', 'j', '2', 'l', 'Y');
helpers.doKeys('G', 'p', 'g', 'g');
helpers.doKeys('<C-v>', 'j', '2', 'l', 'y');
helpers.assertCursorAt(0, 0);
helpers.doKeys('$', 'p');
eq('123456123\n123456123\n123456\n123456', cm.getValue());
var register = helpers.getRegisterController().getRegister();
eq('123\n123', register.toString());
is(register.blockwise);
helpers.assertCursorAt(0, 6);
helpers.doKeys('$', 'j', 'p');
helpers.doKeys('$', 'j', 'P');
eq("123456123\n123456123123\n123456 121233\n123456 123", cm.getValue());
}, { value: '123456\n123456\n' });
testVim('~', function(cm, vim, helpers) {
helpers.doKeys('3', '~');
eq('ABCdefg', cm.getValue());
@ -1433,6 +1492,16 @@ testVim('insert_ctrl_w', function(cm, vim, helpers) {
eqCursorPos(curEnd, cm.getCursor());
eq('vim-insert', cm.getOption('keyMap'));
}, { value: 'word1/word2' });
testVim('normal_ctrl_w', function(cm, vim, helpers) {
var curStart = makeCursor(0, 3);
cm.setCursor(curStart);
helpers.doKeys('<C-w>');
eq('word', cm.getValue());
var curEnd = makeCursor(0, 3);
helpers.assertCursorAt(0,3);
eqCursorPos(curEnd, cm.getCursor());
eq('vim', cm.getOption('keyMap'));
}, {value: 'word'});
testVim('a', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('a');
@ -1460,7 +1529,7 @@ testVim('i', function(cm, vim, helpers) {
});
testVim('i_repeat', function(cm, vim, helpers) {
helpers.doKeys('3', 'i');
cm.replaceRange('test', cm.getCursor());
helpers.doKeys('test')
helpers.doKeys('<Esc>');
eq('testtesttest', cm.getValue());
helpers.assertCursorAt(0, 11);
@ -1468,7 +1537,7 @@ testVim('i_repeat', function(cm, vim, helpers) {
testVim('i_repeat_delete', function(cm, vim, helpers) {
cm.setCursor(0, 4);
helpers.doKeys('2', 'i');
cm.replaceRange('z', cm.getCursor());
helpers.doKeys('z')
helpers.doInsertModeKeys('Backspace', 'Backspace');
helpers.doKeys('<Esc>');
eq('abe', cm.getValue());
@ -1500,6 +1569,31 @@ testVim('i_overwrite_backspace', function(cm, vim, helpers) {
helpers.assertCursorAt(Pos(0, 9, "after"));
eq('0123456789', cm.getValue());
}, { value: '0123456789'});
testVim('i_forward_delete', function(cm, vim, helpers) {
cm.setCursor(0, 3);
helpers.doKeys('i');
helpers.doInsertModeKeys('Delete');
helpers.assertCursorAt(0, 3);
eq('A124\nBCD', cm.getValue());
helpers.doInsertModeKeys('Delete');
helpers.assertCursorAt(0, 3);
eq('A12\nBCD', cm.getValue());
helpers.doInsertModeKeys('Delete');
helpers.assertCursorAt(0, 3);
eq('A12BCD', cm.getValue());
}, { value: 'A1234\nBCD'});
testVim('forward_delete', function(cm, vim, helpers) {
cm.setCursor(0, 3);
helpers.doInsertModeKeys('Delete');
helpers.assertCursorAt(0, 3);
eq('A124\nBCD', cm.getValue());
helpers.doInsertModeKeys('Delete');
helpers.assertCursorAt(0, 2);
eq('A12\nBCD', cm.getValue());
helpers.doInsertModeKeys('Delete');
helpers.assertCursorAt(0, 1);
eq('A1\nBCD', cm.getValue());
}, { value: 'A1234\nBCD'});
testVim('A', function(cm, vim, helpers) {
helpers.doKeys('A');
helpers.assertCursorAt(0, lines[0].length);
@ -1508,9 +1602,7 @@ testVim('A', function(cm, vim, helpers) {
testVim('A_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'A');
var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
replacement.pop();
cm.replaceSelections(replacement);
helpers.doKeys('hello');
eq('testhello\nmehello\npleahellose', cm.getValue());
helpers.doKeys('<Esc>');
cm.setCursor(0, 0);
@ -1527,7 +1619,7 @@ testVim('I', function(cm, vim, helpers) {
testVim('I_repeat', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('3', 'I');
cm.replaceRange('test', cm.getCursor());
helpers.doKeys('test')
helpers.doKeys('<Esc>');
eq('testtesttestblah', cm.getValue());
helpers.assertCursorAt(0, 11);
@ -1535,9 +1627,7 @@ testVim('I_repeat', function(cm, vim, helpers) {
testVim('I_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'I');
var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
replacement.pop();
cm.replaceSelections(replacement);
helpers.doKeys('hello');
eq('hellotest\nhellome\nhelloplease', cm.getValue());
}, {value: 'test\nme\nplease'});
testVim('o', function(cm, vim, helpers) {
@ -1550,7 +1640,7 @@ testVim('o', function(cm, vim, helpers) {
testVim('o_repeat', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('3', 'o');
cm.replaceRange('test', cm.getCursor());
helpers.doKeys('test')
helpers.doKeys('<Esc>');
eq('\ntest\ntest\ntest', cm.getValue());
helpers.assertCursorAt(3, 3);
@ -1674,8 +1764,8 @@ testVim('r_visual_block', function(cm, vim, helpers) {
eq('1 l\n5 l\nalllefg', cm.getValue());
cm.setCursor(2, 0);
helpers.doKeys('o');
helpers.doKeys('\t\t')
helpers.doKeys('<Esc>');
cm.replaceRange('\t\t', cm.getCursor());
helpers.doKeys('<C-v>', 'h', 'h', 'r', 'r');
eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
@ -2515,7 +2605,7 @@ testVim('g#', function(cm, vim, helpers) {
testVim('macro_insert', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', '0', 'i');
cm.replaceRange('foo', cm.getCursor());
helpers.doKeys('foo')
helpers.doKeys('<Esc>');
helpers.doKeys('q', '@', 'a');
eq('foofoo', cm.getValue());
@ -2523,14 +2613,14 @@ testVim('macro_insert', function(cm, vim, helpers) {
testVim('macro_insert_repeat', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', '$', 'a');
cm.replaceRange('larry.', cm.getCursor());
helpers.doKeys('larry.')
helpers.doKeys('<Esc>');
helpers.doKeys('a');
cm.replaceRange('curly.', cm.getCursor());
helpers.doKeys('curly.')
helpers.doKeys('<Esc>');
helpers.doKeys('q');
helpers.doKeys('a');
cm.replaceRange('moe.', cm.getCursor());
helpers.doKeys('moe.')
helpers.doKeys('<Esc>');
helpers.doKeys('@', 'a');
// At this point, the most recent edit should be the 2nd insert change
@ -2596,13 +2686,22 @@ testVim('macro_last_ex_command_register', function (cm, vim, helpers) {
eq('bbbaa', cm.getValue());
helpers.assertCursorAt(0, 2);
}, { value: 'aaaaa'});
testVim('macro_last_run_macro', function (cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', 'C', 'a', '<Esc>', 'q');
helpers.doKeys('q', 'b', 'C', 'b', '<Esc>', 'q');
helpers.doKeys('@', 'a');
helpers.doKeys('d', 'd');
helpers.doKeys('@', '@');
eq('a', cm.getValue());
}, { value: ''});
testVim('macro_parens', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'z', 'i');
cm.replaceRange('(', cm.getCursor());
helpers.doKeys('(')
helpers.doKeys('<Esc>');
helpers.doKeys('e', 'a');
cm.replaceRange(')', cm.getCursor());
helpers.doKeys(')')
helpers.doKeys('<Esc>');
helpers.doKeys('q');
helpers.doKeys('w', '@', 'z');
@ -2612,13 +2711,13 @@ testVim('macro_parens', function(cm, vim, helpers) {
testVim('macro_overwrite', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'z', '0', 'i');
cm.replaceRange('I ', cm.getCursor());
helpers.doKeys('I ')
helpers.doKeys('<Esc>');
helpers.doKeys('q');
helpers.doKeys('e');
// Now replace the macro with something else.
helpers.doKeys('q', 'z', 'a');
cm.replaceRange('.', cm.getCursor());
helpers.doKeys('.')
helpers.doKeys('<Esc>');
helpers.doKeys('q');
helpers.doKeys('e', '@', 'z');
@ -2717,11 +2816,11 @@ testVim('yank_append_word_to_line_register', function(cm, vim, helpers) {
testVim('macro_register', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('q', 'a', 'i');
cm.replaceRange('gangnam', cm.getCursor());
helpers.doKeys('gangnam')
helpers.doKeys('<Esc>');
helpers.doKeys('q');
helpers.doKeys('q', 'b', 'o');
cm.replaceRange('style', cm.getCursor());
helpers.doKeys('style')
helpers.doKeys('<Esc>');
helpers.doKeys('q');
cm.openDialog = helpers.fakeOpenDialog('registers');
@ -2734,7 +2833,7 @@ testVim('macro_register', function(cm, vim, helpers) {
testVim('._register', function(cm,vim,helpers) {
cm.setCursor(0,0);
helpers.doKeys('i');
cm.replaceRange('foo',cm.getCursor());
helpers.doKeys('foo')
helpers.doKeys('<Esc>');
cm.openDialog = helpers.fakeOpenDialog('registers');
cm.openNotification = helpers.fakeOpenNotification(function(text) {
@ -2923,13 +3022,13 @@ testVim('._repeat', function(cm, vim, helpers) {
}, { value: '1 2 3 4 5 6'});
testVim('._insert', function(cm, vim, helpers) {
helpers.doKeys('i');
cm.replaceRange('test', cm.getCursor());
helpers.doKeys('test')
helpers.doKeys('<Esc>');
helpers.doKeys('.');
eq('testestt', cm.getValue());
helpers.assertCursorAt(0, 6);
helpers.doKeys('O');
cm.replaceRange('xyz', cm.getCursor());
helpers.doKeys('xyz')
helpers.doInsertModeKeys('Backspace');
helpers.doInsertModeKeys('Down');
helpers.doKeys('<Esc>');
@ -2939,7 +3038,7 @@ testVim('._insert', function(cm, vim, helpers) {
}, { value: ''});
testVim('._insert_repeat', function(cm, vim, helpers) {
helpers.doKeys('i');
cm.replaceRange('test', cm.getCursor());
helpers.doKeys('test')
cm.setCursor(0, 4);
helpers.doKeys('<Esc>');
helpers.doKeys('2', '.');
@ -2948,7 +3047,7 @@ testVim('._insert_repeat', function(cm, vim, helpers) {
}, { value: ''});
testVim('._repeat_insert', function(cm, vim, helpers) {
helpers.doKeys('3', 'i');
cm.replaceRange('te', cm.getCursor());
helpers.doKeys('te')
cm.setCursor(0, 2);
helpers.doKeys('<Esc>');
helpers.doKeys('.');
@ -2957,7 +3056,7 @@ testVim('._repeat_insert', function(cm, vim, helpers) {
}, { value: ''});
testVim('._insert_o', function(cm, vim, helpers) {
helpers.doKeys('o');
cm.replaceRange('z', cm.getCursor());
helpers.doKeys('z')
cm.setCursor(1, 1);
helpers.doKeys('<Esc>');
helpers.doKeys('.');
@ -2966,7 +3065,7 @@ testVim('._insert_o', function(cm, vim, helpers) {
}, { value: ''});
testVim('._insert_o_repeat', function(cm, vim, helpers) {
helpers.doKeys('o');
cm.replaceRange('z', cm.getCursor());
helpers.doKeys('z')
helpers.doKeys('<Esc>');
cm.setCursor(1, 0);
helpers.doKeys('2', '.');
@ -2975,7 +3074,7 @@ testVim('._insert_o_repeat', function(cm, vim, helpers) {
}, { value: ''});
testVim('._insert_o_indent', function(cm, vim, helpers) {
helpers.doKeys('o');
cm.replaceRange('z', cm.getCursor());
helpers.doKeys('z')
helpers.doKeys('<Esc>');
cm.setCursor(1, 2);
helpers.doKeys('.');
@ -2984,7 +3083,7 @@ testVim('._insert_o_indent', function(cm, vim, helpers) {
}, { value: '{'});
testVim('._insert_cw', function(cm, vim, helpers) {
helpers.doKeys('c', 'w');
cm.replaceRange('test', cm.getCursor());
helpers.doKeys('test')
helpers.doKeys('<Esc>');
cm.setCursor(0, 3);
helpers.doKeys('2', 'l');
@ -2996,7 +3095,7 @@ testVim('._insert_cw_repeat', function(cm, vim, helpers) {
// For some reason, repeat cw in desktop VIM will does not repeat insert mode
// changes. Will conform to that behavior.
helpers.doKeys('c', 'w');
cm.replaceRange('test', cm.getCursor());
helpers.doKeys('test');
helpers.doKeys('<Esc>');
cm.setCursor(0, 4);
helpers.doKeys('l');
@ -3835,6 +3934,29 @@ testSubstitute('ex_substitute_or_word_regex', {
expectedValue: 'five|five \n three|four',
expr: '%s/(one|two)/five/g',
noPcreExpr: '%s/\\(one\\|two\\)/five/g'});
testSubstitute('ex_substitute_forward_slash_regex', {
value: 'forward slash \/ was here',
expectedValue: 'forward slash was here',
expr: '%s#\\/##g',
noPcreExpr: '%s#/##g'});
testVim("ex_substitute_ampersand_pcre", function(cm, vim, helpers) {
cm.setCursor(0, 0);
CodeMirror.Vim.setOption('pcre', true);
helpers.doEx('%s/foo/namespace.&/');
eq("namespace.foo", cm.getValue());
}, { value: 'foo' });
testVim("ex_substitute_ampersand_multiple_pcre", function(cm, vim, helpers) {
cm.setCursor(0, 0);
CodeMirror.Vim.setOption('pcre', true);
helpers.doEx('%s/f.o/namespace.&/');
eq("namespace.foo\nnamespace.fzo", cm.getValue());
}, { value: 'foo\nfzo' });
testVim("ex_escaped_ampersand_should_not_substitute_pcre", function(cm, vim, helpers) {
cm.setCursor(0, 0);
CodeMirror.Vim.setOption('pcre', true);
helpers.doEx('%s/foo/namespace.\\&/');
eq("namespace.&", cm.getValue());
}, { value: 'foo' });
testSubstitute('ex_substitute_backslashslash_regex', {
value: 'one\\two \n three\\four',
expectedValue: 'one,two \n three,four',
@ -3993,11 +4115,9 @@ testVim('set_boolean', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testoption', true, 'boolean');
// Test default value is set.
is(CodeMirror.Vim.getOption('testoption'));
try {
// Test fail to set to non-boolean
CodeMirror.Vim.setOption('testoption', '5');
fail();
} catch (expected) {}
// Test fail to set to non-boolean
var result = CodeMirror.Vim.setOption('testoption', '5');
is(result instanceof Error);
// Test setOption
CodeMirror.Vim.setOption('testoption', false);
is(!CodeMirror.Vim.getOption('testoption'));
@ -4006,11 +4126,10 @@ testVim('ex_set_boolean', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testoption', true, 'boolean');
// Test default value is set.
is(CodeMirror.Vim.getOption('testoption'));
try {
// Test fail to set to non-boolean
helpers.doEx('set testoption=22');
fail();
} catch (expected) {}
is(!cm.state.currentNotificationClose);
// Test fail to set to non-boolean
helpers.doEx('set testoption=22');
is(cm.state.currentNotificationClose);
// Test setOption
helpers.doEx('set notestoption');
is(!CodeMirror.Vim.getOption('testoption'));
@ -4019,16 +4138,12 @@ testVim('set_string', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testoption', 'a', 'string');
// Test default value is set.
eq('a', CodeMirror.Vim.getOption('testoption'));
try {
// Test fail to set non-string.
CodeMirror.Vim.setOption('testoption', true);
fail();
} catch (expected) {}
try {
// Test fail to set 'notestoption'
CodeMirror.Vim.setOption('notestoption', 'b');
fail();
} catch (expected) {}
// Test no fail to set non-string.
var result = CodeMirror.Vim.setOption('testoption', true);
is(!result);
// Test fail to set 'notestoption'
result = CodeMirror.Vim.setOption('notestoption', 'b');
is(result instanceof Error);
// Test setOption
CodeMirror.Vim.setOption('testoption', 'c');
eq('c', CodeMirror.Vim.getOption('testoption'));
@ -4037,11 +4152,10 @@ testVim('ex_set_string', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testopt', 'a', 'string');
// Test default value is set.
eq('a', CodeMirror.Vim.getOption('testopt'));
try {
// Test fail to set 'notestopt'
helpers.doEx('set notestopt=b');
fail();
} catch (expected) {}
// Test fail to set 'notestopt'
is(!cm.state.currentNotificationClose);
helpers.doEx('set notestopt=b');
is(cm.state.currentNotificationClose);
// Test setOption
helpers.doEx('set testopt=c')
eq('c', CodeMirror.Vim.getOption('testopt'));
@ -4087,11 +4201,10 @@ testVim('ex_set_callback', function(cm, vim, helpers) {
CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb);
// Test default value is set.
eq('a', CodeMirror.Vim.getOption('testopt'));
try {
// Test fail to set 'notestopt'
helpers.doEx('set notestopt=b');
fail();
} catch (expected) {}
// Test fail to set 'notestopt'
is(!cm.state.currentNotificationClose);
helpers.doEx('set notestopt=b');
is(cm.state.currentNotificationClose);
// Test setOption (Identical to the string tests, but via callback instead)
helpers.doEx('set testopt=c')
eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global
@ -4140,25 +4253,53 @@ testVim('ex_set_filetype_null', function(cm, vim, helpers) {
helpers.doEx('set filetype=');
eq('null', cm.getMode().name);
});
// TODO: Reset key maps after each test.
testVim('mapclear', function(cm, vim, helpers) {
CodeMirror.Vim.map('w', 'l');
cm.setCursor(0, 0);
helpers.assertCursorAt(0, 0);
helpers.doKeys('w');
helpers.assertCursorAt(0, 1);
CodeMirror.Vim.mapclear('visual');
helpers.doKeys('v', 'w', 'v');
helpers.assertCursorAt(0, 4);
helpers.doKeys('w');
helpers.assertCursorAt(0, 5);
CodeMirror.Vim.mapclear();
}, { value: 'abc abc' });
testVim('mapclear_context', function(cm, vim, helpers) {
CodeMirror.Vim.map('w', 'l', 'normal');
cm.setCursor(0, 0);
helpers.assertCursorAt(0, 0);
helpers.doKeys('w');
helpers.assertCursorAt(0, 1);
CodeMirror.Vim.mapclear('normal');
helpers.doKeys('w');
helpers.assertCursorAt(0, 4);
CodeMirror.Vim.mapclear();
}, { value: 'abc abc' });
testVim('ex_map_key2key', function(cm, vim, helpers) {
helpers.doEx('map a x');
helpers.doKeys('a');
helpers.assertCursorAt(0, 0);
eq('bc', cm.getValue());
CodeMirror.Vim.mapclear();
}, { value: 'abc' });
testVim('ex_unmap_key2key', function(cm, vim, helpers) {
helpers.doEx('map a x');
helpers.doEx('unmap a');
helpers.doKeys('a');
eq('vim-insert', cm.getOption('keyMap'));
CodeMirror.Vim.mapclear();
}, { value: 'abc' });
testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) {
try {
expectFail(function() {
helpers.doEx('unmap a');
fail();
} catch (expected) {}
});
helpers.doKeys('a');
eq('vim-insert', cm.getOption('keyMap'));
CodeMirror.Vim.mapclear();
}, { value: 'abc' });
testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
helpers.doEx('map ; :');
@ -4168,12 +4309,14 @@ testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) {
}
helpers.doKeys(';');
eq(dialogOpened, true);
CodeMirror.Vim.mapclear();
});
testVim('ex_map_ex2key:', function(cm, vim, helpers) {
helpers.doEx('map :del x');
helpers.doEx('del');
helpers.assertCursorAt(0, 0);
eq('bc', cm.getValue());
CodeMirror.Vim.mapclear();
}, { value: 'abc' });
testVim('ex_map_ex2ex', function(cm, vim, helpers) {
helpers.doEx('map :del :w');
@ -4188,6 +4331,7 @@ testVim('ex_map_ex2ex', function(cm, vim, helpers) {
CodeMirror.commands.save = tmp;
eq(written, true);
eq(actualCm, cm);
CodeMirror.Vim.mapclear();
});
testVim('ex_map_key2ex', function(cm, vim, helpers) {
helpers.doEx('map a :w');
@ -4202,6 +4346,7 @@ testVim('ex_map_key2ex', function(cm, vim, helpers) {
CodeMirror.commands.save = tmp;
eq(written, true);
eq(actualCm, cm);
CodeMirror.Vim.mapclear();
});
testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) {
CodeMirror.Vim.map('b', ':w', 'visual');
@ -4221,6 +4366,7 @@ testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) {
eq(actualCm, cm);
CodeMirror.commands.save = tmp;
CodeMirror.Vim.mapclear();
});
testVim('ex_imap', function(cm, vim, helpers) {
CodeMirror.Vim.map('jk', '<Esc>', 'insert');
@ -4231,21 +4377,21 @@ testVim('ex_imap', function(cm, vim, helpers) {
cm.setCursor(0, 1);
CodeMirror.Vim.map('jj', '<Esc>', 'insert');
helpers.doKeys('<C-v>', '2', 'j', 'l', 'c');
var replacement = fillArray('fo', 3);
cm.replaceSelections(replacement);
helpers.doKeys('f', 'o');
eq('1fo4\n5fo8\nafodefg', cm.getValue());
helpers.doKeys('j', 'j');
cm.setCursor(0, 0);
helpers.doKeys('.');
eq('foo4\nfoo8\nfoodefg', cm.getValue());
CodeMirror.Vim.mapclear();
}, { value: '1234\n5678\nabcdefg' });
testVim('ex_unmap_api', function(cm, vim, helpers) {
CodeMirror.Vim.map('<Alt-X>', 'gg', 'normal');
is(CodeMirror.Vim.handleKey(cm, "<Alt-X>", "normal"), "Alt-X key is mapped");
CodeMirror.Vim.unmap("<Alt-X>", "normal");
is(!CodeMirror.Vim.handleKey(cm, "<Alt-X>", "normal"), "Alt-X key is unmapped");
CodeMirror.Vim.mapclear();
});
// Testing registration of functions as ex-commands and mapping to <Key>-keys
testVim('ex_api_test', function(cm, vim, helpers) {
var res=false;
@ -4259,6 +4405,7 @@ testVim('ex_api_test', function(cm, vim, helpers) {
CodeMirror.Vim.map('<C-CR><Space>',':ext');
helpers.doKeys('<C-CR>','<Space>');
is(res,'Mapping to key failed');
CodeMirror.Vim.mapclear();
});
// For now, this test needs to be last because it messes up : for future tests.
testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
@ -4266,8 +4413,67 @@ testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) {
helpers.doKeys(':');
helpers.assertCursorAt(0, 0);
eq('bc', cm.getValue());
CodeMirror.Vim.mapclear();
}, { value: 'abc' });
testVim('noremap', function(cm, vim, helpers) {
CodeMirror.Vim.noremap(';', 'l');
cm.setCursor(0, 0);
eq('wOrd1', cm.getValue());
// Mapping should work in normal mode.
helpers.doKeys(';', 'r', '1');
eq('w1rd1', cm.getValue());
// Mapping will not work in insert mode because of no current fallback
// keyToKey mapping support.
helpers.doKeys('i', ';', '<Esc>');
eq('w;1rd1', cm.getValue());
// unmap all mappings
CodeMirror.Vim.mapclear();
}, { value: 'wOrd1' });
testVim('noremap_swap', function(cm, vim, helpers) {
CodeMirror.Vim.noremap('i', 'a', 'normal');
CodeMirror.Vim.noremap('a', 'i', 'normal');
cm.setCursor(0, 0);
// 'a' should act like 'i'.
helpers.doKeys('a');
eqCursorPos(Pos(0, 0), cm.getCursor());
// ...and 'i' should act like 'a'.
helpers.doKeys('<Esc>', 'i');
eqCursorPos(Pos(0, 1), cm.getCursor());
// unmap all mappings
CodeMirror.Vim.mapclear();
}, { value: 'foo' });
testVim('noremap_map_interaction', function(cm, vim, helpers) {
// noremap should clobber map
CodeMirror.Vim.map(';', 'l');
CodeMirror.Vim.noremap(';', 'l');
CodeMirror.Vim.map('l', 'j');
cm.setCursor(0, 0);
helpers.doKeys(';');
eqCursorPos(Pos(0, 1), cm.getCursor());
helpers.doKeys('l');
eqCursorPos(Pos(1, 1), cm.getCursor());
// map should be able to point to a noremap
CodeMirror.Vim.map('m', ';');
helpers.doKeys('m');
eqCursorPos(Pos(1, 2), cm.getCursor());
// unmap all mappings
CodeMirror.Vim.mapclear();
}, { value: 'wOrd1\nwOrd2' });
testVim('noremap_map_interaction2', function(cm, vim, helpers) {
// map should point to the most recent noremap
CodeMirror.Vim.noremap(';', 'l');
CodeMirror.Vim.map('m', ';');
CodeMirror.Vim.noremap(';', 'h');
cm.setCursor(0, 0);
helpers.doKeys('l');
eqCursorPos(Pos(0, 1), cm.getCursor());
helpers.doKeys('m');
eqCursorPos(Pos(0, 0), cm.getCursor());
// unmap all mappings
CodeMirror.Vim.mapclear();
}, { value: 'wOrd1\nwOrd2' });
// Test event handlers
testVim('beforeSelectionChange', function(cm, vim, helpers) {
cm.setCursor(0, 100);

View File

@ -21,6 +21,7 @@ module.exports = [
"./codemirror/mode/javascript/javascript.js",
"./codemirror/mode/xml/xml.js",
"./codemirror/mode/css/css.js",
"./codemirror/mode/clojure/clojure.js",
"./codemirror/mode/haxe/haxe.js",
"./codemirror/mode/htmlmixed/htmlmixed.js",
"./codemirror/mode/jsx/jsx.js",