From: Alexander Ebert
Date: Fri, 30 Sep 2016 08:35:28 +0000 (+0200)
Subject: Upgraded to CodeMirror 5.19.0
X-Git-Tag: 3.0.0_Beta_2~53
X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=44f4c33923687c796555d73a85ea81dcc362ad6e;p=GitHub%2FWoltLab%2FWCF.git
Upgraded to CodeMirror 5.19.0
---
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/LICENSE b/wcfsetup/install/files/js/3rdParty/codemirror/LICENSE
index 442d11cdce..766132177a 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/LICENSE
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2013 by Marijn Haverbeke and others
+Copyright (C) 2016 by Marijn Haverbeke and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/README.md b/wcfsetup/install/files/js/3rdParty/codemirror/README.md
index 61f6b64525..ff2622a2b8 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/README.md
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/README.md
@@ -1,11 +1,28 @@
# CodeMirror
-[![Build Status](https://secure.travis-ci.org/marijnh/CodeMirror.png?branch=master)](http://travis-ci.org/marijnh/CodeMirror)
-[![NPM version](https://badge.fury.io/js/codemirror.png)](http://badge.fury.io/js/codemirror)
+[![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror)
+[![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror)
+[![Join the chat at https://gitter.im/codemirror/CodeMirror](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/codemirror/CodeMirror)
+[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png?again)](https://marijnhaverbeke.nl/fund/)
-CodeMirror is a JavaScript component that provides a code editor in
-the browser. When a mode is available for the language you are coding
-in, it will color your code, and optionally help with indentation.
+CodeMirror is a versatile text editor implemented in JavaScript for
+the browser. It is specialized for editing code, and comes with over
+100 language modes and various addons that implement more advanced
+editing functionality.
-The project page is http://codemirror.net
-The manual is at http://codemirror.net/doc/manual.html
-The contributing guidelines are in [CONTRIBUTING.md](https://github.com/marijnh/CodeMirror/blob/master/CONTRIBUTING.md)
+A rich programming API and a CSS theming system are available for
+customizing CodeMirror to fit your application, and extending it with
+new functionality.
+
+You can find more information (and the
+[manual](http://codemirror.net/doc/manual.html)) on the [project
+page](http://codemirror.net). For questions and discussion, use the
+[discussion forum](https://discuss.codemirror.net/).
+
+See
+[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
+for contributing guidelines.
+
+The CodeMirror community aims to be welcoming to everybody. We use the
+[Contributor Covenant
+(1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of
+conduct.
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/comment.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/comment.js
index 1eb9a05c5d..d71cf43603 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/comment.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/comment.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -18,26 +21,40 @@
}
CodeMirror.commands.toggleComment = function(cm) {
- var minLine = Infinity, ranges = cm.listSelections(), mode = null;
+ cm.toggleComment();
+ };
+
+ CodeMirror.defineExtension("toggleComment", function(options) {
+ if (!options) options = noOptions;
+ var cm = this;
+ var minLine = Infinity, ranges = this.listSelections(), mode = null;
for (var i = ranges.length - 1; i >= 0; i--) {
var from = ranges[i].from(), to = ranges[i].to();
if (from.line >= minLine) continue;
if (to.line >= minLine) to = Pos(minLine, 0);
minLine = from.line;
if (mode == null) {
- if (cm.uncomment(from, to)) mode = "un";
- else { cm.lineComment(from, to); mode = "line"; }
+ if (cm.uncomment(from, to, options)) mode = "un";
+ else { cm.lineComment(from, to, options); mode = "line"; }
} else if (mode == "un") {
- cm.uncomment(from, to);
+ cm.uncomment(from, to, options);
} else {
- cm.lineComment(from, to);
+ cm.lineComment(from, to, options);
}
}
- };
+ });
+
+ // Rough heuristic to try and detect lines that are part of multi-line string
+ function probablyInsideString(cm, pos, line) {
+ return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"`]/.test(line)
+ }
CodeMirror.defineExtension("lineComment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from);
+ var firstLine = self.getLine(from.line);
+ if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
+
var commentString = options.lineComment || mode.lineComment;
if (!commentString) {
if (options.blockCommentStart || mode.blockCommentStart) {
@@ -46,15 +63,21 @@
}
return;
}
- var firstLine = self.getLine(from.line);
- if (firstLine == null) return;
+
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
var pad = options.padding == null ? " " : options.padding;
var blankLines = options.commentBlankLines || from.line == to.line;
self.operation(function() {
if (options.indent) {
- var baseString = firstLine.slice(0, firstNonWS(firstLine));
+ var baseString = null;
+ for (var i = from.line; i < end; ++i) {
+ var line = self.getLine(i);
+ var whitespace = line.slice(0, firstNonWS(line));
+ if (baseString == null || baseString.length > whitespace.length) {
+ baseString = whitespace;
+ }
+ }
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i), cut = baseString.length;
if (!blankLines && !nonWS.test(line)) continue;
@@ -80,6 +103,7 @@
self.lineComment(from, to, options);
return;
}
+ if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
var end = Math.min(to.line, self.lastLine());
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
@@ -106,7 +130,7 @@
CodeMirror.defineExtension("uncomment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from);
- var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end);
+ var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
// Try finding line comments
var lineString = options.lineComment || mode.lineComment, lines = [];
@@ -117,7 +141,7 @@
var line = self.getLine(i);
var found = line.indexOf(lineString);
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
- if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
+ if (found == -1 && nonWS.test(line)) break lineComment;
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
lines.push(line);
}
@@ -139,17 +163,30 @@
var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) return false;
var lead = options.blockCommentLead || mode.blockCommentLead;
- var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end);
- var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString);
+ var startLine = self.getLine(start), open = startLine.indexOf(startString)
+ if (open == -1) return false
+ var endLine = end == start ? startLine : self.getLine(end)
+ var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
if (close == -1 && start != end) {
endLine = self.getLine(--end);
- close = endLine.lastIndexOf(endString);
+ close = endLine.indexOf(endString);
}
- if (open == -1 || close == -1 ||
+ if (close == -1 ||
!/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
!/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
return false;
+ // Avoid killing block comments completely outside the selection.
+ // Positions of the last startString before the start of the selection, and the first endString after it.
+ var lastStart = startLine.lastIndexOf(startString, from.ch);
+ var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length);
+ if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false;
+ // Positions of the first endString after the end of the selection, and the last startString before it.
+ firstEnd = endLine.indexOf(endString, to.ch);
+ var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch);
+ lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart;
+ if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false;
+
self.operation(function() {
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
Pos(end, close + endString.length));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/continuecomment.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/continuecomment.js
index 42277267f5..b11d51e6ca 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/continuecomment.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/comment/continuecomment.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.css
index 2e7c0fc9b8..677c078387 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.css
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.css
@@ -1,11 +1,11 @@
.CodeMirror-dialog {
position: absolute;
left: 0; right: 0;
- background: white;
+ background: inherit;
z-index: 15;
padding: .1em .8em;
overflow: hidden;
- color: #333;
+ color: inherit;
}
.CodeMirror-dialog-top {
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.js
index 586b7370dc..f10bb5bf19 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/dialog/dialog.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Open simple dialogs on top of an editor. Relies on dialog.css.
(function(mod) {
@@ -12,11 +15,11 @@
var wrap = cm.getWrapperElement();
var dialog;
dialog = wrap.appendChild(document.createElement("div"));
- if (bottom) {
+ if (bottom)
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
- } else {
+ else
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
- }
+
if (typeof template == "string") {
dialog.innerHTML = template;
} else { // Assuming it's a detached DOM element.
@@ -32,40 +35,61 @@
}
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
+ if (!options) options = {};
+
closeNotification(this, null);
- var dialog = dialogDiv(this, template, options && options.bottom);
+
+ var dialog = dialogDiv(this, template, options.bottom);
var closed = false, me = this;
- function close() {
- if (closed) return;
- closed = true;
- dialog.parentNode.removeChild(dialog);
+ function close(newVal) {
+ if (typeof newVal == 'string') {
+ inp.value = newVal;
+ } else {
+ if (closed) return;
+ closed = true;
+ dialog.parentNode.removeChild(dialog);
+ me.focus();
+
+ if (options.onClose) options.onClose(dialog);
+ }
}
+
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
- if (options && options.value) inp.value = options.value;
+ inp.focus();
+
+ if (options.value) {
+ inp.value = options.value;
+ if (options.selectValueOnOpen !== false) {
+ inp.select();
+ }
+ }
+
+ if (options.onInput)
+ CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
+ if (options.onKeyUp)
+ CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
+
CodeMirror.on(inp, "keydown", function(e) {
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
- if (e.keyCode == 13 || e.keyCode == 27) {
+ if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
inp.blur();
CodeMirror.e_stop(e);
close();
- me.focus();
- if (e.keyCode == 13) callback(inp.value);
}
+ if (e.keyCode == 13) callback(inp.value, e);
});
- if (options && options.onKeyUp) {
- CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
- }
- if (options && options.value) inp.value = options.value;
- inp.focus();
- CodeMirror.on(inp, "blur", close);
+
+ if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.on(button, "click", function() {
close();
me.focus();
});
+
+ if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
+
button.focus();
- CodeMirror.on(button, "blur", close);
}
return close;
});
@@ -110,8 +134,8 @@
CodeMirror.defineExtension("openNotification", function(template, options) {
closeNotification(this, close);
var dialog = dialogDiv(this, template, options && options.bottom);
- var duration = options && (options.duration === undefined ? 5000 : options.duration);
var closed = false, doneTimer;
+ var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
function close() {
if (closed) return;
@@ -124,7 +148,10 @@
CodeMirror.e_preventDefault(e);
close();
});
+
if (duration)
- doneTimer = setTimeout(close, options.duration);
+ doneTimer = setTimeout(close, duration);
+
+ return close;
});
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/autorefresh.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/autorefresh.js
new file mode 100644
index 0000000000..1e0e850469
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/autorefresh.js
@@ -0,0 +1,47 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"))
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod)
+ else // Plain browser env
+ mod(CodeMirror)
+})(function(CodeMirror) {
+ "use strict"
+
+ CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
+ if (cm.state.autoRefresh) {
+ stopListening(cm, cm.state.autoRefresh)
+ cm.state.autoRefresh = null
+ }
+ if (val && cm.display.wrapper.offsetHeight == 0)
+ startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
+ })
+
+ function startListening(cm, state) {
+ function check() {
+ if (cm.display.wrapper.offsetHeight) {
+ stopListening(cm, state)
+ if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
+ cm.refresh()
+ } else {
+ state.timeout = setTimeout(check, state.delay)
+ }
+ }
+ state.timeout = setTimeout(check, state.delay)
+ state.hurry = function() {
+ clearTimeout(state.timeout)
+ state.timeout = setTimeout(check, 50)
+ }
+ CodeMirror.on(window, "mouseup", state.hurry)
+ CodeMirror.on(window, "keyup", state.hurry)
+ }
+
+ function stopListening(_cm, state) {
+ clearTimeout(state.timeout)
+ CodeMirror.off(window, "mouseup", state.hurry)
+ CodeMirror.off(window, "keyup", state.hurry)
+ }
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/fullscreen.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/fullscreen.js
index e39c6e162f..cd3673b96c 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/fullscreen.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/fullscreen.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/panel.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/panel.js
new file mode 100644
index 0000000000..ba29484d6c
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/panel.js
@@ -0,0 +1,112 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ CodeMirror.defineExtension("addPanel", function(node, options) {
+ options = options || {};
+
+ if (!this.state.panels) initPanels(this);
+
+ var info = this.state.panels;
+ var wrapper = info.wrapper;
+ var cmWrapper = this.getWrapperElement();
+
+ if (options.after instanceof Panel && !options.after.cleared) {
+ wrapper.insertBefore(node, options.before.node.nextSibling);
+ } else if (options.before instanceof Panel && !options.before.cleared) {
+ wrapper.insertBefore(node, options.before.node);
+ } else if (options.replace instanceof Panel && !options.replace.cleared) {
+ wrapper.insertBefore(node, options.replace.node);
+ options.replace.clear();
+ } else if (options.position == "bottom") {
+ wrapper.appendChild(node);
+ } else if (options.position == "before-bottom") {
+ wrapper.insertBefore(node, cmWrapper.nextSibling);
+ } else if (options.position == "after-top") {
+ wrapper.insertBefore(node, cmWrapper);
+ } else {
+ wrapper.insertBefore(node, wrapper.firstChild);
+ }
+
+ var height = (options && options.height) || node.offsetHeight;
+ this._setSize(null, info.heightLeft -= height);
+ info.panels++;
+ return new Panel(this, node, options, height);
+ });
+
+ function Panel(cm, node, options, height) {
+ this.cm = cm;
+ this.node = node;
+ this.options = options;
+ this.height = height;
+ this.cleared = false;
+ }
+
+ Panel.prototype.clear = function() {
+ if (this.cleared) return;
+ this.cleared = true;
+ var info = this.cm.state.panels;
+ this.cm._setSize(null, info.heightLeft += this.height);
+ info.wrapper.removeChild(this.node);
+ if (--info.panels == 0) removePanels(this.cm);
+ };
+
+ Panel.prototype.changed = function(height) {
+ var newHeight = height == null ? this.node.offsetHeight : height;
+ var info = this.cm.state.panels;
+ this.cm._setSize(null, info.height += (newHeight - this.height));
+ this.height = newHeight;
+ };
+
+ function initPanels(cm) {
+ var wrap = cm.getWrapperElement();
+ var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
+ var height = parseInt(style.height);
+ var info = cm.state.panels = {
+ setHeight: wrap.style.height,
+ heightLeft: height,
+ panels: 0,
+ wrapper: document.createElement("div")
+ };
+ wrap.parentNode.insertBefore(info.wrapper, wrap);
+ var hasFocus = cm.hasFocus();
+ info.wrapper.appendChild(wrap);
+ if (hasFocus) cm.focus();
+
+ cm._setSize = cm.setSize;
+ if (height != null) cm.setSize = function(width, newHeight) {
+ if (newHeight == null) return this._setSize(width, newHeight);
+ info.setHeight = newHeight;
+ if (typeof newHeight != "number") {
+ var px = /^(\d+\.?\d*)px$/.exec(newHeight);
+ if (px) {
+ newHeight = Number(px[1]);
+ } else {
+ info.wrapper.style.height = newHeight;
+ newHeight = info.wrapper.offsetHeight;
+ info.wrapper.style.height = "";
+ }
+ }
+ cm._setSize(width, info.heightLeft += (newHeight - height));
+ height = newHeight;
+ };
+ }
+
+ function removePanels(cm) {
+ var info = cm.state.panels;
+ cm.state.panels = null;
+
+ var wrap = cm.getWrapperElement();
+ info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
+ wrap.style.height = info.setHeight;
+ cm.setSize = cm._setSize;
+ cm.setSize();
+ }
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/placeholder.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/placeholder.js
index 0fdc9b0d5b..2f8b1f84ae 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/placeholder.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/placeholder.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -11,10 +14,12 @@
if (val && !prev) {
cm.on("blur", onBlur);
cm.on("change", onChange);
+ cm.on("swapDoc", onChange);
onChange(cm);
} else if (!val && prev) {
cm.off("blur", onBlur);
cm.off("change", onChange);
+ cm.off("swapDoc", onChange);
clearPlaceholder(cm);
var wrapper = cm.getWrapperElement();
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
@@ -34,7 +39,9 @@
var elt = cm.state.placeholder = document.createElement("pre");
elt.style.cssText = "height: 0; overflow: visible";
elt.className = "CodeMirror-placeholder";
- elt.appendChild(document.createTextNode(cm.getOption("placeholder")));
+ var placeHolder = cm.getOption("placeholder")
+ if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
+ elt.appendChild(placeHolder)
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/rulers.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/rulers.js
index eb0ff06e3a..730054473a 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/rulers.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/display/rulers.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -8,47 +11,41 @@
})(function(CodeMirror) {
"use strict";
- CodeMirror.defineOption("rulers", false, function(cm, val, old) {
- if (old && old != CodeMirror.Init) {
- clearRulers(cm);
- cm.off("refresh", refreshRulers);
+ CodeMirror.defineOption("rulers", false, function(cm, val) {
+ if (cm.state.rulerDiv) {
+ cm.display.lineSpace.removeChild(cm.state.rulerDiv)
+ cm.state.rulerDiv = null
+ cm.off("refresh", drawRulers)
}
if (val && val.length) {
- setRulers(cm);
- cm.on("refresh", refreshRulers);
+ cm.state.rulerDiv = cm.display.lineSpace.insertBefore(document.createElement("div"), cm.display.cursorDiv)
+ cm.state.rulerDiv.className = "CodeMirror-rulers"
+ drawRulers(cm)
+ cm.on("refresh", drawRulers)
}
});
- function clearRulers(cm) {
- for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) {
- var node = cm.display.lineSpace.childNodes[i];
- if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className))
- node.parentNode.removeChild(node);
- }
- }
-
- function setRulers(cm) {
+ function drawRulers(cm) {
+ cm.state.rulerDiv.textContent = ""
var val = cm.getOption("rulers");
var cw = cm.defaultCharWidth();
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
- var bot = -cm.display.scroller.offsetHeight;
+ cm.state.rulerDiv.style.minHeight = (cm.display.scroller.offsetHeight + 30) + "px";
for (var i = 0; i < val.length; i++) {
var elt = document.createElement("div");
- var col, cls = null;
- if (typeof val[i] == "number") {
- col = val[i];
+ elt.className = "CodeMirror-ruler";
+ var col, conf = val[i];
+ if (typeof conf == "number") {
+ col = conf;
} else {
- col = val[i].column;
- cls = val[i].className;
+ col = conf.column;
+ if (conf.className) elt.className += " " + conf.className;
+ if (conf.color) elt.style.borderColor = conf.color;
+ if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle;
+ if (conf.width) elt.style.borderLeftWidth = conf.width;
}
- elt.className = "CodeMirror-ruler" + (cls ? " " + cls : "");
- elt.style.cssText = "left: " + (left + col * cw) + "px; top: -50px; bottom: " + bot + "px";
- cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv);
+ elt.style.left = (left + col * cw) + "px";
+ cm.state.rulerDiv.appendChild(elt)
}
}
-
- function refreshRulers(cm) {
- clearRulers(cm);
- setRulers(cm);
- }
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closebrackets.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closebrackets.js
index f48ad881aa..af7fce2a82 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closebrackets.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closebrackets.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -6,118 +9,187 @@
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
- var DEFAULT_BRACKETS = "()[]{}''\"\"";
- var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
- var SPACE_CHAR_REGEX = /\s/;
+ var defaults = {
+ pairs: "()[]{}''\"\"",
+ triples: "",
+ explode: "[]{}"
+ };
+
+ var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
- if (old != CodeMirror.Init && old)
- cm.removeKeyMap("autoCloseBrackets");
- if (!val) return;
- var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER;
- if (typeof val == "string") pairs = val;
- else if (typeof val == "object") {
- if (val.pairs != null) pairs = val.pairs;
- if (val.explode != null) explode = val.explode;
+ if (old && old != CodeMirror.Init) {
+ cm.removeKeyMap(keyMap);
+ cm.state.closeBrackets = null;
+ }
+ if (val) {
+ cm.state.closeBrackets = val;
+ cm.addKeyMap(keyMap);
}
- var map = buildKeymap(pairs);
- if (explode) map.Enter = buildExplodeHandler(explode);
- cm.addKeyMap(map);
});
- function charsAround(cm, pos) {
- var str = cm.getRange(CodeMirror.Pos(pos.line, pos.ch - 1),
- CodeMirror.Pos(pos.line, pos.ch + 1));
- return str.length == 2 ? str : null;
+ function getOption(conf, name) {
+ if (name == "pairs" && typeof conf == "string") return conf;
+ if (typeof conf == "object" && conf[name] != null) return conf[name];
+ return defaults[name];
}
- function buildKeymap(pairs) {
- var map = {
- name : "autoCloseBrackets",
- Backspace: function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- if (!ranges[i].empty()) return CodeMirror.Pass;
- var around = charsAround(cm, ranges[i].head);
- if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
- }
- for (var i = ranges.length - 1; i >= 0; i--) {
- var cur = ranges[i].head;
- cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
- }
- }
- };
- var closingBrackets = "";
- for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
- if (left != right) closingBrackets += right;
- map["'" + left + "'"] = function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections(), type, next;
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i], cur = range.head, curType;
- if (left == "'" && cm.getTokenTypeAt(cur) == "comment")
- return CodeMirror.Pass;
- var next = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
- if (!range.empty())
- curType = "surround";
- else if (left == right && next == right)
- curType = "skip";
- else if (left == right && CodeMirror.isWordChar(next))
- return CodeMirror.Pass;
- else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next))
- curType = "both";
- else
- return CodeMirror.Pass;
- if (!type) type = curType;
- else if (type != curType) return CodeMirror.Pass;
- }
-
- if (type == "skip") {
- cm.execCommand("goCharRight");
- } else if (type == "surround") {
- var sels = cm.getSelections();
- for (var i = 0; i < sels.length; i++)
- sels[i] = left + sels[i] + right;
- cm.replaceSelections(sels, "around");
- } else if (type == "both") {
- cm.replaceSelection(left + right, null);
- cm.execCommand("goCharLeft");
- }
- };
- if (left != right) map["'" + right + "'"] = function(cm) {
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- if (!range.empty() ||
- cm.getRange(range.head, CodeMirror.Pos(range.head.line, range.head.ch + 1)) != right)
- return CodeMirror.Pass;
- }
- cm.execCommand("goCharRight");
- };
- })(pairs.charAt(i), pairs.charAt(i + 1));
- return map;
+ var bind = defaults.pairs + "`";
+ var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
+ for (var i = 0; i < bind.length; i++)
+ keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i));
+
+ function handler(ch) {
+ return function(cm) { return handleChar(cm, ch); };
+ }
+
+ function getConfig(cm) {
+ var deflt = cm.state.closeBrackets;
+ if (!deflt) return null;
+ var mode = cm.getModeAt(cm.getCursor());
+ return mode.closeBrackets || deflt;
}
- function buildExplodeHandler(pairs) {
- return function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections();
+ function handleBackspace(cm) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var cur = ranges[i].head;
+ cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
+ }
+ }
+
+ function handleEnter(cm) {
+ var conf = getConfig(cm);
+ var explode = conf && getOption(conf, "explode");
+ if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ cm.operation(function() {
+ cm.replaceSelection("\n\n", null);
+ cm.execCommand("goCharLeft");
+ ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
- if (!ranges[i].empty()) return CodeMirror.Pass;
- var around = charsAround(cm, ranges[i].head);
- if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ var line = ranges[i].head.line;
+ cm.indentLine(line, null, true);
+ cm.indentLine(line + 1, null, true);
}
- cm.operation(function() {
- cm.replaceSelection("\n\n", null);
+ });
+ }
+
+ function contractSelection(sel) {
+ var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
+ return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
+ head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
+ }
+
+ function handleChar(cm, ch) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var pos = pairs.indexOf(ch);
+ if (pos == -1) return CodeMirror.Pass;
+ var triples = getOption(conf, "triples");
+
+ var identical = pairs.charAt(pos + 1) == ch;
+ var ranges = cm.listSelections();
+ var opening = pos % 2 == 0;
+
+ var type;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], cur = range.head, curType;
+ var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
+ if (opening && !range.empty()) {
+ curType = "surround";
+ } else if ((identical || !opening) && next == ch) {
+ if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
+ curType = "skipThree";
+ else
+ curType = "skip";
+ } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
+ cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch &&
+ (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) {
+ curType = "addFour";
+ } else if (identical) {
+ if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both";
+ else return CodeMirror.Pass;
+ } else if (opening && (cm.getLine(cur.line).length == cur.ch ||
+ isClosingBracket(next, pairs) ||
+ /\s/.test(next))) {
+ curType = "both";
+ } else {
+ return CodeMirror.Pass;
+ }
+ if (!type) type = curType;
+ else if (type != curType) return CodeMirror.Pass;
+ }
+
+ var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
+ var right = pos % 2 ? ch : pairs.charAt(pos + 1);
+ cm.operation(function() {
+ if (type == "skip") {
+ cm.execCommand("goCharRight");
+ } else if (type == "skipThree") {
+ for (var i = 0; i < 3; i++)
+ cm.execCommand("goCharRight");
+ } else if (type == "surround") {
+ var sels = cm.getSelections();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = left + sels[i] + right;
+ cm.replaceSelections(sels, "around");
+ sels = cm.listSelections().slice();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = contractSelection(sels[i]);
+ cm.setSelections(sels);
+ } else if (type == "both") {
+ cm.replaceSelection(left + right, null);
+ cm.triggerElectric(left + right);
cm.execCommand("goCharLeft");
- ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var line = ranges[i].head.line;
- cm.indentLine(line, null, true);
- cm.indentLine(line + 1, null, true);
- }
- });
- };
+ } else if (type == "addFour") {
+ cm.replaceSelection(left + left + left + left, "before");
+ cm.execCommand("goCharRight");
+ }
+ });
+ }
+
+ function isClosingBracket(ch, pairs) {
+ var pos = pairs.lastIndexOf(ch);
+ return pos > -1 && pos % 2 == 1;
+ }
+
+ function charsAround(cm, pos) {
+ var str = cm.getRange(Pos(pos.line, pos.ch - 1),
+ Pos(pos.line, pos.ch + 1));
+ return str.length == 2 ? str : null;
+ }
+
+ // Project the token type that will exists after the given char is
+ // typed, and use it to determine whether it would cause the start
+ // of a string token.
+ function enteringString(cm, pos, ch) {
+ var line = cm.getLine(pos.line);
+ var token = cm.getTokenAt(pos);
+ if (/\bstring2?\b/.test(token.type)) return false;
+ var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
+ stream.pos = stream.start = token.start;
+ for (;;) {
+ var type1 = cm.getMode().token(stream, token.state);
+ if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
+ stream.start = stream.pos;
+ }
}
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closetag.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closetag.js
index c7c0701ba5..a518da3ec1 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closetag.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/closetag.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
/**
* Tag-closer extension for CodeMirror.
*
@@ -55,6 +58,7 @@
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 opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
@@ -68,8 +72,7 @@
tok.type == "tag" && state.type == "closeTag" ||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
- CodeMirror.scanForClosingTag && CodeMirror.scanForClosingTag(cm, pos, tagName,
- Math.min(cm.lastLine() + 1, pos.line + 50)))
+ closingTagExists(cm, tagName, pos, state, true))
return CodeMirror.Pass;
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
@@ -91,26 +94,76 @@
}
}
- function autoCloseSlash(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
+ function autoCloseCurrent(cm, typingSlash) {
var ranges = cm.listSelections(), replacements = [];
+ var head = typingSlash ? "/" : "";
for (var i = 0; i < ranges.length; i++) {
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 (tok.type == "string" || tok.string.charAt(0) != "<" ||
- tok.start != pos.ch - 1 || inner.mode.name != "xml" ||
- !state.context || !state.context.tagName)
+ if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
+ tok.start != pos.ch - 1))
return CodeMirror.Pass;
- replacements[i] = "/" + state.context.tagName + ">";
+ // Kludge to get around the fact that we are not in XML mode
+ // 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;
+ } else {
+ if (!state.context || !state.context.tagName ||
+ closingTagExists(cm, state.context.tagName, pos, state))
+ return CodeMirror.Pass;
+ replacement = head + state.context.tagName;
+ }
+ if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
+ replacements[i] = replacement;
}
cm.replaceSelections(replacements);
+ ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++)
+ if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
+ cm.indentLine(ranges[i].head.line);
}
+ function autoCloseSlash(cm) {
+ if (cm.getOption("disableInput")) return CodeMirror.Pass;
+ return autoCloseCurrent(cm, true);
+ }
+
+ CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
+
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)
if (collection[i] == elt) return i;
return -1;
}
+
+ // 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) {
+ 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;
+ pos = nextClose.to;
+ for (var i = 1; i < onCx; i++) {
+ var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
+ if (!next || next.tag != tagName) return false;
+ pos = next.to;
+ }
+ return true;
+ }
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/continuelist.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/continuelist.js
index 2946aa6a24..df5179fe47 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/continuelist.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/continuelist.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -8,26 +11,39 @@
})(function(CodeMirror) {
"use strict";
- var listRE = /^(\s*)([*+-]|(\d+)\.)(\s*)/,
- unorderedBullets = "*+-";
+ var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))(\s*)/,
+ emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)[.)])(\s*)$/,
+ unorderedListRE = /[*+-]\s/;
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
- var pos = ranges[i].head, match;
- var inList = cm.getStateAfter(pos.line).list !== false;
+ var pos = ranges[i].head;
+ var eolState = cm.getStateAfter(pos.line);
+ var inList = eolState.list !== false;
+ var inQuote = eolState.quote !== 0;
- if (!ranges[i].empty() || !inList || !(match = cm.getLine(pos.line).match(listRE))) {
+ var line = cm.getLine(pos.line), match = listRE.exec(line);
+ if (!ranges[i].empty() || (!inList && !inQuote) || !match) {
cm.execCommand("newlineAndIndent");
return;
}
- var indent = match[1], after = match[4];
- var bullet = unorderedBullets.indexOf(match[2]) >= 0
- ? match[2]
- : (parseInt(match[3], 10) + 1) + ".";
+ if (emptyListRE.test(line)) {
+ cm.replaceRange("", {
+ line: pos.line, ch: 0
+ }, {
+ line: pos.line, ch: pos.ch + 1
+ });
+ replacements[i] = "\n";
+ } else {
+ var indent = match[1], after = match[5];
+ var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
+ ? match[2]
+ : (parseInt(match[3], 10) + 1) + match[4];
- replacements[i] = "\n" + indent + bullet + after;
+ replacements[i] = "\n" + indent + bullet + after;
+ }
}
cm.replaceSelections(replacements);
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchbrackets.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchbrackets.js
index 576ec143aa..76754ed557 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchbrackets.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchbrackets.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -22,15 +25,24 @@
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
+ if (found == null) return null;
return {from: Pos(where.line, pos), to: found && found.pos,
match: found && found.ch == match.charAt(0), forward: dir > 0};
}
+ // bracketRegex is used to specify which type of bracket to scan
+ // should be a regexp, e.g. /[[\]]/
+ //
+ // Note: If "where" is on an open bracket, then this bracket is ignored.
+ //
+ // Returns false when no bracket was found, null when it reached
+ // maxScanLines and gave up
function scanForBracket(cm, where, dir, style, config) {
var maxScanLen = (config && config.maxScanLineLength) || 10000;
- var maxScanLines = (config && config.maxScanLines) || 500;
+ var maxScanLines = (config && config.maxScanLines) || 1000;
- var stack = [], re = /[(){}[\]]/;
+ var stack = [];
+ var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
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) {
@@ -49,6 +61,7 @@
}
}
}
+ return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
}
function matchBrackets(cm, autoclear, config) {
@@ -57,11 +70,10 @@
var marks = [], ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
- if (match && cm.getLine(match.from.line).length <= maxHighlightLen &&
- match.to && cm.getLine(match.to.line).length <= maxHighlightLen) {
+ if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
- if (match.to)
+ if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
}
}
@@ -69,7 +81,7 @@
if (marks.length) {
// Kludge to work around the IE bug from issue #1193, where text
// input stops going to the textare whever this fires.
- if (ie_lt8 && cm.state.focused) cm.display.input.focus();
+ if (ie_lt8 && cm.state.focused) cm.focus();
var clear = function() {
cm.operation(function() {
@@ -90,8 +102,10 @@
}
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
- if (old && old != CodeMirror.Init)
+ if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchBrackets);
+ if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
+ }
if (val) {
cm.state.matchBrackets = typeof val == "object" ? val : {};
cm.on("cursorActivity", doMatchBrackets);
@@ -99,10 +113,10 @@
});
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
- CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){
- return findMatchingBracket(this, pos, strict);
+ CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
+ return findMatchingBracket(this, pos, strict, config);
});
- CodeMirror.defineExtension("scanForBracket", function(pos, dir, style){
- return scanForBracket(this, pos, dir, style);
+ CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
+ return scanForBracket(this, pos, dir, style, config);
});
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchtags.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchtags.js
index 76a7b87c9a..fb1911a8db 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchtags.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/matchtags.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/trailingspace.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/trailingspace.js
index ec07221e30..fa7b56be51 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/trailingspace.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/edit/trailingspace.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/brace-fold.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/brace-fold.js
index f0ee62029a..13c0f0cd88 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/brace-fold.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/brace-fold.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -10,7 +13,7 @@
CodeMirror.registerHelper("fold", "brace", function(cm, start) {
var line = start.line, lineText = cm.getLine(line);
- var startCh, tokenType;
+ var tokenType;
function findOpening(openCh) {
for (var at = start.ch, pass = 0;;) {
@@ -69,15 +72,15 @@ CodeMirror.registerHelper("fold", "import", function(cm, start) {
}
}
- var start = start.line, has = hasImport(start), prev;
- if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
+ var startLine = start.line, has = hasImport(startLine), prev;
+ if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
return null;
for (var end = has.end;;) {
var next = hasImport(end.line + 1);
if (next == null) break;
end = next.end;
}
- return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
+ return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
});
CodeMirror.registerHelper("fold", "include", function(cm, start) {
@@ -88,14 +91,14 @@ CodeMirror.registerHelper("fold", "include", function(cm, start) {
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
}
- var start = start.line, has = hasInclude(start);
- if (has == null || hasInclude(start - 1) != null) return null;
- for (var end = start;;) {
+ var startLine = start.line, has = hasInclude(startLine);
+ if (has == null || hasInclude(startLine - 1) != null) return null;
+ for (var end = startLine;;) {
var next = hasInclude(end + 1);
if (next == null) break;
++end;
}
- return {from: CodeMirror.Pos(start, has + 1),
+ return {from: CodeMirror.Pos(startLine, has + 1),
to: cm.clipPos(CodeMirror.Pos(end))};
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/comment-fold.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/comment-fold.js
index d72c5479a7..e8d800eb59 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/comment-fold.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/comment-fold.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -25,7 +28,9 @@ CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
continue;
}
if (pass == 1 && found < start.ch) return;
- if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) {
+ if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
+ (found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
+ !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
startCh = found + startToken.length;
break;
}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldcode.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldcode.js
index d7a0bb5e2d..78b36c4641 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldcode.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldcode.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -9,10 +12,14 @@
"use strict";
function doFold(cm, pos, options, force) {
- var finder = options && (options.call ? options : options.rangeFinder);
- if (!finder) finder = CodeMirror.fold.auto;
+ if (options && options.call) {
+ var finder = options;
+ options = null;
+ } else {
+ var finder = getOption(cm, options, "rangeFinder");
+ }
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
- var minSize = options && options.minFoldSize || 0;
+ var minSize = getOption(cm, options, "minFoldSize");
function getRange(allowFolded) {
var range = finder(cm, pos);
@@ -29,17 +36,20 @@
}
var range = getRange(true);
- if (options && options.scanUp) while (!range && pos.line > cm.firstLine()) {
+ if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
pos = CodeMirror.Pos(pos.line - 1, 0);
range = getRange(false);
}
if (!range || range.cleared || force === "unfold") return;
- var myWidget = makeWidget(options);
- CodeMirror.on(myWidget, "mousedown", function() { myRange.clear(); });
+ var myWidget = makeWidget(cm, options);
+ CodeMirror.on(myWidget, "mousedown", function(e) {
+ myRange.clear();
+ CodeMirror.e_preventDefault(e);
+ });
var myRange = cm.markText(range.from, range.to, {
replacedWith: myWidget,
- clearOnEnter: true,
+ clearOnEnter: getOption(cm, options, "clearOnEnter"),
__isFold: true
});
myRange.on("clear", function(from, to) {
@@ -48,8 +58,8 @@
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
}
- function makeWidget(options) {
- var widget = (options && options.widget) || "\u2194";
+ function makeWidget(cm, options) {
+ var widget = getOption(cm, options, "widget");
if (typeof widget == "string") {
var text = document.createTextNode(widget);
widget = document.createElement("span");
@@ -114,4 +124,27 @@
if (cur) return cur;
}
});
+
+ var defaultOptions = {
+ rangeFinder: CodeMirror.fold.auto,
+ widget: "\u2194",
+ minFoldSize: 0,
+ scanUp: false,
+ clearOnEnter: true
+ };
+
+ CodeMirror.defineOption("foldOptions", null);
+
+ function getOption(cm, options, name) {
+ if (options && options[name] !== undefined)
+ return options[name];
+ var editorOptions = cm.options.foldOptions;
+ if (editorOptions && editorOptions[name] !== undefined)
+ return editorOptions[name];
+ return defaultOptions[name];
+ }
+
+ CodeMirror.defineExtension("foldOption", function(options, name) {
+ return getOption(this, options, name);
+ });
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.css
index 49805393d0..ad19ae2d3e 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.css
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.css
@@ -10,7 +10,6 @@
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
- color: #555;
cursor: pointer;
}
.CodeMirror-foldgutter-open:after {
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.js
index 9caba59aad..9d32326566 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/foldgutter.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./foldcode"));
@@ -17,7 +20,7 @@
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
- cm.off("swapDoc", updateInViewport);
+ cm.off("swapDoc", onChange);
}
if (val) {
cm.state.foldGutter = new State(parseOptions(val));
@@ -27,7 +30,7 @@
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
- cm.on("swapDoc", updateInViewport);
+ cm.on("swapDoc", onChange);
}
});
@@ -47,15 +50,15 @@
}
function isFolded(cm, line) {
- var marks = cm.findMarksAt(Pos(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 true;
+ if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
}
function marker(spec) {
if (typeof spec == "string") {
var elt = document.createElement("div");
- elt.className = spec;
+ elt.className = spec + " CodeMirror-guttermarker-subtle";
return elt;
} else {
return spec.cloneNode(true);
@@ -64,14 +67,16 @@
function updateFoldInfo(cm, from, to) {
var opts = cm.state.foldGutter.options, cur = from;
+ var minSize = cm.foldOption(opts, "minFoldSize");
+ var func = cm.foldOption(opts, "rangeFinder");
cm.eachLine(from, to, function(line) {
var mark = null;
if (isFolded(cm, cur)) {
mark = marker(opts.indicatorFolded);
} else {
- var pos = Pos(cur, 0), func = opts.rangeFinder || CodeMirror.fold.auto;
+ var pos = Pos(cur, 0);
var range = func && func(cm, pos);
- if (range && range.from.line + 1 < range.to.line)
+ if (range && range.to.line - range.from.line >= minSize)
mark = marker(opts.indicatorOpen);
}
cm.setGutterMarker(line, opts.gutter, mark);
@@ -89,20 +94,28 @@
}
function onGutterClick(cm, line, gutter) {
- var opts = cm.state.foldGutter.options;
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var opts = state.options;
if (gutter != opts.gutter) return;
- cm.foldCode(Pos(line, 0), opts.rangeFinder);
+ var folded = isFolded(cm, line);
+ if (folded) folded.clear();
+ else cm.foldCode(Pos(line, 0), opts.rangeFinder);
}
function onChange(cm) {
- var state = cm.state.foldGutter, opts = cm.state.foldGutter.options;
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var opts = state.options;
state.from = state.to = 0;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
}
function onViewportChange(cm) {
- var state = cm.state.foldGutter, opts = cm.state.foldGutter.options;
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var opts = state.options;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() {
var vp = cm.getViewport();
@@ -124,7 +137,9 @@
}
function onFold(cm, from) {
- var state = cm.state.foldGutter, line = from.line;
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var line = from.line;
if (line >= state.from && line < state.to)
updateFoldInfo(cm, line, line + 1);
}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/indent-fold.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/indent-fold.js
index d0130836a2..e29f15e9da 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/indent-fold.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/indent-fold.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/markdown-fold.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/markdown-fold.js
index 3bbf5b6077..ce84c946ce 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/markdown-fold.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/markdown-fold.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/xml-fold.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/xml-fold.js
index d554e2fc42..75a9e305bd 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/xml-fold.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/fold/xml-fold.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -18,8 +21,8 @@
function Iter(cm, line, ch, range) {
this.line = line; this.ch = ch;
this.cm = cm; this.text = cm.getLine(line);
- this.min = range ? range.from : cm.firstLine();
- this.max = range ? range.to - 1 : cm.lastLine();
+ this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
+ this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();
}
function tagAt(iter, ch) {
@@ -137,9 +140,9 @@
var openTag = toNextTag(iter), end;
if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
if (!openTag[1] && end != "selfClose") {
- var start = Pos(iter.line, iter.ch);
- var close = findMatchingClose(iter, openTag[2]);
- return close && {from: start, to: close.from};
+ var startPos = Pos(iter.line, iter.ch);
+ var endPos = findMatchingClose(iter, openTag[2]);
+ return endPos && {from: startPos, to: endPos.from};
}
}
});
@@ -148,8 +151,9 @@
if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return;
var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);
var start = end && toTagStart(iter);
- if (!end || end == "selfClose" || !start || cmp(iter, pos) > 0) return;
+ if (!end || !start || cmp(iter, pos) > 0) return;
var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};
+ if (end == "selfClose") return {open: here, close: null, at: "open"};
if (start[1]) { // closing tag
return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"};
@@ -173,6 +177,6 @@
// Used by addon/edit/closetag.js
CodeMirror.scanForClosingTag = function(cm, pos, name, end) {
var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);
- return !!findMatchingClose(iter, name);
+ return findMatchingClose(iter, name);
};
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/anyword-hint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/anyword-hint.js
index 3ef979b524..dae78e2efb 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/anyword-hint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/anyword-hint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -14,12 +17,11 @@
var word = options && options.word || WORD;
var range = options && options.range || RANGE;
var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
- var start = cur.ch, end = start;
- while (end < curLine.length && word.test(curLine.charAt(end))) ++end;
+ var end = cur.ch, start = end;
while (start && word.test(curLine.charAt(start - 1))) --start;
var curWord = start != end && curLine.slice(start, end);
- var list = [], seen = {};
+ var list = options && options.list || [], seen = {};
var re = new RegExp(word.source, "g");
for (var dir = -1; dir <= 1; dir += 2) {
var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/css-hint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/css-hint.js
index 9e5b1e132d..22642727cf 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/css-hint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/css-hint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../../mode/css/css"));
@@ -17,7 +20,11 @@
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
if (inner.mode.name != "css") return;
- var word = token.string, start = token.start, end = token.end;
+ if (token.type == "keyword" && "!important".indexOf(token.string) == 0)
+ return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start),
+ to: CodeMirror.Pos(cur.line, token.end)};
+
+ var start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
if (/[^\w$_-]/.test(word)) {
word = ""; start = end = cur.ch;
}
@@ -31,7 +38,7 @@
result.push(name);
}
- var st = token.state.state;
+ var st = inner.state.state;
if (st == "pseudo" || token.type == "variable-3") {
add(pseudoClasses);
} else if (st == "block" || st == "maybeprop") {
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/html-hint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/html-hint.js
index cbe7c61ad4..c6769bcae5 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/html-hint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/html-hint.js
@@ -1,8 +1,11 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
+ mod(require("../../lib/codemirror"), require("./xml-hint"));
else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
+ define(["../../lib/codemirror", "./xml-hint"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/javascript-hint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/javascript-hint.js
index 305bb85a29..d7088c191b 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/javascript-hint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/javascript-hint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -27,15 +30,20 @@
function scriptHint(editor, keywords, getToken, options) {
// Find the token at the cursor
- var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
+ var cur = editor.getCursor(), token = getToken(editor, cur);
if (/\b(?:string|comment)\b/.test(token.type)) return;
token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_]*$/.test(token.string)) {
- token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
- type: token.string == "." ? "property" : null};
+ token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
+ type: token.string == "." ? "property" : null};
+ } else if (token.end > cur.ch) {
+ token.end = cur.ch;
+ token.string = token.string.slice(0, cur.ch - token.start);
}
+
+ var tprop = token;
// If it is a property, find out what it is a property of.
while (tprop.type == "property") {
tprop = getToken(editor, Pos(cur.line, tprop.start));
@@ -89,8 +97,17 @@
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
+ function forAllProps(obj, callback) {
+ if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
+ for (var name in obj) callback(name)
+ } else {
+ for (var o = obj; o; o = Object.getPrototypeOf(o))
+ Object.getOwnPropertyNames(o).forEach(callback)
+ }
+ }
+
function getCompletions(token, context, keywords, options) {
- var found = [], start = token.string;
+ var found = [], start = token.string, global = options && options.globalScope || window;
function maybeAdd(str) {
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}
@@ -98,7 +115,7 @@
if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
- for (var name in obj) maybeAdd(name);
+ forAllProps(obj, maybeAdd)
}
if (context && context.length) {
@@ -108,27 +125,29 @@
if (obj.type && obj.type.indexOf("variable") === 0) {
if (options && options.additionalContext)
base = options.additionalContext[obj.string];
- base = base || window[obj.string];
+ if (!options || options.useGlobalScope !== false)
+ base = base || global[obj.string];
} else if (obj.type == "string") {
base = "";
} else if (obj.type == "atom") {
base = 1;
} else if (obj.type == "function") {
- if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
- (typeof window.jQuery == 'function'))
- base = window.jQuery();
- else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function'))
- base = window._();
+ if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
+ (typeof global.jQuery == 'function'))
+ base = global.jQuery();
+ else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
+ base = global._();
}
while (base != null && context.length)
base = base[context.pop().string];
if (base != null) gatherCompletions(base);
} else {
- // If not, just look in the window object and any local scope
+ // If not, just look in the global object and any local scope
// (reading into JS mode internals to get at the local and global variables)
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
- gatherCompletions(window);
+ if (!options || options.useGlobalScope !== false)
+ gatherCompletions(global);
forEach(keywords, maybeAdd);
}
return found;
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.css
index 8a4ff052e3..5617ccca2b 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.css
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.css
@@ -25,14 +25,12 @@
margin: 0;
padding: 0 4px;
border-radius: 2px;
- max-width: 19em;
- overflow: hidden;
white-space: pre;
color: black;
cursor: pointer;
}
-.CodeMirror-hint-active {
+li.CodeMirror-hint-active {
background: #08f;
color: white;
}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.js
index 6f04c56584..604bd3b715 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/show-hint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -11,38 +14,65 @@
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
+ // This is the old interface, kept around for now to stay
+ // backwards-compatible.
CodeMirror.showHint = function(cm, getHints, options) {
- // We want a single cursor position.
- if (cm.listSelections().length > 1 || cm.somethingSelected()) return;
- if (getHints == null) {
- if (options && options.async) return;
- else getHints = CodeMirror.hint.auto;
+ if (!getHints) return cm.showHint(options);
+ if (options && options.async) getHints.async = true;
+ var newOpts = {hint: getHints};
+ if (options) for (var prop in options) newOpts[prop] = options[prop];
+ return cm.showHint(newOpts);
+ };
+
+ CodeMirror.defineExtension("showHint", function(options) {
+ options = parseOptions(this, this.getCursor("start"), options);
+ var selections = this.listSelections()
+ if (selections.length > 1) return;
+ // By default, don't allow completion when something is selected.
+ // A hint function can have a `supportsSelection` property to
+ // indicate that it can handle selections.
+ if (this.somethingSelected()) {
+ if (!options.hint.supportsSelection) return;
+ // Don't try with cross-line selections
+ for (var i = 0; i < selections.length; i++)
+ if (selections[i].head.line != selections[i].anchor.line) return;
}
- if (cm.state.completionActive) cm.state.completionActive.close();
+ if (this.state.completionActive) this.state.completionActive.close();
+ var completion = this.state.completionActive = new Completion(this, options);
+ if (!completion.options.hint) return;
- var completion = cm.state.completionActive = new Completion(cm, getHints, options || {});
- CodeMirror.signal(cm, "startCompletion", cm);
- if (completion.options.async)
- getHints(cm, function(hints) { completion.showHints(hints); }, completion.options);
- else
- return completion.showHints(getHints(cm, completion.options));
- };
+ CodeMirror.signal(this, "startCompletion", this);
+ completion.update(true);
+ });
- function Completion(cm, getHints, options) {
+ function Completion(cm, options) {
this.cm = cm;
- this.getHints = getHints;
this.options = options;
- this.widget = this.onClose = null;
+ this.widget = null;
+ this.debounce = 0;
+ this.tick = 0;
+ this.startPos = this.cm.getCursor("start");
+ this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
+
+ var self = this;
+ cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
}
+ var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
+ return setTimeout(fn, 1000/60);
+ };
+ var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
+
Completion.prototype = {
close: function() {
if (!this.active()) return;
this.cm.state.completionActive = null;
+ this.tick = null;
+ this.cm.off("cursorActivity", this.activityFunc);
+ if (this.widget && this.data) CodeMirror.signal(this.data, "close");
if (this.widget) this.widget.close();
- if (this.onClose) this.onClose();
CodeMirror.signal(this.cm, "endCompletion", this.cm);
},
@@ -53,86 +83,81 @@
pick: function(data, i) {
var completion = data.list[i];
if (completion.hint) completion.hint(this.cm, data, completion);
- else this.cm.replaceRange(getText(completion), completion.from||data.from, completion.to||data.to);
+ else this.cm.replaceRange(getText(completion), completion.from || data.from,
+ completion.to || data.to, "complete");
CodeMirror.signal(data, "pick", completion);
this.close();
},
- showHints: function(data) {
- if (!data || !data.list.length || !this.active()) return this.close();
+ cursorActivity: function() {
+ if (this.debounce) {
+ cancelAnimationFrame(this.debounce);
+ this.debounce = 0;
+ }
- if (this.options.completeSingle != false && data.list.length == 1)
- this.pick(data, 0);
- else
- this.showWidget(data);
+ var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
+ if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
+ pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
+ (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
+ this.close();
+ } else {
+ var self = this;
+ this.debounce = requestAnimationFrame(function() {self.update();});
+ if (this.widget) this.widget.disable();
+ }
},
- showWidget: function(data) {
- this.widget = new Widget(this, data);
- CodeMirror.signal(data, "shown");
-
- var debounce = 0, completion = this, finished;
- var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/;
- var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;
-
- var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
- return setTimeout(fn, 1000/60);
- };
- var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
-
- function done() {
- if (finished) return;
- finished = true;
- completion.close();
- completion.cm.off("cursorActivity", activity);
- if (data) CodeMirror.signal(data, "close");
- }
+ update: function(first) {
+ if (this.tick == null) return
+ var self = this, myTick = ++this.tick
+ fetchHints(this.options.hint, this.cm, this.options, function(data) {
+ if (self.tick == myTick) self.finishUpdate(data, first)
+ })
+ },
- function update() {
- if (finished) return;
- CodeMirror.signal(data, "update");
- if (completion.options.async)
- completion.getHints(completion.cm, finishUpdate, completion.options);
- else
- finishUpdate(completion.getHints(completion.cm, completion.options));
- }
- function finishUpdate(data_) {
- data = data_;
- if (finished) return;
- if (!data || !data.list.length) return done();
- completion.widget = new Widget(completion, data);
- }
+ finishUpdate: function(data, first) {
+ if (this.data) CodeMirror.signal(this.data, "update");
- function clearDebounce() {
- if (debounce) {
- cancelAnimationFrame(debounce);
- debounce = 0;
- }
- }
+ var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
+ if (this.widget) this.widget.close();
+
+ if (data && this.data && isNewCompletion(this.data, data)) return;
+ this.data = data;
- function activity() {
- clearDebounce();
- var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
- if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
- pos.ch < startPos.ch || completion.cm.somethingSelected() ||
- (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
- completion.close();
+ if (data && data.list.length) {
+ if (picked && data.list.length == 1) {
+ this.pick(data, 0);
} else {
- debounce = requestAnimationFrame(update);
- if (completion.widget) completion.widget.close();
+ this.widget = new Widget(this, data);
+ CodeMirror.signal(data, "shown");
}
}
- this.cm.on("cursorActivity", activity);
- this.onClose = done;
}
};
+ function isNewCompletion(old, nw) {
+ var moved = CodeMirror.cmpPos(nw.from, old.from)
+ return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch
+ }
+
+ function parseOptions(cm, pos, options) {
+ var editor = cm.options.hintOptions;
+ var out = {};
+ for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
+ if (editor) for (var prop in editor)
+ if (editor[prop] !== undefined) out[prop] = editor[prop];
+ if (options) for (var prop in options)
+ if (options[prop] !== undefined) out[prop] = options[prop];
+ if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
+ return out;
+ }
+
function getText(completion) {
if (typeof completion == "string") return completion;
else return completion.text;
}
- function buildKeyMap(options, handle) {
+ function buildKeyMap(completion, handle) {
var baseMap = {
Up: function() {handle.moveFocus(-1);},
Down: function() {handle.moveFocus(1);},
@@ -144,7 +169,8 @@
Tab: handle.pick,
Esc: handle.close
};
- var ourMap = options.customKeys ? {} : baseMap;
+ var custom = completion.options.customKeys;
+ var ourMap = custom ? {} : baseMap;
function addBinding(key, val) {
var bound;
if (typeof val != "string")
@@ -156,12 +182,13 @@
bound = val;
ourMap[key] = bound;
}
- if (options.customKeys)
- for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key))
- addBinding(key, options.customKeys[key]);
- if (options.extraKeys)
- for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key))
- addBinding(key, options.extraKeys[key]);
+ if (custom)
+ for (var key in custom) if (custom.hasOwnProperty(key))
+ addBinding(key, custom[key]);
+ var extra = completion.options.extraKeys;
+ if (extra)
+ for (var key in extra) if (extra.hasOwnProperty(key))
+ addBinding(key, extra[key]);
return ourMap;
}
@@ -175,11 +202,12 @@
function Widget(completion, data) {
this.completion = completion;
this.data = data;
- var widget = this, cm = completion.cm, options = completion.options;
+ this.picked = false;
+ var widget = this, cm = completion.cm;
var hints = this.hints = document.createElement("ul");
hints.className = "CodeMirror-hints";
- this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0;
+ this.selectedHint = data.selectedHint || 0;
var completions = data.list;
for (var i = 0; i < completions.length; ++i) {
@@ -192,19 +220,22 @@
elt.hintId = i;
}
- var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null);
+ 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";
// 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);
- (options.container || document.body).appendChild(hints);
+ (completion.options.container || document.body).appendChild(hints);
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+ var scrolls = hints.scrollHeight > hints.clientHeight + 1
+ var startScroll = cm.getScrollInfo();
+
if (overlapY > 0) {
- var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top);
+ var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor
- hints.style.top = (top = curTop - height) + "px";
+ hints.style.top = (top = pos.top - height) + "px";
below = false;
} else if (height > winH) {
hints.style.height = (winH - 5) + "px";
@@ -217,7 +248,7 @@
}
}
}
- var overlapX = box.left - winW;
+ var overlapX = box.right - winW;
if (overlapX > 0) {
if (box.right - box.left > winW) {
hints.style.width = (winW - 5) + "px";
@@ -225,8 +256,10 @@
}
hints.style.left = (left = pos.left - overlapX) + "px";
}
+ if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
+ node.style.paddingRight = cm.display.nativeBarWidth + "px"
- cm.addKeyMap(this.keyMap = buildKeyMap(options, {
+ cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
setFocus: function(n) { widget.changeActive(n); },
menuSize: function() { return widget.screenAmount(); },
@@ -236,13 +269,12 @@
data: data
}));
- if (options.closeOnUnfocus !== false) {
+ if (completion.options.closeOnUnfocus) {
var closingOnBlur;
cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
}
- var startScroll = cm.getScrollInfo();
cm.on("scroll", this.onScroll = function() {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
var newTop = top + startScroll.top - curScroll.top;
@@ -262,7 +294,7 @@
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
- if (options.completeOnSingleClick) widget.pick();
+ if (completion.options.completeOnSingleClick) widget.pick();
}
});
@@ -282,13 +314,20 @@
this.completion.cm.removeKeyMap(this.keyMap);
var cm = this.completion.cm;
- if (this.completion.options.closeOnUnfocus !== false) {
+ if (this.completion.options.closeOnUnfocus) {
cm.off("blur", this.onBlur);
cm.off("focus", this.onFocus);
}
cm.off("scroll", this.onScroll);
},
+ disable: function() {
+ this.completion.cm.removeKeyMap(this.keyMap);
+ var widget = this;
+ this.keyMap = {Enter: function() { widget.picked = true; }};
+ this.completion.cm.addKeyMap(this.keyMap);
+ },
+
pick: function() {
this.completion.pick(this.data, this.selectedHint);
},
@@ -315,35 +354,85 @@
}
};
- CodeMirror.registerHelper("hint", "auto", function(cm, options) {
- var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
+ function applicableHelpers(cm, helpers) {
+ if (!cm.somethingSelected()) return helpers
+ var result = []
+ for (var i = 0; i < helpers.length; i++)
+ if (helpers[i].supportsSelection) result.push(helpers[i])
+ return result
+ }
+
+ function fetchHints(hint, cm, options, callback) {
+ if (hint.async) {
+ hint(cm, callback, options)
+ } else {
+ var result = hint(cm, options)
+ if (result && result.then) result.then(callback)
+ else callback(result)
+ }
+ }
+
+ function resolveAutoHints(cm, pos) {
+ var helpers = cm.getHelpers(pos, "hint"), words
if (helpers.length) {
- for (var i = 0; i < helpers.length; i++) {
- var cur = helpers[i](cm, options);
- if (cur && cur.list.length) return cur;
+ var resolved = function(cm, callback, options) {
+ var app = applicableHelpers(cm, helpers);
+ function run(i) {
+ if (i == app.length) return callback(null)
+ fetchHints(app[i], cm, options, function(result) {
+ if (result && result.list.length > 0) callback(result)
+ else run(i + 1)
+ })
+ }
+ run(0)
}
+ resolved.async = true
+ resolved.supportsSelection = true
+ return resolved
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
- if (words) return CodeMirror.hint.fromList(cm, {words: words});
+ return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
} else if (CodeMirror.hint.anyword) {
- return CodeMirror.hint.anyword(cm, options);
+ return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
+ } else {
+ return function() {}
}
+ }
+
+ CodeMirror.registerHelper("hint", "auto", {
+ resolve: resolveAutoHints
});
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+ var to = CodeMirror.Pos(cur.line, token.end);
+ if (token.string && /\w/.test(token.string[token.string.length - 1])) {
+ var term = token.string, from = CodeMirror.Pos(cur.line, token.start);
+ } else {
+ var term = "", from = to;
+ }
var found = [];
for (var i = 0; i < options.words.length; i++) {
var word = options.words[i];
- if (word.slice(0, token.string.length) == token.string)
+ if (word.slice(0, term.length) == term)
found.push(word);
}
- if (found.length) return {
- list: found,
- from: CodeMirror.Pos(cur.line, token.start),
- to: CodeMirror.Pos(cur.line, token.end)
- };
+ if (found.length) return {list: found, from: from, to: to};
});
CodeMirror.commands.autocomplete = CodeMirror.showHint;
+
+ var defaultOptions = {
+ hint: CodeMirror.hint.auto,
+ completeSingle: true,
+ alignWithWord: true,
+ closeCharacters: /[\s()\[\]{};:>,]/,
+ closeOnUnfocus: true,
+ completeOnSingleClick: true,
+ container: null,
+ customKeys: null,
+ extraKeys: null
+ };
+
+ CodeMirror.defineOption("hintOptions", null);
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/sql-hint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/sql-hint.js
index a48d2b3cea..1ee4f0bbed 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/sql-hint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/sql-hint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
@@ -9,72 +12,172 @@
"use strict";
var tables;
+ var defaultTable;
var keywords;
var CONS = {
QUERY_DIV: ";",
ALIAS_KEYWORD: "AS"
};
- var Pos = CodeMirror.Pos;
+ var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
+
+ function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
function getKeywords(editor) {
var mode = editor.doc.modeOption;
- if(mode === "sql") mode = "text/x-sql";
+ if (mode === "sql") mode = "text/x-sql";
return CodeMirror.resolveMode(mode).keywords;
}
+ function getText(item) {
+ return typeof item == "string" ? item : item.text;
+ }
+
+ function wrapTable(name, value) {
+ if (isArray(value)) value = {columns: value}
+ if (!value.text) value.text = name
+ return value
+ }
+
+ function parseTables(input) {
+ var result = {}
+ if (isArray(input)) {
+ for (var i = input.length - 1; i >= 0; i--) {
+ var item = input[i]
+ result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
+ }
+ } else if (input) {
+ for (var name in input)
+ result[name.toUpperCase()] = wrapTable(name, input[name])
+ }
+ return result
+ }
+
+ function getTable(name) {
+ return tables[name.toUpperCase()]
+ }
+
+ function shallowClone(object) {
+ var result = {};
+ for (var key in object) if (object.hasOwnProperty(key))
+ result[key] = object[key];
+ return result;
+ }
+
function match(string, word) {
var len = string.length;
- var sub = word.substr(0, len);
+ var sub = getText(word).substr(0, len);
return string.toUpperCase() === sub.toUpperCase();
}
function addMatches(result, search, wordlist, formatter) {
- for(var word in wordlist) {
- if(!wordlist.hasOwnProperty(word)) continue;
- if(Array.isArray(wordlist)) {
- word = wordlist[word];
- }
- if(match(search, word)) {
- result.push(formatter(word));
+ if (isArray(wordlist)) {
+ for (var i = 0; i < wordlist.length; i++)
+ if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
+ } else {
+ for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
+ var val = wordlist[word]
+ if (!val || val === true)
+ val = word
+ else
+ val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
+ if (match(search, val)) result.push(formatter(val))
}
}
}
- function columnCompletion(result, editor) {
- var cur = editor.getCursor();
- var token = editor.getTokenAt(cur);
- var string = token.string.substr(1);
- var prevCur = Pos(cur.line, token.start);
- var table = editor.getTokenAt(prevCur).string;
- if( !tables.hasOwnProperty( table ) ){
+ function cleanName(name) {
+ // Get rid name from backticks(`) and preceding dot(.)
+ if (name.charAt(0) == ".") {
+ name = name.substr(1);
+ }
+ return name.replace(/`/g, "");
+ }
+
+ function insertBackticks(name) {
+ var nameParts = getText(name).split(".");
+ for (var i = 0; i < nameParts.length; i++)
+ nameParts[i] = "`" + nameParts[i] + "`";
+ var escaped = nameParts.join(".");
+ if (typeof name == "string") return escaped;
+ name = shallowClone(name);
+ name.text = escaped;
+ return name;
+ }
+
+ function nameCompletion(cur, token, result, editor) {
+ // Try to complete table, column names and return start position of completion
+ var useBacktick = false;
+ var nameParts = [];
+ var start = token.start;
+ var cont = true;
+ while (cont) {
+ cont = (token.string.charAt(0) == ".");
+ useBacktick = useBacktick || (token.string.charAt(0) == "`");
+
+ start = token.start;
+ nameParts.unshift(cleanName(token.string));
+
+ token = editor.getTokenAt(Pos(cur.line, token.start));
+ if (token.string == ".") {
+ cont = true;
+ token = editor.getTokenAt(Pos(cur.line, token.start));
+ }
+ }
+
+ // Try to complete table names
+ var string = nameParts.join(".");
+ addMatches(result, string, tables, function(w) {
+ return useBacktick ? insertBackticks(w) : w;
+ });
+
+ // Try to complete columns from defaultTable
+ addMatches(result, string, defaultTable, function(w) {
+ return useBacktick ? insertBackticks(w) : w;
+ });
+
+ // Try to complete columns
+ string = nameParts.pop();
+ var table = nameParts.join(".");
+
+ var alias = false;
+ var aliasTable = table;
+ // Check if table is available. If not, find table by Alias
+ if (!getTable(table)) {
+ var oldTable = table;
table = findTableByAlias(table, editor);
+ if (table !== oldTable) alias = true;
}
- var columns = tables[table];
- if(!columns) {
- return;
+
+ var columns = getTable(table);
+ if (columns && columns.columns)
+ columns = columns.columns;
+
+ if (columns) {
+ addMatches(result, string, columns, function(w) {
+ var tableInsert = table;
+ if (alias == true) tableInsert = aliasTable;
+ if (typeof w == "string") {
+ w = tableInsert + "." + w;
+ } else {
+ w = shallowClone(w);
+ w.text = tableInsert + "." + w.text;
+ }
+ return useBacktick ? insertBackticks(w) : w;
+ });
}
- addMatches(result, string, columns,
- function(w) {return "." + w;});
+
+ return start;
}
function eachWord(lineText, f) {
- if( !lineText ){return;}
+ if (!lineText) return;
var excepted = /[,;]/g;
- var words = lineText.split( " " );
- for( var i = 0; i < words.length; i++ ){
- f( words[i]?words[i].replace( excepted, '' ) : '' );
+ var words = lineText.split(" ");
+ for (var i = 0; i < words.length; i++) {
+ f(words[i]?words[i].replace(excepted, '') : '');
}
}
- function convertCurToNumber( cur ){
- // max characters of a line is 999,999.
- return cur.line + cur.ch / Math.pow( 10, 6 );
- }
-
- function convertNumberToCur( num ){
- return Pos(Math.floor( num ), +num.toString().split( '.' ).pop());
- }
-
function findTableByAlias(alias, editor) {
var doc = editor.doc;
var fullQuery = doc.getValue();
@@ -83,79 +186,86 @@
var table = "";
var separator = [];
var validRange = {
- start: Pos( 0, 0 ),
- end: Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length )
+ start: Pos(0, 0),
+ end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
};
//add separator
- var indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV );
- while( indexOfSeparator != -1 ){
- separator.push( doc.posFromIndex(indexOfSeparator));
- indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV, indexOfSeparator+1);
+ var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
+ while(indexOfSeparator != -1) {
+ separator.push(doc.posFromIndex(indexOfSeparator));
+ indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
}
- separator.unshift( Pos( 0, 0 ) );
- separator.push( Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) );
-
- //find valieRange
- var prevItem = 0;
- var current = convertCurToNumber( editor.getCursor() );
- for( var i=0; i< separator.length; i++){
- var _v = convertCurToNumber( separator[i] );
- if( current > prevItem && current <= _v ){
- validRange = { start: convertNumberToCur( prevItem ), end: convertNumberToCur( _v ) };
+ separator.unshift(Pos(0, 0));
+ separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
+
+ //find valid range
+ var prevItem = null;
+ var current = editor.getCursor()
+ for (var i = 0; i < separator.length; i++) {
+ if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
+ validRange = {start: prevItem, end: separator[i]};
break;
}
- prevItem = _v;
+ prevItem = separator[i];
}
var query = doc.getRange(validRange.start, validRange.end, false);
- for(var i=0; i < query.length; i++){
+ for (var i = 0; i < query.length; i++) {
var lineText = query[i];
- eachWord( lineText, function( word ){
+ eachWord(lineText, function(word) {
var wordUpperCase = word.toUpperCase();
- if( wordUpperCase === aliasUpperCase && tables.hasOwnProperty( previousWord ) ){
- table = previousWord;
- }
- if( wordUpperCase !== CONS.ALIAS_KEYWORD ){
+ if (wordUpperCase === aliasUpperCase && getTable(previousWord))
+ table = previousWord;
+ if (wordUpperCase !== CONS.ALIAS_KEYWORD)
previousWord = word;
- }
});
- if( table ){ break; }
+ if (table) break;
}
return table;
}
- function sqlHint(editor, options) {
- tables = (options && options.tables) || {};
- keywords = keywords || getKeywords(editor);
+ CodeMirror.registerHelper("hint", "sql", function(editor, options) {
+ tables = parseTables(options && options.tables)
+ var defaultTableName = options && options.defaultTable;
+ var disableKeywords = options && options.disableKeywords;
+ defaultTable = defaultTableName && getTable(defaultTableName);
+ keywords = getKeywords(editor);
+
+ if (defaultTableName && !defaultTable)
+ defaultTable = findTableByAlias(defaultTableName, editor);
+
+ defaultTable = defaultTable || [];
+
+ if (defaultTable.columns)
+ defaultTable = defaultTable.columns;
+
var cur = editor.getCursor();
- var token = editor.getTokenAt(cur), end = token.end;
var result = [];
- var search = token.string.trim();
-
- if (search.charAt(0) == ".") {
- columnCompletion(result, editor);
- if (!result.length) {
- while (token.start && search.charAt(0) == ".") {
- token = editor.getTokenAt(Pos(cur.line, token.start - 1));
- search = token.string + search;
- }
- addMatches(result, search, tables,
- function(w) {return w;});
- }
+ var token = editor.getTokenAt(cur), start, end, search;
+ if (token.end > cur.ch) {
+ token.end = cur.ch;
+ token.string = token.string.slice(0, cur.ch - token.start);
+ }
+
+ if (token.string.match(/^[.`\w@]\w*$/)) {
+ search = token.string;
+ start = token.start;
+ end = token.end;
+ } else {
+ start = end = cur.ch;
+ search = "";
+ }
+ if (search.charAt(0) == "." || search.charAt(0) == "`") {
+ start = nameCompletion(cur, token, result, editor);
} else {
- addMatches(result, search, keywords,
- function(w) {return w.toUpperCase();});
- addMatches(result, search, tables,
- function(w) {return w;});
+ addMatches(result, search, tables, function(w) {return w;});
+ addMatches(result, search, defaultTable, function(w) {return w;});
+ if (!disableKeywords)
+ addMatches(result, search, keywords, function(w) {return w.toUpperCase();});
}
- return {
- list: result,
- from: Pos(cur.line, token.start),
- to: Pos(cur.line, end)
- };
- }
- CodeMirror.registerHelper("hint", "sql", sqlHint);
+ return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
+ });
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/xml-hint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/xml-hint.js
index 857564909a..9b9baa0c67 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/xml-hint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/hint/xml-hint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -15,30 +18,55 @@
var quote = (options && options.quoteChar) || '"';
if (!tags) return;
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+ if (token.end > cur.ch) {
+ token.end = cur.ch;
+ token.string = token.string.slice(0, cur.ch - token.start);
+ }
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
if (inner.mode.name != "xml") return;
var result = [], replaceToken = false, prefix;
- var isTag = token.string.charAt(0) == "<";
- if (!inner.state.tagName || isTag) { // Tag completion
- if (isTag) {
- prefix = token.string.slice(1);
- replaceToken = true;
- }
+ var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string);
+ var tagName = tag && /^\w/.test(token.string), tagStart;
+
+ if (tagName) {
+ var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);
+ var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null;
+ if (tagType) tagStart = token.start - (tagType == "close" ? 2 : 1);
+ } else if (tag && token.string == "<") {
+ tagType = "open";
+ } else if (tag && token.string == "") {
+ tagType = "close";
+ }
+
+ if (!tag && !inner.state.tagName || tagType) {
+ if (tagName)
+ prefix = token.string;
+ replaceToken = tagType;
var cx = inner.state.context, curTag = cx && tags[cx.tagName];
var childList = cx ? curTag && curTag.children : tags["!top"];
- if (childList) {
+ if (childList && tagType != "close") {
for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0)
result.push("<" + childList[i]);
- } else {
- for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.lastIndexOf(prefix, 0) == 0))
- result.push("<" + name);
+ } else if (tagType != "close") {
+ for (var name in tags)
+ if (tags.hasOwnProperty(name) && name != "!top" && name != "!attrs" && (!prefix || name.lastIndexOf(prefix, 0) == 0))
+ result.push("<" + name);
}
- if (cx && (!prefix || ("/" + cx.tagName).lastIndexOf(prefix, 0) == 0))
+ if (cx && (!prefix || tagType == "close" && cx.tagName.lastIndexOf(prefix, 0) == 0))
result.push("" + cx.tagName + ">");
} else {
// Attribute completion
var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs;
- if (!attrs) return;
+ var globalAttrs = tags["!attrs"];
+ if (!attrs && !globalAttrs) return;
+ if (!attrs) {
+ attrs = globalAttrs;
+ } else if (globalAttrs) { // Combine tag-local and global attributes
+ var set = {};
+ for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm];
+ for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm];
+ attrs = set;
+ }
if (token.type == "string" || token.string == "=") { // A value
var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)),
Pos(cur.line, token.type == "string" ? token.start : token.end));
@@ -47,9 +75,16 @@
if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget
if (token.type == "string") {
prefix = token.string;
+ var n = 0;
if (/['"]/.test(token.string.charAt(0))) {
quote = token.string.charAt(0);
prefix = token.string.slice(1);
+ n++;
+ }
+ var len = token.string.length;
+ if (/['"]/.test(token.string.charAt(len - 1))) {
+ quote = token.string.charAt(len - 1);
+ prefix = token.string.substr(n, len - 2);
}
replaceToken = true;
}
@@ -66,7 +101,7 @@
}
return {
list: result,
- from: replaceToken ? Pos(cur.line, token.start) : cur,
+ from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
to: replaceToken ? Pos(cur.line, token.end) : cur
};
}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/coffeescript-lint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/coffeescript-lint.js
index 6df17f8f84..7e39428f7a 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/coffeescript-lint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/coffeescript-lint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js
// declare global: coffeelint
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/css-lint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/css-lint.js
index 2a799ddc4f..1f61b479b2 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/css-lint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/css-lint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Depends on csslint.js from https://github.com/stubbornella/csslint
// declare global: CSSLint
@@ -14,6 +17,7 @@
CodeMirror.registerHelper("lint", "css", function(text) {
var found = [];
+ if (!window.CSSLint) return found;
var results = CSSLint.verify(text), messages = results.messages, message = null;
for ( var i = 0; i < messages.length; i++) {
message = messages[i];
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/html-lint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/html-lint.js
new file mode 100644
index 0000000000..1e8417098d
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/html-lint.js
@@ -0,0 +1,46 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// Depends on htmlhint.js from http://htmlhint.com/js/htmlhint.js
+
+// declare global: HTMLHint
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("htmlhint"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "htmlhint"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ var defaultRules = {
+ "tagname-lowercase": true,
+ "attr-lowercase": true,
+ "attr-value-double-quotes": true,
+ "doctype-first": false,
+ "tag-pair": true,
+ "spec-char-escape": true,
+ "id-unique": true,
+ "src-not-empty": true,
+ "attr-no-duplication": true
+ };
+
+ CodeMirror.registerHelper("lint", "html", function(text, options) {
+ var found = [];
+ if (!window.HTMLHint) return found;
+ var messages = HTMLHint.verify(text, options && options.rules || defaultRules);
+ for (var i = 0; i < messages.length; i++) {
+ var message = messages[i];
+ var startLine = message.line - 1, endLine = message.line - 1, startCol = message.col - 1, endCol = message.col;
+ found.push({
+ from: CodeMirror.Pos(startLine, startCol),
+ to: CodeMirror.Pos(endLine, endCol),
+ message: message.message,
+ severity : message.type
+ });
+ }
+ return found;
+ });
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/javascript-lint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/javascript-lint.js
index bbb51083b8..d4f2ae9a1f 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/javascript-lint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/javascript-lint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -19,7 +22,8 @@
"Unclosed string", "Stopping, unable to continue" ];
function validator(text, options) {
- JSHINT(text, options);
+ if (!window.JSHINT) return [];
+ JSHINT(text, options, options.globals);
var errors = JSHINT.data().errors, result = [];
if (errors) parseErrors(errors, result);
return result;
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/json-lint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/json-lint.js
index 1f5f82d0ca..9dbb616b3b 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/json-lint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/json-lint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Depends on jsonlint.js from https://github.com/zaach/jsonlint
// declare global: jsonlint
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.css
index 414a9a0e06..f097cfe345 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.css
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.css
@@ -4,10 +4,10 @@
}
.CodeMirror-lint-tooltip {
- background-color: infobackground;
+ background-color: #ffd;
border: 1px solid black;
border-radius: 4px 4px 4px 4px;
- color: infotext;
+ color: black;
font-family: monospace;
font-size: 10pt;
overflow: hidden;
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.js
index 393a689036..e3a452766d 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/lint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -8,7 +11,6 @@
})(function(CodeMirror) {
"use strict";
var GUTTER_ID = "CodeMirror-lint-markers";
- var SEVERITIES = /^(?:error|warning)$/;
function showTooltip(e, content) {
var tt = document.createElement("div");
@@ -44,6 +46,7 @@
}
var poll = setInterval(function() {
if (tooltip) for (var n = node;; n = n.parentNode) {
+ if (n && n.nodeType == 11) n = n.host;
if (n == document.body) return;
if (!n) { hide(); break; }
}
@@ -58,13 +61,12 @@
this.timeout = null;
this.hasGutter = hasGutter;
this.onMouseOver = function(e) { onMouseOver(cm, e); };
+ this.waitingFor = 0
}
- function parseOptions(cm, options) {
+ function parseOptions(_cm, options) {
if (options instanceof Function) return {getAnnotations: options};
if (!options || options === true) options = {};
- if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint");
- if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)");
return options;
}
@@ -107,19 +109,39 @@
function annotationTooltip(ann) {
var severity = ann.severity;
- if (!SEVERITIES.test(severity)) severity = "error";
+ if (!severity) severity = "error";
var tip = document.createElement("div");
tip.className = "CodeMirror-lint-message-" + severity;
tip.appendChild(document.createTextNode(ann.message));
return tip;
}
+ function lintAsync(cm, getAnnotations, passOptions) {
+ var state = cm.state.lint
+ var id = ++state.waitingFor
+ function abort() {
+ id = -1
+ cm.off("change", abort)
+ }
+ cm.on("change", abort)
+ getAnnotations(cm.getValue(), function(annotations, arg2) {
+ cm.off("change", abort)
+ if (state.waitingFor != id) return
+ if (arg2 && annotations instanceof CodeMirror) annotations = arg2
+ updateLinting(cm, annotations)
+ }, passOptions, cm);
+ }
+
function startLinting(cm) {
var state = cm.state.lint, options = state.options;
- if (options.async)
- options.getAnnotations(cm, updateLinting, options);
- else
- updateLinting(cm, options.getAnnotations(cm.getValue(), options.options));
+ var passOptions = options.options || options; // Support deprecated passing of `options` property in options
+ var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
+ if (!getAnnotations) return;
+ if (options.async || getAnnotations.async) {
+ lintAsync(cm, getAnnotations, passOptions)
+ } else {
+ updateLinting(cm, getAnnotations(cm.getValue(), passOptions, cm));
+ }
}
function updateLinting(cm, annotationsNotSorted) {
@@ -138,7 +160,7 @@
for (var i = 0; i < anns.length; ++i) {
var ann = anns[i];
var severity = ann.severity;
- if (!SEVERITIES.test(severity)) severity = "error";
+ if (!severity) severity = "error";
maxSeverity = getMaxSeverity(maxSeverity, severity);
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
@@ -159,37 +181,42 @@
function onChange(cm) {
var state = cm.state.lint;
+ if (!state) return;
clearTimeout(state.timeout);
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
}
- function popupSpanTooltip(ann, e) {
+ function popupTooltips(annotations, e) {
var target = e.target || e.srcElement;
- showTooltipFor(e, annotationTooltip(ann), target);
+ var tooltip = document.createDocumentFragment();
+ for (var i = 0; i < annotations.length; i++) {
+ var ann = annotations[i];
+ tooltip.appendChild(annotationTooltip(ann));
+ }
+ showTooltipFor(e, tooltip, target);
}
- // When the mouseover fires, the cursor might not actually be over
- // the character itself yet. These pairs of x,y offsets are used to
- // probe a few nearby points when no suitable marked range is found.
- var nearby = [0, 0, 0, 5, 0, -5, 5, 0, -5, 0];
-
function onMouseOver(cm, e) {
- if (!/\bCodeMirror-lint-mark-/.test((e.target || e.srcElement).className)) return;
- for (var i = 0; i < nearby.length; i += 2) {
- var spans = cm.findMarksAt(cm.coordsChar({left: e.clientX + nearby[i],
- top: e.clientY + nearby[i + 1]}, "client"));
- for (var j = 0; j < spans.length; ++j) {
- var span = spans[j], ann = span.__annotation;
- if (ann) return popupSpanTooltip(ann, e);
- }
+ var target = e.target || e.srcElement;
+ if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
+ var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
+ var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
+
+ var annotations = [];
+ for (var i = 0; i < spans.length; ++i) {
+ var ann = spans[i].__annotation;
+ if (ann) annotations.push(ann);
}
+ if (annotations.length) popupTooltips(annotations, e);
}
CodeMirror.defineOption("lint", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
clearMarks(cm);
- cm.off("change", onChange);
+ if (cm.state.lint.options.lintOnChange !== false)
+ cm.off("change", onChange);
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
+ clearTimeout(cm.state.lint.timeout);
delete cm.state.lint;
}
@@ -197,11 +224,16 @@
var gutters = cm.getOption("gutters"), hasLintGutter = false;
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
- cm.on("change", onChange);
+ if (state.options.lintOnChange !== false)
+ cm.on("change", onChange);
if (state.options.tooltips != false)
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
startLinting(cm);
}
});
+
+ CodeMirror.defineExtension("performLint", function() {
+ if (this.state.lint) startLinting(this);
+ });
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/yaml-lint.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/yaml-lint.js
index b53673af4b..3f77e525b7 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/yaml-lint.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/lint/yaml-lint.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.css
index 63237fc8e9..bda3d9f806 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.css
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.css
@@ -60,6 +60,13 @@
position: absolute;
cursor: pointer;
color: #44c;
+ z-index: 3;
+}
+
+.CodeMirror-merge-copy-reverse {
+ position: absolute;
+ cursor: pointer;
+ color: #44c;
}
.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; }
@@ -90,3 +97,17 @@
.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; }
.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; }
.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; }
+
+.CodeMirror-merge-collapsed-widget:before {
+ content: "(...)";
+}
+.CodeMirror-merge-collapsed-widget {
+ cursor: pointer;
+ color: #88b;
+ background: #eef;
+ border: 1px solid #ddf;
+ font-size: 90%;
+ padding: 0 3px;
+ border-radius: 4px;
+}
+.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; }
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.js
index fc1cb2f5a1..cc94cafb8c 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/merge/merge.js
@@ -1,14 +1,17 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
+ mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch
else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
+ define(["../../lib/codemirror", "diff_match_patch"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
- // declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL
-
var Pos = CodeMirror.Pos;
var svgNS = "http://www.w3.org/2000/svg";
@@ -34,10 +37,13 @@
constructor: DiffView,
init: function(pane, orig, options) {
this.edit = this.mv.edit;
- this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options)));
+ (this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
+ this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
+ this.orig.state.diffViews = [this];
this.diff = getDiff(asString(orig), asString(options.value));
- this.diffOutOfDate = false;
+ this.chunks = getChunks(this.diff);
+ this.diffOutOfDate = this.dealigned = false;
this.showDifferences = options.showDifferences !== false;
this.forceUpdate = registerUpdate(this);
@@ -53,58 +59,78 @@
}
};
+ function ensureDiff(dv) {
+ if (dv.diffOutOfDate) {
+ dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue());
+ dv.chunks = getChunks(dv.diff);
+ dv.diffOutOfDate = false;
+ CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
+ }
+ }
+
+ var updating = false;
function registerUpdate(dv) {
var edit = {from: 0, to: 0, marked: []};
var orig = {from: 0, to: 0, marked: []};
- var debounceChange;
+ var debounceChange, updatingFast = false;
function update(mode) {
+ updating = true;
+ updatingFast = false;
if (mode == "full") {
if (dv.svg) clear(dv.svg);
- clear(dv.copyButtons);
+ if (dv.copyButtons) clear(dv.copyButtons);
clearMarks(dv.edit, edit.marked, dv.classes);
clearMarks(dv.orig, orig.marked, dv.classes);
edit.from = edit.to = orig.from = orig.to = 0;
}
- if (dv.diffOutOfDate) {
- dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue());
- dv.diffOutOfDate = false;
- CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
- }
+ ensureDiff(dv);
if (dv.showDifferences) {
updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes);
updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes);
}
- drawConnectors(dv);
+ makeConnections(dv);
+
+ if (dv.mv.options.connect == "align")
+ alignChunks(dv);
+ updating = false;
+ }
+ function setDealign(fast) {
+ if (updating) return;
+ dv.dealigned = true;
+ set(fast);
}
- function set(slow) {
+ function set(fast) {
+ if (updating || updatingFast) return;
clearTimeout(debounceChange);
- debounceChange = setTimeout(update, slow == true ? 250 : 100);
+ if (fast === true) updatingFast = true;
+ debounceChange = setTimeout(update, fast === true ? 20 : 250);
}
- function change() {
+ function change(_cm, change) {
if (!dv.diffOutOfDate) {
dv.diffOutOfDate = true;
edit.from = edit.to = orig.from = orig.to = 0;
}
- set(true);
+ // Update faster when a line was added/removed
+ setDealign(change.text.length - 1 != change.to.line - change.from.line);
}
dv.edit.on("change", change);
dv.orig.on("change", change);
- dv.edit.on("markerAdded", set);
- dv.edit.on("markerCleared", set);
- dv.orig.on("markerAdded", set);
- dv.orig.on("markerCleared", set);
- dv.edit.on("viewportChange", set);
- dv.orig.on("viewportChange", set);
+ dv.edit.on("markerAdded", setDealign);
+ dv.edit.on("markerCleared", setDealign);
+ dv.orig.on("markerAdded", setDealign);
+ dv.orig.on("markerCleared", setDealign);
+ dv.edit.on("viewportChange", function() { set(false); });
+ dv.orig.on("viewportChange", function() { set(false); });
update();
return update;
}
function registerScroll(dv) {
dv.edit.on("scroll", function() {
- syncScroll(dv, DIFF_INSERT) && drawConnectors(dv);
+ syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
});
dv.orig.on("scroll", function() {
- syncScroll(dv, DIFF_DELETE) && drawConnectors(dv);
+ syncScroll(dv, DIFF_DELETE) && makeConnections(dv);
});
}
@@ -119,24 +145,29 @@
// (to prevent feedback loops)
if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false;
- var sInfo = editor.getScrollInfo(), halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
- var mid = editor.lineAtHeight(midY, "local");
- var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT);
- var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
- var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
- var ratio = (midY - off.top) / (off.bot - off.top);
- var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
-
- var botDist, mix;
- // Some careful tweaking to make sure no space is left out of view
- // when scrolling to top or bottom.
- if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) {
- targetPos = targetPos * mix + sInfo.top * (1 - mix);
- } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) {
- var otherInfo = other.getScrollInfo();
- var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos;
- if (botDistOther > botDist && (mix = botDist / halfScreen) < 1)
- targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
+ var sInfo = editor.getScrollInfo();
+ if (dv.mv.options.connect == "align") {
+ targetPos = sInfo.top;
+ } else {
+ var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
+ var mid = editor.lineAtHeight(midY, "local");
+ var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT);
+ var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
+ var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
+ var ratio = (midY - off.top) / (off.bot - off.top);
+ var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
+
+ var botDist, mix;
+ // Some careful tweaking to make sure no space is left out of view
+ // when scrolling to top or bottom.
+ if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) {
+ targetPos = targetPos * mix + sInfo.top * (1 - mix);
+ } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) {
+ var otherInfo = other.getScrollInfo();
+ var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos;
+ if (botDistOther > botDist && (mix = botDist / halfScreen) < 1)
+ targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
+ }
}
other.scrollTo(sInfo.left, targetPos);
@@ -154,7 +185,7 @@
function setScrollLock(dv, val, action) {
dv.lockScroll = val;
- if (val && action != false) syncScroll(dv, DIFF_INSERT) && drawConnectors(dv);
+ if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db \u21da";
}
@@ -165,7 +196,7 @@
var mark = arr[i];
if (mark instanceof CodeMirror.TextMarker) {
mark.clear();
- } else {
+ } else if (mark.parent) {
editor.removeLineClass(mark, "background", classes.chunk);
editor.removeLineClass(mark, "background", classes.start);
editor.removeLineClass(mark, "background", classes.end);
@@ -242,7 +273,7 @@
// Updating the gap between editor and original
- function drawConnectors(dv) {
+ function makeConnections(dv) {
if (!dv.showDifferences) return;
if (dv.svg) {
@@ -250,40 +281,157 @@
var w = dv.gap.offsetWidth;
attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight);
}
- clear(dv.copyButtons);
+ if (dv.copyButtons) clear(dv.copyButtons);
- var flip = dv.type == "left";
var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
- var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top;
- iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) {
- if (topEdit > vpEdit.to || botEdit < vpEdit.from ||
- topOrig > vpOrig.to || botOrig < vpOrig.from)
- return;
- var topLpx = dv.orig.heightAtLine(topOrig, "local") - sTopOrig, top = topLpx;
- if (dv.svg) {
- var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit;
- if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
- var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig;
- var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit;
- if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
- var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
- var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
- attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")),
- "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
- "class", dv.classes.connect);
+ var outerTop = dv.mv.wrap.getBoundingClientRect().top
+ var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top
+ var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top;
+ for (var i = 0; i < dv.chunks.length; i++) {
+ var ch = dv.chunks[i];
+ if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from &&
+ ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from)
+ drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w);
+ }
+ }
+
+ function getMatchingOrigLine(editLine, chunks) {
+ var editStart = 0, origStart = 0;
+ for (var i = 0; i < chunks.length; i++) {
+ var chunk = chunks[i];
+ if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null;
+ if (chunk.editFrom > editLine) break;
+ editStart = chunk.editTo;
+ origStart = chunk.origTo;
+ }
+ return origStart + (editLine - editStart);
+ }
+
+ function findAlignedLines(dv, other) {
+ var linesToAlign = [];
+ for (var i = 0; i < dv.chunks.length; i++) {
+ var chunk = dv.chunks[i];
+ linesToAlign.push([chunk.origTo, chunk.editTo, other ? getMatchingOrigLine(chunk.editTo, other.chunks) : null]);
+ }
+ if (other) {
+ for (var i = 0; i < other.chunks.length; i++) {
+ var chunk = other.chunks[i];
+ for (var j = 0; j < linesToAlign.length; j++) {
+ var align = linesToAlign[j];
+ if (align[1] == chunk.editTo) {
+ j = -1;
+ break;
+ } else if (align[1] > chunk.editTo) {
+ break;
+ }
+ }
+ if (j > -1)
+ linesToAlign.splice(j - 1, 0, [getMatchingOrigLine(chunk.editTo, dv.chunks), chunk.editTo, chunk.origTo]);
}
+ }
+ return linesToAlign;
+ }
+
+ function alignChunks(dv, force) {
+ if (!dv.dealigned && !force) return;
+ if (!dv.orig.curOp) return dv.orig.operation(function() {
+ alignChunks(dv, force);
+ });
+
+ dv.dealigned = false;
+ var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left;
+ if (other) {
+ ensureDiff(other);
+ other.dealigned = false;
+ }
+ var linesToAlign = findAlignedLines(dv, other);
+
+ // Clear old aligners
+ var aligners = dv.mv.aligners;
+ for (var i = 0; i < aligners.length; i++)
+ aligners[i].clear();
+ aligners.length = 0;
+
+ var cm = [dv.orig, dv.edit], scroll = [];
+ if (other) cm.push(other.orig);
+ for (var i = 0; i < cm.length; i++)
+ scroll.push(cm[i].getScrollInfo().top);
+
+ for (var ln = 0; ln < linesToAlign.length; ln++)
+ alignLines(cm, linesToAlign[ln], aligners);
+
+ for (var i = 0; i < cm.length; i++)
+ cm[i].scrollTo(null, scroll[i]);
+ }
+
+ function alignLines(cm, lines, aligners) {
+ var maxOffset = 0, offset = [];
+ for (var i = 0; i < cm.length; i++) if (lines[i] != null) {
+ var off = cm[i].heightAtLine(lines[i], "local");
+ offset[i] = off;
+ maxOffset = Math.max(maxOffset, off);
+ }
+ for (var i = 0; i < cm.length; i++) if (lines[i] != null) {
+ var diff = maxOffset - offset[i];
+ if (diff > 1)
+ aligners.push(padAbove(cm[i], lines[i], diff));
+ }
+ }
+
+ function padAbove(cm, line, size) {
+ var above = true;
+ if (line > cm.lastLine()) {
+ line--;
+ above = false;
+ }
+ var elt = document.createElement("div");
+ elt.className = "CodeMirror-merge-spacer";
+ elt.style.height = size + "px"; elt.style.minWidth = "1px";
+ return cm.addLineWidget(line, elt, {height: size, above: above});
+ }
+
+ function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
+ var flip = dv.type == "left";
+ var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig;
+ if (dv.svg) {
+ var topLpx = top;
+ var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
+ if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
+ var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig;
+ var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit;
+ if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
+ var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
+ var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
+ attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")),
+ "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
+ "class", dv.classes.connect);
+ }
+ if (dv.copyButtons) {
var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
"CodeMirror-merge-copy"));
- copy.title = "Revert chunk";
- copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
+ var editOriginals = dv.mv.options.allowEditingOriginals;
+ copy.title = editOriginals ? "Push to left" : "Revert chunk";
+ copy.chunk = chunk;
copy.style.top = top + "px";
- });
+
+ if (editOriginals) {
+ var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit;
+ var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
+ "CodeMirror-merge-copy-reverse"));
+ copyReverse.title = "Push to right";
+ copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo,
+ origFrom: chunk.editFrom, origTo: chunk.editTo};
+ copyReverse.style.top = topReverse + "px";
+ dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
+ }
+ }
}
- function copyChunk(dv, chunk) {
+ function copyChunk(dv, to, from, chunk) {
if (dv.diffOutOfDate) return;
- dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)),
- Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0));
+ var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
+ var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
+ to.replaceRange(from.getRange(origStart, Pos(chunk.origTo, 0)), editStart, Pos(chunk.editTo, 0))
}
// Merge view, containing 0, 1, or 2 diff views.
@@ -291,10 +439,13 @@
var MergeView = CodeMirror.MergeView = function(node, options) {
if (!(this instanceof MergeView)) return new MergeView(node, options);
+ this.options = options;
var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight;
+
var hasLeft = origLeft != null, hasRight = origRight != null;
var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0);
var wrap = [], left = this.left = null, right = this.right = null;
+ var self = this;
if (hasLeft) {
left = this.left = new DiffView(this, "left");
@@ -316,15 +467,25 @@
(hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost";
wrap.push(elt("div", null, null, "height: 0; clear: both;"));
+
var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane"));
this.edit = CodeMirror(editPane, copyObj(options));
if (left) left.init(leftPane, origLeft, options);
if (right) right.init(rightPane, origRight, options);
+ if (options.collapseIdentical)
+ this.editor().operation(function() {
+ collapseIdenticalStretches(self, options.collapseIdentical);
+ });
+ if (options.connect == "align") {
+ this.aligners = [];
+ alignChunks(this.left || this.right, true);
+ }
+
var onResize = function() {
- if (left) drawConnectors(left);
- if (right) drawConnectors(right);
+ if (left) makeConnections(left);
+ if (right) makeConnections(right);
};
CodeMirror.on(window, "resize", onResize);
var resizeInterval = setInterval(function() {
@@ -338,16 +499,26 @@
lock.title = "Toggle locked scrolling";
var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
- dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
- CodeMirror.on(dv.copyButtons, "click", function(e) {
- var node = e.target || e.srcElement;
- if (node.chunk) copyChunk(dv, node.chunk);
- });
- var gapElts = [dv.copyButtons, lockWrap];
- var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
- if (svg && !svg.createSVGRect) svg = null;
- dv.svg = svg;
- if (svg) gapElts.push(svg);
+ var gapElts = [lockWrap];
+ if (dv.mv.options.revertButtons !== false) {
+ dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
+ CodeMirror.on(dv.copyButtons, "click", function(e) {
+ var node = e.target || e.srcElement;
+ if (!node.chunk) return;
+ if (node.className == "CodeMirror-merge-copy-reverse") {
+ copyChunk(dv, dv.orig, dv.edit, node.chunk);
+ return;
+ }
+ copyChunk(dv, dv.edit, dv.orig, node.chunk);
+ });
+ gapElts.unshift(dv.copyButtons);
+ }
+ if (dv.mv.options.connect != "align") {
+ var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
+ if (svg && !svg.createSVGRect) svg = null;
+ dv.svg = svg;
+ if (svg) gapElts.push(svg);
+ }
return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap");
}
@@ -362,10 +533,10 @@
if (this.left) this.left.setShowDifferences(val);
},
rightChunks: function() {
- return this.right && getChunks(this.right.diff);
+ if (this.right) { ensureDiff(this.right); return this.right.chunks; }
},
leftChunks: function() {
- return this.left && getChunks(this.left.diff);
+ if (this.left) { ensureDiff(this.left); return this.left.chunks; }
}
};
@@ -393,7 +564,8 @@
return diff;
}
- function iterateChunks(diff, f) {
+ function getChunks(diff) {
+ var chunks = [];
var startEdit = 0, startOrig = 0;
var edit = Pos(0, 0), orig = Pos(0, 0);
for (var i = 0; i < diff.length; ++i) {
@@ -405,7 +577,8 @@
var endOff = endOfLineClean(diff, i) ? 1 : 0;
var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff;
if (cleanToEdit > cleanFromEdit) {
- if (i) f(startOrig, cleanFromOrig, startEdit, cleanFromEdit);
+ if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig,
+ editFrom: startEdit, editTo: cleanFromEdit});
startEdit = cleanToEdit; startOrig = cleanToOrig;
}
} else {
@@ -413,16 +586,9 @@
}
}
if (startEdit <= edit.line || startOrig <= orig.line)
- f(startOrig, orig.line + 1, startEdit, edit.line + 1);
- }
-
- function getChunks(diff) {
- var collect = [];
- iterateChunks(diff, function(topOrig, botOrig, topEdit, botEdit) {
- collect.push({origFrom: topOrig, origTo: botOrig,
- editFrom: topEdit, editTo: botEdit});
- });
- return collect;
+ chunks.push({origFrom: startOrig, origTo: orig.line + 1,
+ editFrom: startEdit, editTo: edit.line + 1});
+ return chunks;
}
function endOfLineClean(diff, i) {
@@ -443,21 +609,87 @@
return last.charCodeAt(last.length - 1) == 10;
}
- function chunkBoundariesAround(diff, n, nInEdit) {
+ function chunkBoundariesAround(chunks, n, nInEdit) {
var beforeE, afterE, beforeO, afterO;
- iterateChunks(diff, function(fromOrig, toOrig, fromEdit, toEdit) {
- var fromLocal = nInEdit ? fromEdit : fromOrig;
- var toLocal = nInEdit ? toEdit : toOrig;
+ for (var i = 0; i < chunks.length; i++) {
+ var chunk = chunks[i];
+ var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom;
+ var toLocal = nInEdit ? chunk.editTo : chunk.origTo;
if (afterE == null) {
- if (fromLocal > n) { afterE = fromEdit; afterO = fromOrig; }
- else if (toLocal > n) { afterE = toEdit; afterO = toOrig; }
+ if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; }
+ else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; }
}
- if (toLocal <= n) { beforeE = toEdit; beforeO = toOrig; }
- else if (fromLocal <= n) { beforeE = fromEdit; beforeO = fromOrig; }
- });
+ if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; }
+ else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; }
+ }
return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
}
+ function collapseSingle(cm, from, to) {
+ cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
+ var widget = document.createElement("span");
+ widget.className = "CodeMirror-merge-collapsed-widget";
+ widget.title = "Identical text collapsed. Click to expand.";
+ var mark = cm.markText(Pos(from, 0), Pos(to - 1), {
+ inclusiveLeft: true,
+ inclusiveRight: true,
+ replacedWith: widget,
+ clearOnEnter: true
+ });
+ function clear() {
+ mark.clear();
+ cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
+ }
+ CodeMirror.on(widget, "click", clear);
+ return {mark: mark, clear: clear};
+ }
+
+ function collapseStretch(size, editors) {
+ var marks = [];
+ function clear() {
+ for (var i = 0; i < marks.length; i++) marks[i].clear();
+ }
+ for (var i = 0; i < editors.length; i++) {
+ var editor = editors[i];
+ var mark = collapseSingle(editor.cm, editor.line, editor.line + size);
+ marks.push(mark);
+ mark.mark.on("clear", clear);
+ }
+ return marks[0].mark;
+ }
+
+ function unclearNearChunks(dv, margin, off, clear) {
+ for (var i = 0; i < dv.chunks.length; i++) {
+ var chunk = dv.chunks[i];
+ for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) {
+ var pos = l + off;
+ if (pos >= 0 && pos < clear.length) clear[pos] = false;
+ }
+ }
+ }
+
+ function collapseIdenticalStretches(mv, margin) {
+ if (typeof margin != "number") margin = 2;
+ var clear = [], edit = mv.editor(), off = edit.firstLine();
+ for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true);
+ if (mv.left) unclearNearChunks(mv.left, margin, off, clear);
+ if (mv.right) unclearNearChunks(mv.right, margin, off, clear);
+
+ for (var i = 0; i < clear.length; i++) {
+ if (clear[i]) {
+ var line = i + off;
+ for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {}
+ if (size > margin) {
+ var editors = [{line: line, cm: edit}];
+ if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig});
+ if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig});
+ var mark = collapseStretch(size, editors);
+ if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark);
+ }
+ }
+ }
+ }
+
// General utilities
function elt(tag, content, className, style) {
@@ -502,4 +734,42 @@
function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
+
+ function findPrevDiff(chunks, start, isOrig) {
+ for (var i = chunks.length - 1; i >= 0; i--) {
+ var chunk = chunks[i];
+ var to = (isOrig ? chunk.origTo : chunk.editTo) - 1;
+ if (to < start) return to;
+ }
+ }
+
+ function findNextDiff(chunks, start, isOrig) {
+ for (var i = 0; i < chunks.length; i++) {
+ var chunk = chunks[i];
+ var from = (isOrig ? chunk.origFrom : chunk.editFrom);
+ if (from > start) return from;
+ }
+ }
+
+ function goNearbyDiff(cm, dir) {
+ var found = null, views = cm.state.diffViews, line = cm.getCursor().line;
+ if (views) for (var i = 0; i < views.length; i++) {
+ var dv = views[i], isOrig = cm == dv.orig;
+ ensureDiff(dv);
+ var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig);
+ if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found)))
+ found = pos;
+ }
+ if (found != null)
+ cm.setCursor(found, 0);
+ else
+ return CodeMirror.Pass;
+ }
+
+ CodeMirror.commands.goNextDiff = function(cm) {
+ return goNearbyDiff(cm, 1);
+ };
+ CodeMirror.commands.goPrevDiff = function(cm) {
+ return goNearbyDiff(cm, -1);
+ };
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/loadmode.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/loadmode.js
index e08c281321..10117ec22f 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/loadmode.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/loadmode.js
@@ -1,11 +1,14 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
+ mod(require("../../lib/codemirror"), "cjs");
else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
+ define(["../../lib/codemirror"], function(CM) { mod(CM, "amd"); });
else // Plain browser env
- mod(CodeMirror);
-})(function(CodeMirror) {
+ mod(CodeMirror, "plain");
+})(function(CodeMirror, env) {
if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js";
var loading = {};
@@ -32,21 +35,24 @@
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont);
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
- var script = document.createElement("script");
- script.src = CodeMirror.modeURL.replace(/%N/g, mode);
- var others = document.getElementsByTagName("script")[0];
- others.parentNode.insertBefore(script, others);
- var list = loading[mode] = [cont];
- var count = 0, poll = setInterval(function() {
- if (++count > 100) return clearInterval(poll);
- if (CodeMirror.modes.hasOwnProperty(mode)) {
- clearInterval(poll);
- loading[mode] = null;
+ var file = CodeMirror.modeURL.replace(/%N/g, mode);
+ if (env == "plain") {
+ var script = document.createElement("script");
+ script.src = file;
+ var others = document.getElementsByTagName("script")[0];
+ var list = loading[mode] = [cont];
+ CodeMirror.on(script, "load", function() {
ensureDeps(mode, function() {
for (var i = 0; i < list.length; ++i) list[i]();
});
- }
- }, 200);
+ });
+ others.parentNode.insertBefore(script, others);
+ } else if (env == "cjs") {
+ require(file);
+ cont();
+ } else if (env == "amd") {
+ requirejs([file], cont);
+ }
};
CodeMirror.autoLoadMode = function(instance, mode) {
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex.js
index 07385c35f2..3d8b34c452 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -11,12 +14,14 @@
CodeMirror.multiplexingMode = function(outer /*, others */) {
// Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects
var others = Array.prototype.slice.call(arguments, 1);
- var n_others = others.length;
- function indexOf(string, pattern, from) {
- if (typeof pattern == "string") return string.indexOf(pattern, from);
+ function indexOf(string, pattern, from, returnEnd) {
+ if (typeof pattern == "string") {
+ var found = string.indexOf(pattern, from);
+ return returnEnd && found > -1 ? found + pattern.length : found;
+ }
var m = pattern.exec(from ? string.slice(from) : string);
- return m ? m.index + from : -1;
+ return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1;
}
return {
@@ -39,14 +44,14 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
token: function(stream, state) {
if (!state.innerActive) {
var cutOff = Infinity, oldContent = stream.string;
- for (var i = 0; i < n_others; ++i) {
+ for (var i = 0; i < others.length; ++i) {
var other = others[i];
var found = indexOf(oldContent, other.open, stream.pos);
if (found == stream.pos) {
- stream.match(other.open);
+ if (!other.parseDelimiters) stream.match(other.open);
state.innerActive = other;
state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
- return other.delimStyle;
+ return other.delimStyle && (other.delimStyle + " " + other.delimStyle + "-open");
} else if (found != -1 && found < cutOff) {
cutOff = found;
}
@@ -61,18 +66,21 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
state.innerActive = state.inner = null;
return this.token(stream, state);
}
- var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos) : -1;
- if (found == stream.pos) {
+ var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1;
+ if (found == stream.pos && !curInner.parseDelimiters) {
stream.match(curInner.close);
state.innerActive = state.inner = null;
- return curInner.delimStyle;
+ return curInner.delimStyle && (curInner.delimStyle + " " + curInner.delimStyle + "-close");
}
if (found > -1) stream.string = oldContent.slice(0, found);
var innerToken = curInner.mode.token(stream, state.inner);
if (found > -1) stream.string = oldContent;
+ if (found == stream.pos && curInner.parseDelimiters)
+ state.innerActive = state.inner = null;
+
if (curInner.innerStyle) {
- if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
+ if (innerToken) innerToken = innerToken + " " + curInner.innerStyle;
else innerToken = curInner.innerStyle;
}
@@ -92,7 +100,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
mode.blankLine(state.innerActive ? state.inner : state.outer);
}
if (!state.innerActive) {
- for (var i = 0; i < n_others; ++i) {
+ for (var i = 0; i < others.length; ++i) {
var other = others[i];
if (other.open === "\n") {
state.innerActive = other;
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex_test.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex_test.js
index c0656357c7..24e5e670de 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex_test.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/multiplex_test.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function() {
CodeMirror.defineMode("markdown_with_stex", function(){
var inner = CodeMirror.getMode({}, "stex");
@@ -26,5 +29,5 @@
MT(
"stexInsideMarkdown",
- "[strong **Equation:**] [delim $][inner&tag \\pi][delim $]");
+ "[strong **Equation:**] [delim&delim-open $][inner&tag \\pi][delim&delim-close $]");
})();
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/overlay.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/overlay.js
index 6f556a13a7..e1b9ed3753 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/overlay.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/overlay.js
@@ -1,10 +1,14 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Utility function that allows modes to be combined. The mode given
// as the base argument takes care of most of the normal mode
// functionality, but a second (typically simple) mode is used, which
// can override the style of text. Both modes get to parse all of the
// text, but when both assign a non-null style to a piece of code, the
-// overlay wins, unless the combine argument was true, in which case
-// the styles are combined.
+// overlay wins, unless the combine argument was true and not overridden,
+// or state.overlay.combineTokens was true, in which case the styles are
+// combined.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -23,7 +27,8 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
base: CodeMirror.startState(base),
overlay: CodeMirror.startState(overlay),
basePos: 0, baseCur: null,
- overlayPos: 0, overlayCur: null
+ overlayPos: 0, overlayCur: null,
+ streamSeen: null
};
},
copyState: function(state) {
@@ -36,6 +41,12 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
},
token: function(stream, state) {
+ if (stream != state.streamSeen ||
+ Math.min(state.basePos, state.overlayPos) < stream.start) {
+ state.streamSeen = stream;
+ state.basePos = state.overlayPos = stream.start;
+ }
+
if (stream.start == state.basePos) {
state.baseCur = base.token(stream, state.base);
state.basePos = stream.pos;
@@ -46,10 +57,14 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
state.overlayPos = stream.pos;
}
stream.pos = Math.min(state.basePos, state.overlayPos);
- if (stream.eol()) state.basePos = state.overlayPos = 0;
+ // state.overlay.combineTokens always takes precedence over combine,
+ // unless set to null
if (state.overlayCur == null) return state.baseCur;
- if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
+ else if (state.baseCur != null &&
+ state.overlay.combineTokens ||
+ combine && state.overlay.combineTokens == null)
+ return state.baseCur + " " + state.overlayCur;
else return state.overlayCur;
},
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/simple.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/simple.js
new file mode 100644
index 0000000000..df663365e8
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/mode/simple.js
@@ -0,0 +1,213 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineSimpleMode = function(name, states) {
+ CodeMirror.defineMode(name, function(config) {
+ return CodeMirror.simpleMode(config, states);
+ });
+ };
+
+ CodeMirror.simpleMode = function(config, states) {
+ ensureState(states, "start");
+ var states_ = {}, meta = states.meta || {}, hasIndentation = false;
+ for (var state in states) if (state != meta && states.hasOwnProperty(state)) {
+ var list = states_[state] = [], orig = states[state];
+ for (var i = 0; i < orig.length; i++) {
+ var data = orig[i];
+ list.push(new Rule(data, states));
+ if (data.indent || data.dedent) hasIndentation = true;
+ }
+ }
+ var mode = {
+ startState: function() {
+ return {state: "start", pending: null,
+ local: null, localState: null,
+ indent: hasIndentation ? [] : null};
+ },
+ copyState: function(state) {
+ var s = {state: state.state, pending: state.pending,
+ local: state.local, localState: null,
+ indent: state.indent && state.indent.slice(0)};
+ if (state.localState)
+ s.localState = CodeMirror.copyState(state.local.mode, state.localState);
+ if (state.stack)
+ s.stack = state.stack.slice(0);
+ for (var pers = state.persistentStates; pers; pers = pers.next)
+ s.persistentStates = {mode: pers.mode,
+ spec: pers.spec,
+ state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),
+ next: s.persistentStates};
+ return s;
+ },
+ token: tokenFunction(states_, config),
+ innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },
+ indent: indentFunction(states_, meta)
+ };
+ if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))
+ mode[prop] = meta[prop];
+ return mode;
+ };
+
+ function ensureState(states, name) {
+ if (!states.hasOwnProperty(name))
+ throw new Error("Undefined state " + name + " in simple mode");
+ }
+
+ function toRegex(val, caret) {
+ if (!val) return /(?:)/;
+ var flags = "";
+ if (val instanceof RegExp) {
+ if (val.ignoreCase) flags = "i";
+ val = val.source;
+ } else {
+ val = String(val);
+ }
+ return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags);
+ }
+
+ function asToken(val) {
+ if (!val) return null;
+ if (typeof val == "string") return val.replace(/\./g, " ");
+ var result = [];
+ for (var i = 0; i < val.length; i++)
+ result.push(val[i] && val[i].replace(/\./g, " "));
+ return result;
+ }
+
+ function Rule(data, states) {
+ if (data.next || data.push) ensureState(states, data.next || data.push);
+ this.regex = toRegex(data.regex);
+ this.token = asToken(data.token);
+ this.data = data;
+ }
+
+ function tokenFunction(states, config) {
+ return function(stream, state) {
+ if (state.pending) {
+ var pend = state.pending.shift();
+ if (state.pending.length == 0) state.pending = null;
+ stream.pos += pend.text.length;
+ return pend.token;
+ }
+
+ if (state.local) {
+ if (state.local.end && stream.match(state.local.end)) {
+ var tok = state.local.endToken || null;
+ state.local = state.localState = null;
+ return tok;
+ } else {
+ var tok = state.local.mode.token(stream, state.localState), m;
+ if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))
+ stream.pos = stream.start + m.index;
+ return tok;
+ }
+ }
+
+ var curState = states[state.state];
+ for (var i = 0; i < curState.length; i++) {
+ var rule = curState[i];
+ var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);
+ if (matches) {
+ if (rule.data.next) {
+ state.state = rule.data.next;
+ } else if (rule.data.push) {
+ (state.stack || (state.stack = [])).push(state.state);
+ state.state = rule.data.push;
+ } else if (rule.data.pop && state.stack && state.stack.length) {
+ state.state = state.stack.pop();
+ }
+
+ if (rule.data.mode)
+ enterLocalMode(config, state, rule.data.mode, rule.token);
+ if (rule.data.indent)
+ state.indent.push(stream.indentation() + config.indentUnit);
+ if (rule.data.dedent)
+ state.indent.pop();
+ if (matches.length > 2) {
+ state.pending = [];
+ for (var j = 2; j < matches.length; j++)
+ if (matches[j])
+ state.pending.push({text: matches[j], token: rule.token[j - 1]});
+ stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));
+ return rule.token[0];
+ } else if (rule.token && rule.token.join) {
+ return rule.token[0];
+ } else {
+ return rule.token;
+ }
+ }
+ }
+ stream.next();
+ return null;
+ };
+ }
+
+ function cmp(a, b) {
+ if (a === b) return true;
+ if (!a || typeof a != "object" || !b || typeof b != "object") return false;
+ var props = 0;
+ for (var prop in a) if (a.hasOwnProperty(prop)) {
+ if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;
+ props++;
+ }
+ for (var prop in b) if (b.hasOwnProperty(prop)) props--;
+ return props == 0;
+ }
+
+ function enterLocalMode(config, state, spec, token) {
+ var pers;
+ if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)
+ if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;
+ var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);
+ var lState = pers ? pers.state : CodeMirror.startState(mode);
+ if (spec.persistent && !pers)
+ state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};
+
+ state.localState = lState;
+ state.local = {mode: mode,
+ end: spec.end && toRegex(spec.end),
+ endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
+ endToken: token && token.join ? token[token.length - 1] : token};
+ }
+
+ function indexOf(val, arr) {
+ for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;
+ }
+
+ function indentFunction(states, meta) {
+ return function(state, textAfter, line) {
+ if (state.local && state.local.mode.indent)
+ return state.local.mode.indent(state.localState, textAfter, line);
+ if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)
+ return CodeMirror.Pass;
+
+ var pos = state.indent.length - 1, rules = states[state.state];
+ scan: for (;;) {
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i];
+ if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {
+ var m = rule.regex.exec(textAfter);
+ if (m && m[0]) {
+ pos--;
+ if (rule.next || rule.push) rules = states[rule.next || rule.push];
+ textAfter = textAfter.slice(m[0].length);
+ continue scan;
+ }
+ }
+ }
+ break;
+ }
+ return pos < 0 ? 0 : state.indent[pos];
+ };
+ }
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/colorize.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/colorize.js
index 0f9530b17b..eb7060d0a2 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/colorize.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/colorize.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./runmode"));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode-standalone.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode-standalone.js
index eaa2b8f2fb..f4f352c803 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode-standalone.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode-standalone.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
window.CodeMirror = {};
(function() {
@@ -71,7 +74,11 @@ CodeMirror.startState = function (mode, a1, a2) {
};
var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
-CodeMirror.defineMode = function (name, mode) { modes[name] = mode; };
+CodeMirror.defineMode = function (name, mode) {
+ if (arguments.length > 2)
+ mode.dependencies = Array.prototype.slice.call(arguments, 2);
+ modes[name] = mode;
+};
CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; };
CodeMirror.resolveMode = function(spec) {
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
@@ -139,6 +146,7 @@ CodeMirror.runMode = function (string, modespec, callback, options) {
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new CodeMirror.StringStream(lines[i]);
+ if (!stream.string && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
var style = mode.token(stream, state);
callback(stream.current(), style, i, stream.start, state);
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.js
index 351840e08e..a51c6d0d52 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -13,7 +16,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
var ie = /MSIE \d/.test(navigator.userAgent);
var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
- if (callback.nodeType == 1) {
+ if (callback.appendChild) {
var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
var node = callback, col = 0;
node.innerHTML = "";
@@ -57,6 +60,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new CodeMirror.StringStream(lines[i]);
+ if (!stream.string && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
var style = mode.token(stream, state);
callback(stream.current(), style, i, stream.start, state);
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.node.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.node.js
index 74c39be7e7..b22a5187f3 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.node.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/runmode/runmode.node.js
@@ -1,18 +1,39 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
/* Just enough of CodeMirror to run runMode under node.js */
-// declare global: StringStream
+function splitLines(string){return string.split(/\r\n?|\n/);};
-function splitLines(string){ return string.split(/\r?\n|\r/); };
+// Counts the column offset in a string, taking tabs into account.
+// Used mostly to find indentation.
+var countColumn = function(string, end, tabSize, startIndex, startValue) {
+ if (end == null) {
+ end = string.search(/[^\s\u00a0]/);
+ if (end == -1) end = string.length;
+ }
+ for (var i = startIndex || 0, n = startValue || 0;;) {
+ var nextTab = string.indexOf("\t", i);
+ if (nextTab < 0 || nextTab >= end)
+ return n + (end - i);
+ n += nextTab - i;
+ n += tabSize - (n % tabSize);
+ i = nextTab + 1;
+ }
+};
-function StringStream(string) {
+function StringStream(string, tabSize) {
this.pos = this.start = 0;
this.string = string;
+ this.tabSize = tabSize || 8;
+ this.lastColumnPos = this.lastColumnValue = 0;
this.lineStart = 0;
-}
+};
+
StringStream.prototype = {
eol: function() {return this.pos >= this.string.length;},
- sol: function() {return this.pos == 0;},
- peek: function() {return this.string.charAt(this.pos) || null;},
+ sol: function() {return this.pos == this.lineStart;},
+ peek: function() {return this.string.charAt(this.pos) || undefined;},
next: function() {
if (this.pos < this.string.length)
return this.string.charAt(this.pos++);
@@ -39,8 +60,17 @@ StringStream.prototype = {
if (found > -1) {this.pos = found; return true;}
},
backUp: function(n) {this.pos -= n;},
- column: function() {return this.start - this.lineStart;},
- indentation: function() {return 0;},
+ column: function() {
+ if (this.lastColumnPos < this.start) {
+ this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
+ this.lastColumnPos = this.start;
+ }
+ return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
+ },
+ indentation: function() {
+ return countColumn(this.string, null, this.tabSize) -
+ (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
+ },
match: function(pattern, consume, caseInsensitive) {
if (typeof pattern == "string") {
var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
@@ -71,10 +101,8 @@ exports.startState = function(mode, a1, a2) {
var modes = exports.modes = {}, mimeModes = exports.mimeModes = {};
exports.defineMode = function(name, mode) {
- if (arguments.length > 2) {
- mode.dependencies = [];
- for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
- }
+ if (arguments.length > 2)
+ mode.dependencies = Array.prototype.slice.call(arguments, 2);
modes[name] = mode;
};
exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; };
@@ -93,11 +121,42 @@ exports.resolveMode = function(spec) {
if (typeof spec == "string") return {name: spec};
else return spec || {name: "null"};
};
+
+function copyObj(obj, target, overwrite) {
+ if (!target) target = {};
+ for (var prop in obj)
+ if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
+ target[prop] = obj[prop];
+ return target;
+}
+
+// This can be used to attach properties to mode objects from
+// outside the actual mode definition.
+var modeExtensions = exports.modeExtensions = {};
+exports.extendMode = function(mode, properties) {
+ var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
+ copyObj(properties, exts);
+};
+
exports.getMode = function(options, spec) {
- spec = exports.resolveMode(spec);
+ var spec = exports.resolveMode(spec);
var mfactory = modes[spec.name];
- if (!mfactory) throw new Error("Unknown mode: " + spec);
- return mfactory(options, spec);
+ if (!mfactory) return exports.getMode(options, "text/plain");
+ var modeObj = mfactory(options, spec);
+ if (modeExtensions.hasOwnProperty(spec.name)) {
+ var exts = modeExtensions[spec.name];
+ for (var prop in exts) {
+ if (!exts.hasOwnProperty(prop)) continue;
+ if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
+ modeObj[prop] = exts[prop];
+ }
+ }
+ modeObj.name = spec.name;
+ if (spec.helperType) modeObj.helperType = spec.helperType;
+ if (spec.modeProps) for (var prop in spec.modeProps)
+ modeObj[prop] = spec.modeProps[prop];
+
+ return modeObj;
};
exports.registerHelper = exports.registerGlobalHelper = Math.min;
@@ -107,6 +166,7 @@ exports.runMode = function(string, modespec, callback, options) {
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new exports.StringStream(lines[i]);
+ if (!stream.string && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
var style = mode.token(stream, state);
callback(stream.current(), style, i, stream.start, state);
@@ -116,3 +176,4 @@ exports.runMode = function(string, modespec, callback, options) {
};
require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")];
+require.cache[require.resolve("../../addon/runmode/runmode")] = require.cache[require.resolve("./runmode.node")];
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/annotatescrollbar.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/annotatescrollbar.js
new file mode 100644
index 0000000000..5e748e816d
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/annotatescrollbar.js
@@ -0,0 +1,118 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineExtension("annotateScrollbar", function(options) {
+ if (typeof options == "string") options = {className: options};
+ return new Annotation(this, options);
+ });
+
+ CodeMirror.defineOption("scrollButtonHeight", 0);
+
+ function Annotation(cm, options) {
+ this.cm = cm;
+ this.options = options;
+ this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
+ this.annotations = [];
+ this.doRedraw = this.doUpdate = null;
+ this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
+ this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
+ this.computeScale();
+
+ function scheduleRedraw(delay) {
+ clearTimeout(self.doRedraw);
+ self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
+ }
+
+ var self = this;
+ cm.on("refresh", this.resizeHandler = function() {
+ clearTimeout(self.doUpdate);
+ self.doUpdate = setTimeout(function() {
+ if (self.computeScale()) scheduleRedraw(20);
+ }, 100);
+ });
+ cm.on("markerAdded", this.resizeHandler);
+ cm.on("markerCleared", this.resizeHandler);
+ if (options.listenForChanges !== false)
+ cm.on("change", this.changeHandler = function() {
+ scheduleRedraw(250);
+ });
+ }
+
+ Annotation.prototype.computeScale = function() {
+ var cm = this.cm;
+ var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
+ cm.getScrollerElement().scrollHeight
+ if (hScale != this.hScale) {
+ this.hScale = hScale;
+ return true;
+ }
+ };
+
+ Annotation.prototype.update = function(annotations) {
+ this.annotations = annotations;
+ this.redraw();
+ };
+
+ Annotation.prototype.redraw = function(compute) {
+ if (compute !== false) this.computeScale();
+ var cm = this.cm, hScale = this.hScale;
+
+ var frag = document.createDocumentFragment(), anns = this.annotations;
+
+ var wrapping = cm.getOption("lineWrapping");
+ var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
+ var curLine = null, curLineObj = null;
+ function getY(pos, top) {
+ if (curLine != pos.line) {
+ curLine = pos.line;
+ curLineObj = cm.getLineHandle(curLine);
+ }
+ if (wrapping && curLineObj.height > singleLineH)
+ return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
+ var topY = cm.heightAtLine(curLineObj, "local");
+ return topY + (top ? 0 : curLineObj.height);
+ }
+
+ if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
+ var ann = anns[i];
+ var top = nextTop || getY(ann.from, true) * hScale;
+ var bottom = getY(ann.to, false) * hScale;
+ while (i < anns.length - 1) {
+ nextTop = getY(anns[i + 1].from, true) * hScale;
+ if (nextTop > bottom + .9) break;
+ ann = anns[++i];
+ bottom = getY(ann.to, false) * hScale;
+ }
+ if (bottom == top) continue;
+ var height = Math.max(bottom - top, 3);
+
+ var elt = frag.appendChild(document.createElement("div"));
+ elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
+ + (top + this.buttonHeight) + "px; height: " + height + "px";
+ elt.className = this.options.className;
+ if (ann.id) {
+ elt.setAttribute("annotation-id", ann.id);
+ }
+ }
+ this.div.textContent = "";
+ this.div.appendChild(frag);
+ };
+
+ Annotation.prototype.clear = function() {
+ this.cm.off("refresh", this.resizeHandler);
+ this.cm.off("markerAdded", this.resizeHandler);
+ this.cm.off("markerCleared", this.resizeHandler);
+ if (this.changeHandler) this.cm.off("change", this.changeHandler);
+ this.div.parentNode.removeChild(this.div);
+ };
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/scrollpastend.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/scrollpastend.js
index 467b7aa1cf..a2ed089b48 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/scrollpastend.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/scrollpastend.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -37,7 +40,9 @@
if (cm.state.scrollPastEndPadding != padding) {
cm.state.scrollPastEndPadding = padding;
cm.display.lineSpace.parentNode.style.paddingBottom = padding;
+ cm.off("refresh", updateBottomMargin);
cm.setSize();
+ cm.on("refresh", updateBottomMargin);
}
}
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/simplescrollbars.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/simplescrollbars.css
new file mode 100644
index 0000000000..5eea7aa1b3
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/simplescrollbars.css
@@ -0,0 +1,66 @@
+.CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div {
+ position: absolute;
+ background: #ccc;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ border: 1px solid #bbb;
+ border-radius: 2px;
+}
+
+.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {
+ position: absolute;
+ z-index: 6;
+ background: #eee;
+}
+
+.CodeMirror-simplescroll-horizontal {
+ bottom: 0; left: 0;
+ height: 8px;
+}
+.CodeMirror-simplescroll-horizontal div {
+ bottom: 0;
+ height: 100%;
+}
+
+.CodeMirror-simplescroll-vertical {
+ right: 0; top: 0;
+ width: 8px;
+}
+.CodeMirror-simplescroll-vertical div {
+ right: 0;
+ width: 100%;
+}
+
+
+.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler {
+ display: none;
+}
+
+.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
+ position: absolute;
+ background: #bcd;
+ border-radius: 3px;
+}
+
+.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical {
+ position: absolute;
+ z-index: 6;
+}
+
+.CodeMirror-overlayscroll-horizontal {
+ bottom: 0; left: 0;
+ height: 6px;
+}
+.CodeMirror-overlayscroll-horizontal div {
+ bottom: 0;
+ height: 100%;
+}
+
+.CodeMirror-overlayscroll-vertical {
+ right: 0; top: 0;
+ width: 6px;
+}
+.CodeMirror-overlayscroll-vertical div {
+ right: 0;
+ width: 100%;
+}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/simplescrollbars.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/simplescrollbars.js
new file mode 100644
index 0000000000..23f3e03f81
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/scroll/simplescrollbars.js
@@ -0,0 +1,152 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ function Bar(cls, orientation, scroll) {
+ this.orientation = orientation;
+ this.scroll = scroll;
+ this.screen = this.total = this.size = 1;
+ this.pos = 0;
+
+ this.node = document.createElement("div");
+ this.node.className = cls + "-" + orientation;
+ this.inner = this.node.appendChild(document.createElement("div"));
+
+ var self = this;
+ CodeMirror.on(this.inner, "mousedown", function(e) {
+ if (e.which != 1) return;
+ CodeMirror.e_preventDefault(e);
+ var axis = self.orientation == "horizontal" ? "pageX" : "pageY";
+ var start = e[axis], startpos = self.pos;
+ function done() {
+ CodeMirror.off(document, "mousemove", move);
+ CodeMirror.off(document, "mouseup", done);
+ }
+ function move(e) {
+ if (e.which != 1) return done();
+ self.moveTo(startpos + (e[axis] - start) * (self.total / self.size));
+ }
+ CodeMirror.on(document, "mousemove", move);
+ CodeMirror.on(document, "mouseup", done);
+ });
+
+ CodeMirror.on(this.node, "click", function(e) {
+ CodeMirror.e_preventDefault(e);
+ var innerBox = self.inner.getBoundingClientRect(), where;
+ if (self.orientation == "horizontal")
+ where = e.clientX < innerBox.left ? -1 : e.clientX > innerBox.right ? 1 : 0;
+ else
+ where = e.clientY < innerBox.top ? -1 : e.clientY > innerBox.bottom ? 1 : 0;
+ self.moveTo(self.pos + where * self.screen);
+ });
+
+ function onWheel(e) {
+ var moved = CodeMirror.wheelEventPixels(e)[self.orientation == "horizontal" ? "x" : "y"];
+ var oldPos = self.pos;
+ self.moveTo(self.pos + moved);
+ if (self.pos != oldPos) CodeMirror.e_preventDefault(e);
+ }
+ CodeMirror.on(this.node, "mousewheel", onWheel);
+ CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
+ }
+
+ Bar.prototype.setPos = function(pos, force) {
+ if (pos < 0) pos = 0;
+ if (pos > this.total - this.screen) pos = this.total - this.screen;
+ if (!force && pos == this.pos) return false;
+ this.pos = pos;
+ this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
+ (pos * (this.size / this.total)) + "px";
+ return true
+ };
+
+ Bar.prototype.moveTo = function(pos) {
+ if (this.setPos(pos)) this.scroll(pos, this.orientation);
+ }
+
+ var minButtonSize = 10;
+
+ Bar.prototype.update = function(scrollSize, clientSize, barSize) {
+ var sizeChanged = this.screen != clientSize || this.total != scrollSize || this.size != barSize
+ if (sizeChanged) {
+ this.screen = clientSize;
+ this.total = scrollSize;
+ this.size = barSize;
+ }
+
+ var buttonSize = this.screen * (this.size / this.total);
+ if (buttonSize < minButtonSize) {
+ this.size -= minButtonSize - buttonSize;
+ buttonSize = minButtonSize;
+ }
+ this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
+ buttonSize + "px";
+ this.setPos(this.pos, sizeChanged);
+ };
+
+ function SimpleScrollbars(cls, place, scroll) {
+ this.addClass = cls;
+ this.horiz = new Bar(cls, "horizontal", scroll);
+ place(this.horiz.node);
+ this.vert = new Bar(cls, "vertical", scroll);
+ place(this.vert.node);
+ this.width = null;
+ }
+
+ SimpleScrollbars.prototype.update = function(measure) {
+ if (this.width == null) {
+ var style = window.getComputedStyle ? window.getComputedStyle(this.horiz.node) : this.horiz.node.currentStyle;
+ if (style) this.width = parseInt(style.height);
+ }
+ var width = this.width || 0;
+
+ var needsH = measure.scrollWidth > measure.clientWidth + 1;
+ var needsV = measure.scrollHeight > measure.clientHeight + 1;
+ this.vert.node.style.display = needsV ? "block" : "none";
+ this.horiz.node.style.display = needsH ? "block" : "none";
+
+ if (needsV) {
+ this.vert.update(measure.scrollHeight, measure.clientHeight,
+ measure.viewHeight - (needsH ? width : 0));
+ this.vert.node.style.bottom = needsH ? width + "px" : "0";
+ }
+ if (needsH) {
+ this.horiz.update(measure.scrollWidth, measure.clientWidth,
+ measure.viewWidth - (needsV ? width : 0) - measure.barLeft);
+ this.horiz.node.style.right = needsV ? width + "px" : "0";
+ this.horiz.node.style.left = measure.barLeft + "px";
+ }
+
+ return {right: needsV ? width : 0, bottom: needsH ? width : 0};
+ };
+
+ SimpleScrollbars.prototype.setScrollTop = function(pos) {
+ this.vert.setPos(pos);
+ };
+
+ SimpleScrollbars.prototype.setScrollLeft = function(pos) {
+ this.horiz.setPos(pos);
+ };
+
+ SimpleScrollbars.prototype.clear = function() {
+ var parent = this.horiz.node.parentNode;
+ parent.removeChild(this.horiz.node);
+ parent.removeChild(this.vert.node);
+ };
+
+ CodeMirror.scrollbarModel.simple = function(place, scroll) {
+ return new SimpleScrollbars("CodeMirror-simplescroll", place, scroll);
+ };
+ CodeMirror.scrollbarModel.overlay = function(place, scroll) {
+ return new SimpleScrollbars("CodeMirror-overlayscroll", place, scroll);
+ };
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/jump-to-line.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/jump-to-line.js
new file mode 100644
index 0000000000..8b599cbc17
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/jump-to-line.js
@@ -0,0 +1,49 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// Defines jumpToLine command. Uses dialog.js if present.
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("../dialog/dialog"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "../dialog/dialog"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ function dialog(cm, text, shortText, deflt, f) {
+ if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
+ else f(prompt(shortText, deflt));
+ }
+
+ var jumpDialog =
+ 'Jump to line: (Use line:column or scroll% syntax) ';
+
+ function interpretLine(cm, string) {
+ var num = Number(string)
+ if (/^[-+]/.test(string)) return cm.getCursor().line + num
+ else return num - 1
+ }
+
+ CodeMirror.commands.jumpToLine = function(cm) {
+ var cur = cm.getCursor();
+ dialog(cm, jumpDialog, "Jump to line:", (cur.line + 1) + ":" + cur.ch, function(posStr) {
+ if (!posStr) return;
+
+ var match;
+ if (match = /^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr)) {
+ cm.setCursor(interpretLine(cm, match[1]), Number(match[2]))
+ } else if (match = /^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)) {
+ var line = Math.round(cm.lineCount() * Number(match[1]) / 100);
+ if (/^[-+]/.test(match[1])) line = cur.line + line + 1;
+ cm.setCursor(line - 1, cur.ch);
+ } else if (match = /^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr)) {
+ cm.setCursor(interpretLine(cm, match[1]), cur.ch);
+ }
+ });
+ };
+
+ CodeMirror.keyMap["default"]["Alt-G"] = "jumpToLine";
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/match-highlighter.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/match-highlighter.js
index d9c818b838..73ba0e0537 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/match-highlighter.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/match-highlighter.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Highlighting text that matches the selection
//
// Defines an option highlightSelectionMatches, which, when enabled,
@@ -5,84 +8,146 @@
// document.
//
// The option can be set to true to simply enable it, or to a
-// {minChars, style, showToken} object to explicitly configure it.
-// minChars is the minimum amount of characters that should be
+// {minChars, style, wordsOnly, showToken, delay} object to explicitly
+// configure it. minChars is the minimum amount of characters that should be
// selected for the behavior to occur, and style is the token style to
// apply to the matches. This will be prefixed by "cm-" to create an
-// actual CSS class name. showToken, when enabled, will cause the
-// current token to be highlighted when nothing is selected.
+// actual CSS class name. If wordsOnly is enabled, the matches will be
+// highlighted only if the selected text is a word. showToken, when enabled,
+// will cause the current token to be highlighted when nothing is selected.
+// delay is used to specify how much time to wait, in milliseconds, before
+// highlighting the matches. If annotateScrollbar is enabled, the occurences
+// will be highlighted on the scrollbar via the matchesonscrollbar addon.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
+ mod(require("../../lib/codemirror"), require("./matchesonscrollbar"));
else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
+ define(["../../lib/codemirror", "./matchesonscrollbar"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
- var DEFAULT_MIN_CHARS = 2;
- var DEFAULT_TOKEN_STYLE = "matchhighlight";
- var DEFAULT_DELAY = 100;
+ var defaults = {
+ style: "matchhighlight",
+ minChars: 2,
+ delay: 100,
+ wordsOnly: false,
+ annotateScrollbar: false,
+ showToken: false,
+ trim: true
+ }
function State(options) {
- if (typeof options == "object") {
- this.minChars = options.minChars;
- this.style = options.style;
- this.showToken = options.showToken;
- this.delay = options.delay;
- }
- if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
- if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
- if (this.delay == null) this.delay = DEFAULT_DELAY;
+ this.options = {}
+ for (var name in defaults)
+ this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]
this.overlay = this.timeout = null;
+ this.matchesonscroll = null;
+ this.active = false;
}
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
- var over = cm.state.matchHighlighter.overlay;
- if (over) cm.removeOverlay(over);
+ removeOverlay(cm);
clearTimeout(cm.state.matchHighlighter.timeout);
cm.state.matchHighlighter = null;
cm.off("cursorActivity", cursorActivity);
+ cm.off("focus", onFocus)
}
if (val) {
- cm.state.matchHighlighter = new State(val);
- highlightMatches(cm);
+ var state = cm.state.matchHighlighter = new State(val);
+ if (cm.hasFocus()) {
+ state.active = true
+ highlightMatches(cm)
+ } else {
+ cm.on("focus", onFocus)
+ }
cm.on("cursorActivity", cursorActivity);
}
});
function cursorActivity(cm) {
var state = cm.state.matchHighlighter;
+ if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)
+ }
+
+ function onFocus(cm) {
+ var state = cm.state.matchHighlighter
+ if (!state.active) {
+ state.active = true
+ scheduleHighlight(cm, state)
+ }
+ }
+
+ function scheduleHighlight(cm, state) {
clearTimeout(state.timeout);
- state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
+ state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
+ }
+
+ function addOverlay(cm, query, hasBoundary, style) {
+ var state = cm.state.matchHighlighter;
+ cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
+ if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
+ var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
+ state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
+ {className: "CodeMirror-selection-highlight-scrollbar"});
+ }
+ }
+
+ function removeOverlay(cm) {
+ var state = cm.state.matchHighlighter;
+ if (state.overlay) {
+ cm.removeOverlay(state.overlay);
+ state.overlay = null;
+ if (state.matchesonscroll) {
+ state.matchesonscroll.clear();
+ state.matchesonscroll = null;
+ }
+ }
}
function highlightMatches(cm) {
cm.operation(function() {
var state = cm.state.matchHighlighter;
- if (state.overlay) {
- cm.removeOverlay(state.overlay);
- state.overlay = null;
- }
- if (!cm.somethingSelected() && state.showToken) {
- var re = state.showToken === true ? /[\w$]/ : state.showToken;
+ removeOverlay(cm);
+ if (!cm.somethingSelected() && state.options.showToken) {
+ var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken;
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
while (start && re.test(line.charAt(start - 1))) --start;
while (end < line.length && re.test(line.charAt(end))) ++end;
if (start < end)
- cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
+ addOverlay(cm, line.slice(start, end), re, state.options.style);
return;
}
- if (cm.getCursor("head").line != cm.getCursor("anchor").line) return;
- var selection = cm.getSelections()[0].replace(/^\s+|\s+$/g, "");
- if (selection.length >= state.minChars)
- cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
+ var from = cm.getCursor("from"), to = cm.getCursor("to");
+ if (from.line != to.line) return;
+ if (state.options.wordsOnly && !isWord(cm, from, to)) return;
+ var selection = cm.getRange(from, to)
+ if (state.options.trim) selection = selection.replace(/^\s+|\s+$/g, "")
+ if (selection.length >= state.options.minChars)
+ addOverlay(cm, selection, false, state.options.style);
});
}
+ function isWord(cm, from, to) {
+ var str = cm.getRange(from, to);
+ if (str.match(/^\w+$/) !== null) {
+ if (from.ch > 0) {
+ var pos = {line: from.line, ch: from.ch - 1};
+ var chr = cm.getRange(pos, from);
+ if (chr.match(/\W/) === null) return false;
+ }
+ if (to.ch < cm.getLine(from.line).length) {
+ var pos = {line: to.line, ch: to.ch + 1};
+ var chr = cm.getRange(to, pos);
+ if (chr.match(/\W/) === null) return false;
+ }
+ return true;
+ } else return false;
+ }
+
function boundariesAround(stream, re) {
return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&
(stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/matchesonscrollbar.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/matchesonscrollbar.css
new file mode 100644
index 0000000000..77932cc908
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/matchesonscrollbar.css
@@ -0,0 +1,8 @@
+.CodeMirror-search-match {
+ background: gold;
+ border-top: 1px solid orange;
+ border-bottom: 1px solid orange;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ opacity: .5;
+}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/matchesonscrollbar.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/matchesonscrollbar.js
new file mode 100644
index 0000000000..8d19228971
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/matchesonscrollbar.js
@@ -0,0 +1,97 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) {
+ if (typeof options == "string") options = {className: options};
+ if (!options) options = {};
+ return new SearchAnnotation(this, query, caseFold, options);
+ });
+
+ function SearchAnnotation(cm, query, caseFold, options) {
+ this.cm = cm;
+ this.options = options;
+ var annotateOptions = {listenForChanges: false};
+ for (var prop in options) annotateOptions[prop] = options[prop];
+ if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
+ this.annotation = cm.annotateScrollbar(annotateOptions);
+ this.query = query;
+ this.caseFold = caseFold;
+ this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};
+ this.matches = [];
+ this.update = null;
+
+ this.findMatches();
+ this.annotation.update(this.matches);
+
+ var self = this;
+ cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); });
+ }
+
+ var MAX_MATCHES = 1000;
+
+ SearchAnnotation.prototype.findMatches = function() {
+ if (!this.gap) return;
+ for (var i = 0; i < this.matches.length; i++) {
+ var match = this.matches[i];
+ if (match.from.line >= this.gap.to) break;
+ if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
+ }
+ var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold);
+ var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
+ while (cursor.findNext()) {
+ var match = {from: cursor.from(), to: cursor.to()};
+ if (match.from.line >= this.gap.to) break;
+ this.matches.splice(i++, 0, match);
+ if (this.matches.length > maxMatches) break;
+ }
+ this.gap = null;
+ };
+
+ function offsetLine(line, changeStart, sizeChange) {
+ if (line <= changeStart) return line;
+ return Math.max(changeStart, line + sizeChange);
+ }
+
+ SearchAnnotation.prototype.onChange = function(change) {
+ var startLine = change.from.line;
+ var endLine = CodeMirror.changeEnd(change).line;
+ var sizeChange = endLine - change.to.line;
+ if (this.gap) {
+ this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);
+ this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);
+ } else {
+ this.gap = {from: change.from.line, to: endLine + 1};
+ }
+
+ if (sizeChange) for (var i = 0; i < this.matches.length; i++) {
+ var match = this.matches[i];
+ var newFrom = offsetLine(match.from.line, startLine, sizeChange);
+ if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);
+ var newTo = offsetLine(match.to.line, startLine, sizeChange);
+ if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);
+ }
+ clearTimeout(this.update);
+ var self = this;
+ this.update = setTimeout(function() { self.updateAfterChange(); }, 250);
+ };
+
+ SearchAnnotation.prototype.updateAfterChange = function() {
+ this.findMatches();
+ this.annotation.update(this.matches);
+ };
+
+ SearchAnnotation.prototype.clear = function() {
+ this.cm.off("change", this.changeHandler);
+ this.annotation.clear();
+ };
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/search.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/search.js
index 19f51f1dcd..753b1afe1e 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/search.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/search.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.
@@ -15,76 +18,153 @@
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
+
function searchOverlay(query, caseInsensitive) {
- var startChar;
- if (typeof query == "string") {
- startChar = query.charAt(0);
- query = new RegExp("^" + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"),
- caseInsensitive ? "i" : "");
- } else {
- query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : "");
- }
+ if (typeof query == "string")
+ query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
+ else if (!query.global)
+ query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
+
return {token: function(stream) {
- if (stream.match(query)) return "searching";
- while (!stream.eol()) {
- stream.next();
- if (startChar && !caseInsensitive)
- stream.skipTo(startChar) || stream.skipToEnd();
- if (stream.match(query, false)) break;
+ query.lastIndex = stream.pos;
+ var match = query.exec(stream.string);
+ if (match && match.index == stream.pos) {
+ stream.pos += match[0].length || 1;
+ return "searching";
+ } else if (match) {
+ stream.pos = match.index;
+ } else {
+ stream.skipToEnd();
}
}};
}
function SearchState() {
- this.posFrom = this.posTo = this.query = null;
+ this.posFrom = this.posTo = this.lastQuery = this.query = null;
this.overlay = null;
}
+
function getSearchState(cm) {
return cm.state.search || (cm.state.search = new SearchState());
}
+
function queryCaseInsensitive(query) {
return typeof query == "string" && query == query.toLowerCase();
}
+
function getSearchCursor(cm, query, pos) {
// Heuristic: if the query string is all lowercase, do a case insensitive search.
return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
}
+
+ function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
+ cm.openDialog(text, onEnter, {
+ value: deflt,
+ selectValueOnOpen: true,
+ closeOnEnter: false,
+ onClose: function() { clearSearch(cm); },
+ onKeyDown: onKeyDown
+ });
+ }
+
function dialog(cm, text, shortText, deflt, f) {
- if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
+ if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
else f(prompt(shortText, deflt));
}
+
function confirmDialog(cm, text, shortText, fs) {
if (cm.openConfirm) cm.openConfirm(text, fs);
else if (confirm(shortText)) fs[0]();
}
+
+ function parseString(string) {
+ return string.replace(/\\(.)/g, function(_, ch) {
+ if (ch == "n") return "\n"
+ if (ch == "r") return "\r"
+ return ch
+ })
+ }
+
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
- query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i");
- if (query.test("")) query = /x^/;
- } else if (query == "") {
- query = /x^/;
+ try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
+ catch(e) {} // Not a regular expression after all, do a string search
+ } else {
+ query = parseString(query)
}
+ if (typeof query == "string" ? query == "" : query.test(""))
+ query = /x^/;
return query;
}
+
var queryDialog =
- 'Search: (Use /re/ syntax for regexp search) ';
- function doSearch(cm, rev) {
+ 'Search: (Use /re/ syntax for regexp search) ';
+
+ function startSearch(cm, state, query) {
+ state.queryText = query;
+ state.query = parseQuery(query);
+ cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
+ state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
+ cm.addOverlay(state.overlay);
+ if (cm.showMatchesOnScrollbar) {
+ if (state.annotate) { state.annotate.clear(); state.annotate = null; }
+ state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
+ }
+ }
+
+ function doSearch(cm, rev, persistent, immediate) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
- dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
- cm.operation(function() {
- if (!query || state.query) return;
- state.query = parseQuery(query);
- cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
- state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
- cm.addOverlay(state.overlay);
- state.posFrom = state.posTo = cm.getCursor();
+ var q = cm.getSelection() || state.lastQuery;
+ if (persistent && cm.openDialog) {
+ var hiding = null
+ var searchNext = function(query, event) {
+ CodeMirror.e_stop(event);
+ if (!query) return;
+ if (query != state.queryText) {
+ startSearch(cm, state, query);
+ state.posFrom = state.posTo = cm.getCursor();
+ }
+ if (hiding) hiding.style.opacity = 1
+ findNext(cm, event.shiftKey, function(_, to) {
+ var dialog
+ if (to.line < 3 && document.querySelector &&
+ (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
+ dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
+ (hiding = dialog).style.opacity = .4
+ })
+ };
+ persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
+ var keyName = CodeMirror.keyName(event)
+ var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
+ if (!cmd) cmd = cm.getOption('extraKeys')[keyName]
+ if (cmd == "findNext" || cmd == "findPrev" ||
+ cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
+ CodeMirror.e_stop(event);
+ startSearch(cm, getSearchState(cm), query);
+ cm.execCommand(cmd);
+ } else if (cmd == "find" || cmd == "findPersistent") {
+ CodeMirror.e_stop(event);
+ searchNext(query, event);
+ }
+ });
+ if (immediate && q) {
+ startSearch(cm, state, q);
findNext(cm, rev);
+ }
+ } else {
+ dialog(cm, queryDialog, "Search for:", q, function(query) {
+ if (query && !state.query) cm.operation(function() {
+ startSearch(cm, state, query);
+ state.posFrom = state.posTo = cm.getCursor();
+ findNext(cm, rev);
+ });
});
- });
+ }
}
- function findNext(cm, rev) {cm.operation(function() {
+
+ function findNext(cm, rev, callback) {cm.operation(function() {
var state = getSearchState(cm);
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) {
@@ -92,37 +172,50 @@
if (!cursor.find(rev)) return;
}
cm.setSelection(cursor.from(), cursor.to());
- cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
+ cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
state.posFrom = cursor.from(); state.posTo = cursor.to();
+ if (callback) callback(cursor.from(), cursor.to())
});}
+
function clearSearch(cm) {cm.operation(function() {
var state = getSearchState(cm);
+ state.lastQuery = state.query;
if (!state.query) return;
- state.query = null;
+ state.query = state.queryText = null;
cm.removeOverlay(state.overlay);
+ if (state.annotate) { state.annotate.clear(); state.annotate = null; }
});}
var replaceQueryDialog =
- 'Replace: (Use /re/ syntax for regexp search) ';
- var replacementQueryDialog = 'With: ';
- var doReplaceConfirm = "Replace? Yes No Stop ";
+ ' (Use /re/ syntax for regexp search) ';
+ var replacementQueryDialog = 'With: ';
+ var doReplaceConfirm = "Replace? Yes No All Stop ";
+
+ function replaceAll(cm, query, text) {
+ cm.operation(function() {
+ for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
+ if (typeof query != "string") {
+ var match = cm.getRange(cursor.from(), cursor.to()).match(query);
+ cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
+ } else cursor.replace(text);
+ }
+ });
+ }
+
function replace(cm, all) {
- dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
+ if (cm.getOption("readOnly")) return;
+ var query = cm.getSelection() || getSearchState(cm).lastQuery;
+ var dialogText = all ? "Replace all:" : "Replace:"
+ dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
if (!query) return;
query = parseQuery(query);
dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
+ text = parseString(text)
if (all) {
- cm.operation(function() {
- for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
- if (typeof query != "string") {
- var match = cm.getRange(cursor.from(), cursor.to()).match(query);
- cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
- } else cursor.replace(text);
- }
- });
+ replaceAll(cm, query, text)
} else {
clearSearch(cm);
- var cursor = getSearchCursor(cm, query, cm.getCursor());
+ var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
var advance = function() {
var start = cursor.from(), match;
if (!(match = cursor.findNext())) {
@@ -133,7 +226,8 @@
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
confirmDialog(cm, doReplaceConfirm, "Replace?",
- [function() {doReplace(match);}, advance]);
+ [function() {doReplace(match);}, advance,
+ function() {replaceAll(cm, query, text)}]);
};
var doReplace = function(match) {
cursor.replace(typeof query == "string" ? text :
@@ -147,6 +241,9 @@
}
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
+ CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
+ CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
+ CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
CodeMirror.commands.findNext = doSearch;
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
CodeMirror.commands.clearSearch = clearSearch;
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/searchcursor.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/searchcursor.js
index 899f44c4ab..b70242ee4b 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/searchcursor.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/search/searchcursor.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -104,7 +107,7 @@
var from = Pos(pos.line, cut);
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
if (target[i] != fold(doc.getLine(ln))) return;
- if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return;
+ if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
return {from: from, to: Pos(ln, origTarget[last].length)};
}
};
@@ -145,10 +148,10 @@
from: function() {if (this.atOccurrence) return this.pos.from;},
to: function() {if (this.atOccurrence) return this.pos.to;},
- replace: function(newText) {
+ replace: function(newText, origin) {
if (!this.atOccurrence) return;
var lines = CodeMirror.splitLines(newText);
- this.doc.replaceRange(lines, this.pos.from, this.pos.to);
+ this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
}
@@ -174,9 +177,9 @@
});
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
- var ranges = [], next;
+ var ranges = [];
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
- while (next = cur.findNext()) {
+ while (cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
ranges.push({anchor: cur.from(), head: cur.to()});
}
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/active-line.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/active-line.js
index a818f109b6..b0b3f61af2 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/active-line.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/active-line.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Because sometimes you need to style the cursor's line.
//
// Adds an option 'styleActiveLine' which, when enabled, gives the
@@ -15,6 +18,7 @@
"use strict";
var WRAP_CLASS = "CodeMirror-activeline";
var BACK_CLASS = "CodeMirror-activeline-background";
+ var GUTT_CLASS = "CodeMirror-activeline-gutter";
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
@@ -33,6 +37,7 @@
for (var i = 0; i < cm.state.activeLines.length; i++) {
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
+ cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
}
}
@@ -46,7 +51,9 @@
function updateActiveLines(cm, ranges) {
var active = [];
for (var i = 0; i < ranges.length; i++) {
- var line = cm.getLineHandleVisualStart(ranges[i].head.line);
+ var range = ranges[i];
+ if (!range.empty()) continue;
+ var line = cm.getLineHandleVisualStart(range.head.line);
if (active[active.length - 1] != line) active.push(line);
}
if (sameArray(cm.state.activeLines, active)) return;
@@ -55,6 +62,7 @@
for (var i = 0; i < active.length; i++) {
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
cm.addLineClass(active[i], "background", BACK_CLASS);
+ cm.addLineClass(active[i], "gutter", GUTT_CLASS);
}
cm.state.activeLines = active;
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/mark-selection.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/mark-selection.js
index ae0d393143..5c42d21eb2 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/mark-selection.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/mark-selection.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Because sometimes you need to mark the selected *text*.
//
// Adds an option 'styleSelectedText' which, when enabled, gives
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/selection-pointer.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/selection-pointer.js
new file mode 100644
index 0000000000..ef5e404ad3
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/selection/selection-pointer.js
@@ -0,0 +1,98 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineOption("selectionPointer", false, function(cm, val) {
+ var data = cm.state.selectionPointer;
+ if (data) {
+ CodeMirror.off(cm.getWrapperElement(), "mousemove", data.mousemove);
+ CodeMirror.off(cm.getWrapperElement(), "mouseout", data.mouseout);
+ CodeMirror.off(window, "scroll", data.windowScroll);
+ cm.off("cursorActivity", reset);
+ cm.off("scroll", reset);
+ cm.state.selectionPointer = null;
+ cm.display.lineDiv.style.cursor = "";
+ }
+ if (val) {
+ data = cm.state.selectionPointer = {
+ value: typeof val == "string" ? val : "default",
+ mousemove: function(event) { mousemove(cm, event); },
+ mouseout: function(event) { mouseout(cm, event); },
+ windowScroll: function() { reset(cm); },
+ rects: null,
+ mouseX: null, mouseY: null,
+ willUpdate: false
+ };
+ CodeMirror.on(cm.getWrapperElement(), "mousemove", data.mousemove);
+ CodeMirror.on(cm.getWrapperElement(), "mouseout", data.mouseout);
+ CodeMirror.on(window, "scroll", data.windowScroll);
+ cm.on("cursorActivity", reset);
+ cm.on("scroll", reset);
+ }
+ });
+
+ function mousemove(cm, event) {
+ var data = cm.state.selectionPointer;
+ if (event.buttons == null ? event.which : event.buttons) {
+ data.mouseX = data.mouseY = null;
+ } else {
+ data.mouseX = event.clientX;
+ data.mouseY = event.clientY;
+ }
+ scheduleUpdate(cm);
+ }
+
+ function mouseout(cm, event) {
+ if (!cm.getWrapperElement().contains(event.relatedTarget)) {
+ var data = cm.state.selectionPointer;
+ data.mouseX = data.mouseY = null;
+ scheduleUpdate(cm);
+ }
+ }
+
+ function reset(cm) {
+ cm.state.selectionPointer.rects = null;
+ scheduleUpdate(cm);
+ }
+
+ function scheduleUpdate(cm) {
+ if (!cm.state.selectionPointer.willUpdate) {
+ cm.state.selectionPointer.willUpdate = true;
+ setTimeout(function() {
+ update(cm);
+ cm.state.selectionPointer.willUpdate = false;
+ }, 50);
+ }
+ }
+
+ function update(cm) {
+ var data = cm.state.selectionPointer;
+ if (!data) return;
+ if (data.rects == null && data.mouseX != null) {
+ data.rects = [];
+ if (cm.somethingSelected()) {
+ for (var sel = cm.display.selectionDiv.firstChild; sel; sel = sel.nextSibling)
+ data.rects.push(sel.getBoundingClientRect());
+ }
+ }
+ var inside = false;
+ if (data.mouseX != null) for (var i = 0; i < data.rects.length; i++) {
+ var rect = data.rects[i];
+ if (rect.left <= data.mouseX && rect.right >= data.mouseX &&
+ rect.top <= data.mouseY && rect.bottom >= data.mouseY)
+ inside = true;
+ }
+ var cursor = inside ? data.value : "";
+ if (cm.display.lineDiv.style.cursor != cursor)
+ cm.display.lineDiv.style.cursor = cursor;
+ }
+});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.css b/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.css
index eacc2f053a..c4b8a2f77e 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.css
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.css
@@ -1,6 +1,7 @@
.CodeMirror-Tern-completion {
padding-left: 22px;
position: relative;
+ line-height: 1.5;
}
.CodeMirror-Tern-completion:before {
position: absolute;
@@ -76,6 +77,7 @@
.CodeMirror-Tern-hint-doc {
max-width: 25em;
+ margin-top: -3px;
}
.CodeMirror-Tern-fname { color: black; }
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.js
index 6fbd10c55d..efdf2ed628 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/tern.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// Glue code between CodeMirror and Tern.
//
// Create a CodeMirror.TernServer to wrap an actual Tern server,
@@ -14,7 +17,7 @@
// indicate that a file is not available.
// * fileFilter: A function(value, docName, doc) that will be applied
// to documents before passing them on to Tern.
-// * switchToDoc: A function(name) that should, when providing a
+// * switchToDoc: A function(name, doc) that should, when providing a
// multi-file view, switch the view or focus to the named file.
// * showError: A function(editor, message) that can be used to
// override the way errors are displayed.
@@ -56,6 +59,7 @@
this.options = options || {};
var plugins = this.options.plugins || (this.options.plugins = {});
if (!plugins.doc_comment) plugins.doc_comment = true;
+ this.docs = Object.create(null);
if (this.options.useWorker) {
this.server = new WorkerServer(this);
} else {
@@ -66,12 +70,14 @@
plugins: plugins
});
}
- this.docs = Object.create(null);
this.trackChange = function(doc, change) { trackChange(self, doc, change); };
this.cachedArgHints = null;
this.activeArgHints = null;
this.jumpStack = [];
+
+ this.getHint = function(cm, c) { return hint(self, cm, c); };
+ this.getHint.async = true;
};
CodeMirror.TernServer.prototype = {
@@ -82,28 +88,27 @@
return this.docs[name] = data;
},
- delDoc: function(name) {
- var found = this.docs[name];
+ delDoc: function(id) {
+ var found = resolveDoc(this, id);
if (!found) return;
CodeMirror.off(found.doc, "change", this.trackChange);
- delete this.docs[name];
- this.server.delFile(name);
+ delete this.docs[found.name];
+ this.server.delFile(found.name);
},
- hideDoc: function(name) {
+ hideDoc: function(id) {
closeArgHints(this);
- var found = this.docs[name];
+ var found = resolveDoc(this, id);
if (found && found.changed) sendDoc(this, found);
},
complete: function(cm) {
- var self = this;
- CodeMirror.showHint(cm, function(cm, c) { return hint(self, cm, c); }, {async: true});
+ cm.showHint({hint: this.getHint});
},
- getHint: function(cm, c) { return hint(this, cm, c); },
+ showType: function(cm, pos, c) { showContextInfo(this, cm, pos, "type", c); },
- showType: function(cm, pos) { showType(this, cm, pos); },
+ showDocs: function(cm, pos, c) { showContextInfo(this, cm, pos, "documentation", c); },
updateArgHints: function(cm) { updateArgHints(this, cm); },
@@ -119,12 +124,22 @@
var self = this;
var doc = findDoc(this, cm.getDoc());
var request = buildRequest(this, doc, query, pos);
+ var extraOptions = request.query && this.options.queryOptions && this.options.queryOptions[request.query.type]
+ if (extraOptions) for (var prop in extraOptions) request.query[prop] = extraOptions[prop];
this.server.request(request, function (error, data) {
if (!error && self.options.responseFilter)
data = self.options.responseFilter(doc, query, request, error, data);
c(error, data);
});
+ },
+
+ destroy: function () {
+ closeArgHints(this)
+ if (this.worker) {
+ this.worker.terminate();
+ this.worker = null;
+ }
}
};
@@ -154,11 +169,17 @@
return ts.addDoc(name, doc);
}
+ function resolveDoc(ts, id) {
+ if (typeof id == "string") return ts.docs[id];
+ if (id instanceof CodeMirror) id = id.getDoc();
+ if (id instanceof CodeMirror.Doc) return findDoc(ts, id);
+ }
+
function trackChange(ts, doc, change) {
var data = findDoc(ts, doc);
var argHints = ts.cachedArgHints;
- if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
+ if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) >= 0)
ts.cachedArgHints = null;
var changed = data.changed;
@@ -196,7 +217,7 @@
var completion = data.completions[i], className = typeToIcon(completion.type);
if (data.guess) className += " " + cls + "guess";
completions.push({text: completion.name + after,
- displayText: completion.name,
+ displayText: completion.displayName || completion.name,
className: className,
data: completion});
}
@@ -230,8 +251,8 @@
// Type queries
- function showType(ts, cm, pos) {
- ts.request(cm, "type", function(error, data) {
+ function showContextInfo(ts, cm, pos, queryName, c) {
+ ts.request(cm, queryName, function(error, data) {
if (error) return showError(ts, cm, error);
if (ts.options.typeTip) {
var tip = ts.options.typeTip(data);
@@ -241,10 +262,13 @@
tip.appendChild(document.createTextNode(" â " + data.doc));
if (data.url) {
tip.appendChild(document.createTextNode(" "));
- tip.appendChild(elt("a", null, "[docs]")).href = data.url;
+ var child = tip.appendChild(elt("a", null, "[docs]"));
+ child.href = data.url;
+ child.target = "_blank";
}
}
- tempTooltip(cm, tip);
+ tempTooltip(cm, tip, ts);
+ if (c) c();
}, pos);
}
@@ -282,7 +306,7 @@
ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
if (error || !data.type || !(/^fn\(/).test(data.type)) return;
ts.cachedArgHints = {
- start: pos,
+ start: start,
type: parseFnType(data.type),
name: data.exprName || data.name || "fn",
guess: data.guess,
@@ -381,10 +405,10 @@
}
function moveTo(ts, curDoc, doc, start, end) {
- doc.doc.setSelection(end, start);
+ doc.doc.setSelection(start, end);
if (curDoc != doc && ts.options.switchToDoc) {
closeArgHints(ts);
- ts.options.switchToDoc(doc.name);
+ ts.options.switchToDoc(doc.name, doc.doc);
}
}
@@ -421,15 +445,15 @@
function atInterestingExpression(cm) {
var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
- if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
- return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
+ if (tok.start < pos.ch && tok.type == "comment") return false;
+ return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
}
// Variable renaming
function rename(ts, cm) {
var token = cm.getTokenAt(cm.getCursor());
- if (!/\w/.test(token.string)) showError(ts, cm, "Not at a variable");
+ if (!/\w/.test(token.string)) return showError(ts, cm, "Not at a variable");
dialog(cm, "New name for " + token.string, function(newName) {
ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
if (error) return showError(ts, cm, error);
@@ -439,17 +463,16 @@
}
function selectName(ts, cm) {
- var cur = cm.getCursor(), token = cm.getTokenAt(cur);
- if (!/\w/.test(token.string)) showError(ts, cm, "Not at a variable");
var name = findDoc(ts, cm.doc).name;
ts.request(cm, {type: "refs"}, function(error, data) {
if (error) return showError(ts, cm, error);
var ranges = [], cur = 0;
+ var curPos = cm.getCursor();
for (var i = 0; i < data.refs.length; i++) {
var ref = data.refs[i];
if (ref.file == name) {
ranges.push({anchor: ref.start, head: ref.end});
- if (cmpPos(cur, ref.start) >= 0 && cmpPos(cur, ref.end) <= 0)
+ if (cmpPos(curPos, ref.start) >= 0 && cmpPos(curPos, ref.end) <= 0)
cur = ranges.length - 1;
}
}
@@ -571,16 +594,34 @@
// Tooltips
- function tempTooltip(cm, content) {
+ function tempTooltip(cm, content, ts) {
+ if (cm.state.ternTooltip) remove(cm.state.ternTooltip);
var where = cm.cursorCoords();
- var tip = makeTooltip(where.right + 1, where.bottom, content);
+ var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content);
+ function maybeClear() {
+ old = true;
+ if (!mouseOnTip) clear();
+ }
function clear() {
+ cm.state.ternTooltip = null;
if (!tip.parentNode) return;
cm.off("cursorActivity", clear);
+ cm.off('blur', clear);
+ cm.off('scroll', clear);
fadeOut(tip);
}
- setTimeout(clear, 1700);
+ var mouseOnTip = false, old = false;
+ CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
+ CodeMirror.on(tip, "mouseout", function(e) {
+ if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) {
+ if (old) clear();
+ else mouseOnTip = false;
+ }
+ });
+ setTimeout(maybeClear, ts.options.hintDelay ? ts.options.hintDelay : 1700);
cm.on("cursorActivity", clear);
+ cm.on('blur', clear);
+ cm.on('scroll', clear);
}
function makeTooltip(x, y, content) {
@@ -605,7 +646,7 @@
if (ts.options.showError)
ts.options.showError(cm, msg);
else
- tempTooltip(cm, String(msg));
+ tempTooltip(cm, String(msg), ts);
}
function closeArgHints(ts) {
@@ -621,7 +662,7 @@
// Worker wrapper
function WorkerServer(ts) {
- var worker = new Worker(ts.options.workerScript);
+ var worker = ts.worker = new Worker(ts.options.workerScript);
worker.postMessage({type: "init",
defs: ts.options.defs,
plugins: ts.options.plugins,
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/worker.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/worker.js
index 1ff63de411..887f906a44 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/worker.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/tern/worker.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// declare global: tern, server
var server;
@@ -36,6 +39,6 @@ function startServer(defs, plugins, scripts) {
});
}
-var console = {
+this.console = {
log: function(v) { postMessage({type: "debug", message: v}); }
};
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/addon/wrap/hardwrap.js b/wcfsetup/install/files/js/3rdParty/codemirror/addon/wrap/hardwrap.js
index 87aab1b8f6..04851f99ff 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/addon/wrap/hardwrap.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/addon/wrap/hardwrap.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -27,13 +30,17 @@
}
function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
- for (var at = column; at > 0; --at)
+ var at = column
+ while (at < text.length && text.charAt(at) == " ") at++
+ for (; at > 0; --at)
if (wrapOn.test(text.slice(at - 1, at + 1))) break;
- if (at == 0) at = column;
- var endOfText = at;
- if (killTrailingSpace)
- while (text.charAt(endOfText - 1) == " ") --endOfText;
- return {from: endOfText, to: at};
+ for (var first = true;; first = false) {
+ var endOfText = at;
+ if (killTrailingSpace)
+ while (text.charAt(endOfText - 1) == " ") --endOfText;
+ if (endOfText == 0 && first) at = column;
+ else return {from: endOfText, to: at};
+ }
}
function wrapRange(cm, from, to, options) {
@@ -83,7 +90,8 @@
if (changes.length) cm.operation(function() {
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
- cm.replaceRange(change.text, change.from, change.to);
+ if (change.text || CodeMirror.cmpPos(change.from, change.to))
+ cm.replaceRange(change.text, change.from, change.to);
}
});
return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null;
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.css b/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.css
index d263e44b71..18b0bf70db 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.css
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.css
@@ -4,10 +4,7 @@
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
-}
-.CodeMirror-scroll {
- /* Set scrolling behaviour here */
- overflow: auto;
+ color: black;
}
/* PADDING */
@@ -36,45 +33,93 @@
min-width: 20px;
text-align: right;
color: #999;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
+ white-space: nowrap;
}
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
/* CURSOR */
-.CodeMirror div.CodeMirror-cursor {
+.CodeMirror-cursor {
border-left: 1px solid black;
+ border-right: none;
+ width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
-.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+.cm-fat-cursor .CodeMirror-cursor {
width: auto;
- border: 0;
+ border: 0 !important;
background: #7e7;
}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
/* Can style cursor different in overwrite (non-insert) mode */
-div.CodeMirror-overwrite div.CodeMirror-cursor {}
+.CodeMirror-overwrite .CodeMirror-cursor {}
-.cm-tab { display: inline-block; }
+.cm-tab { display: inline-block; text-decoration: inherit; }
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0; right: 0; top: -50px; bottom: -20px;
+ overflow: hidden;
+}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
+ top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
-.cm-s-default .cm-variable {color: black;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
-.cm-s-default .cm-property {color: black;}
-.cm-s-default .cm-operator {color: black;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
@@ -84,22 +129,19 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
-.cm-s-default .cm-header {color: blue;}
-.cm-s-default .cm-quote {color: #090;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
-.cm-negative {color: #d44;}
-.cm-positive {color: #292;}
-.cm-header, .cm-strong {font-weight: bold;}
-.cm-em {font-style: italic;}
-.cm-link {text-decoration: underline;}
-
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
@@ -108,14 +150,13 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
the editor. You probably shouldn't touch them. */
.CodeMirror {
- line-height: 1;
position: relative;
overflow: hidden;
background: white;
- color: black;
}
.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
@@ -123,18 +164,14 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
- before actuall scrolling happens, thus preventing shaking and
+ before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
@@ -160,29 +197,44 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
- padding-bottom: 30px;
+ min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
- padding-bottom: 30px;
- margin-bottom: -32px;
display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
+.CodeMirror-gutter-wrapper {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
.CodeMirror-lines {
cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
@@ -199,6 +251,9 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
z-index: 2;
position: relative;
overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: none;
+ font-variant-ligatures: none;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
@@ -220,8 +275,18 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-widget {}
-.CodeMirror-wrap .CodeMirror-scroll {
- overflow-x: hidden;
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
}
.CodeMirror-measure {
@@ -231,25 +296,31 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
overflow: hidden;
visibility: hidden;
}
-.CodeMirror-measure pre { position: static; }
-.CodeMirror div.CodeMirror-cursor {
+.CodeMirror-cursor {
position: absolute;
- border-right: none;
- width: 0;
+ pointer-events: none;
}
+.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
- z-index: 1;
+ z-index: 3;
}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background: #ffa;
@@ -268,3 +339,9 @@ div.CodeMirror-cursors {
visibility: hidden;
}
}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.js b/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.js
index c3205cc189..32d9743981 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/codemirror.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
// This is CodeMirror (http://codemirror.net), a code editor
// implemented in JavaScript on top of the browser's DOM.
//
@@ -10,7 +13,7 @@
else if (typeof define == "function" && define.amd) // AMD
return define([], mod);
else // Plain browser env
- this.CodeMirror = mod();
+ (this || window).CodeMirror = mod();
})(function() {
"use strict";
@@ -18,37 +21,35 @@
// Kludges for bugs and behavior differences that can't be feature
// detected are enabled based on userAgent etc sniffing.
+ var userAgent = navigator.userAgent;
+ var platform = navigator.platform;
- var gecko = /gecko\/\d/i.test(navigator.userAgent);
- // ie_uptoN means Internet Explorer version N or lower
- var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
- var ie_upto7 = ie_upto10 && (document.documentMode == null || document.documentMode < 8);
- var ie_upto8 = ie_upto10 && (document.documentMode == null || document.documentMode < 9);
- var ie_upto9 = ie_upto10 && (document.documentMode == null || document.documentMode < 10);
- var ie_11up = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
+ var gecko = /gecko\/\d/i.test(userAgent);
+ var ie_upto10 = /MSIE \d/.test(userAgent);
+ var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent);
var ie = ie_upto10 || ie_11up;
- var webkit = /WebKit\//.test(navigator.userAgent);
- var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
- var chrome = /Chrome\//.test(navigator.userAgent);
- var presto = /Opera\//.test(navigator.userAgent);
+ var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
+ var webkit = /WebKit\//.test(userAgent);
+ var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
+ var chrome = /Chrome\//.test(userAgent);
+ var presto = /Opera\//.test(userAgent);
var safari = /Apple Computer/.test(navigator.vendor);
- var khtml = /KHTML\//.test(navigator.userAgent);
- var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
- var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
- var phantom = /PhantomJS/.test(navigator.userAgent);
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
+ var phantom = /PhantomJS/.test(userAgent);
- var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
+ var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
// This is woefully incomplete. Suggestions for alternative methods welcome.
- var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
- var mac = ios || /Mac/.test(navigator.platform);
- var windows = /win/i.test(navigator.platform);
+ var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);
+ var mac = ios || /Mac/.test(platform);
+ var chromeOS = /\bCrOS\b/.test(userAgent);
+ var windows = /win/i.test(platform);
- var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
+ var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/);
if (presto_version) presto_version = Number(presto_version[1]);
if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
// Some browsers use the wrong event properties to signal cmd/ctrl on OS X
var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
- var captureRightClick = gecko || (ie && !ie_upto8);
+ var captureRightClick = gecko || (ie && ie_version >= 9);
// Optimize some code when these features are not used.
var sawReadOnlySpans = false, sawCollapsedSpans = false;
@@ -61,55 +62,70 @@
function CodeMirror(place, options) {
if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
- this.options = options = options || {};
+ this.options = options = options ? copyObj(options) : {};
// Determine effective options based on given values and defaults.
- for (var opt in defaults) if (!options.hasOwnProperty(opt))
- options[opt] = defaults[opt];
+ copyObj(defaults, options, false);
setGuttersForLineNumbers(options);
var doc = options.value;
- if (typeof doc == "string") doc = new Doc(doc, options.mode);
+ if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator);
this.doc = doc;
- var display = this.display = new Display(place, doc);
+ var input = new CodeMirror.inputStyles[options.inputStyle](this);
+ var display = this.display = new Display(place, doc, input);
display.wrapper.CodeMirror = this;
updateGutters(this);
themeChanged(this);
if (options.lineWrapping)
this.display.wrapper.className += " CodeMirror-wrap";
- if (options.autofocus && !mobile) focusInput(this);
+ if (options.autofocus && !mobile) display.input.focus();
+ initScrollbars(this);
this.state = {
keyMaps: [], // stores maps added by addKeyMap
overlays: [], // highlighting overlays, as added by addOverlay
modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
- overwrite: false, focused: false,
+ overwrite: false,
+ delayingBlurEvent: false,
+ focused: false,
suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
- pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
+ pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
+ selectingText: false,
draggingText: false,
- highlight: new Delayed() // stores highlight worker timeout
+ highlight: new Delayed(), // stores highlight worker timeout
+ keySeq: null, // Unfinished key sequence
+ specialChars: null
};
+ var cm = this;
+
// Override magic textarea content restore that IE sometimes does
// on our hidden textarea on reload
- if (ie_upto10) setTimeout(bind(resetInput, this, true), 20);
+ if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20);
registerEventHandlers(this);
+ ensureGlobalHandlers();
- var cm = this;
- runInOp(this, function() {
- cm.curOp.forceUpdate = true;
- attachDoc(cm, doc);
+ startOperation(this);
+ this.curOp.forceUpdate = true;
+ attachDoc(this, doc);
- if ((options.autofocus && !mobile) || activeElt() == display.input)
- setTimeout(bind(onFocus, cm), 20);
- else
- onBlur(cm);
+ if ((options.autofocus && !mobile) || cm.hasFocus())
+ setTimeout(bind(onFocus, this), 20);
+ else
+ onBlur(this);
- for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
- optionHandlers[opt](cm, options[opt], Init);
- for (var i = 0; i < initHooks.length; ++i) initHooks[i](cm);
- });
+ for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
+ optionHandlers[opt](this, options[opt], Init);
+ maybeUpdateLineNumberWidth(this);
+ if (options.finishInit) options.finishInit(this);
+ for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
+ endOperation(this);
+ // Suppress optimizelegibility in Webkit, since it breaks text
+ // measuring on line wrapping boundaries.
+ if (webkit && options.lineWrapping &&
+ getComputedStyle(display.lineDiv).textRendering == "optimizelegibility")
+ display.lineDiv.style.textRendering = "auto";
}
// DISPLAY CONSTRUCTOR
@@ -118,32 +134,17 @@
// and content drawing. It holds references to DOM nodes and
// display-related state.
- function Display(place, doc) {
+ function Display(place, doc, input) {
var d = this;
+ this.input = input;
- // The semihidden textarea that is focused when the editor is
- // focused, and receives input.
- var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
- // The textarea is kept positioned near the cursor to prevent the
- // fact that it'll be scrolled into view on input from scrolling
- // our fake cursor out of view. On webkit, when wrap=off, paste is
- // very slow. So make the area wide instead.
- if (webkit) input.style.width = "1000px";
- else input.setAttribute("wrap", "off");
- // If border: 0; -- iOS fails to open keyboard (issue #1287)
- if (ios) input.style.border = "1px solid black";
- input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
-
- // Wraps and hides input textarea
- d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
- // The fake scrollbar elements.
- d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
- d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
// Covers bottom-right square when both scrollbars are present.
d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
+ d.scrollbarFiller.setAttribute("cm-not-content", "true");
// Covers bottom of gutter when coverGutterNextToScrollbar is on
// and h scrollbar is present.
d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
+ d.gutterFiller.setAttribute("cm-not-content", "true");
// Will contain the actual code, positioned to cover the viewport.
d.lineDiv = elt("div", null, "CodeMirror-code");
// Elements are added to these to represent selection and cursors.
@@ -160,10 +161,11 @@
d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
// Set to the height of the document, allowing scrolling.
d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
+ d.sizerWidth = null;
// Behavior of elts with overflow: auto and padding is
// inconsistent across browsers. This is used to ensure the
// scrollable area is big enough.
- d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
+ d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;");
// Will contain the gutters, if any.
d.gutters = elt("div", null, "CodeMirror-gutters");
d.lineGutter = null;
@@ -171,56 +173,44 @@
d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
d.scroller.setAttribute("tabIndex", "-1");
// The element in which the editor lives.
- d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
- d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
+ d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
// Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
- if (ie_upto7) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
- // Needed to hide big blue blinking cursor on Mobile Safari
- if (ios) input.style.width = "0px";
- if (!webkit) d.scroller.draggable = true;
- // Needed to handle Tab key in KHTML
- if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
- // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
- if (ie_upto7) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px";
+ if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
+ if (!webkit && !(gecko && mobile)) d.scroller.draggable = true;
- if (place.appendChild) place.appendChild(d.wrapper);
- else place(d.wrapper);
+ if (place) {
+ if (place.appendChild) place.appendChild(d.wrapper);
+ else place(d.wrapper);
+ }
// Current rendered range (may be bigger than the view window).
d.viewFrom = d.viewTo = doc.first;
+ d.reportedViewFrom = d.reportedViewTo = doc.first;
// Information about the rendered lines.
d.view = [];
+ d.renderedView = null;
// Holds info about a single rendered line when it was rendered
// for measurement, while not in view.
d.externalMeasured = null;
// Empty space (in pixels) above the view
d.viewOffset = 0;
- d.lastSizeC = 0;
+ d.lastWrapHeight = d.lastWrapWidth = 0;
d.updateLineNumbers = null;
+ d.nativeBarWidth = d.barHeight = d.barWidth = 0;
+ d.scrollbarsClipped = false;
+
// Used to only resize the line number gutter when necessary (when
// the amount of lines crosses a boundary that makes its width change)
d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
- // See readInput and resetInput
- d.prevInput = "";
// Set to true when a non-horizontal-scrolling line widget is
// added. As an optimization, line widget aligning is skipped when
// this is false.
d.alignWidgets = false;
- // Flag that indicates whether we expect input to appear real soon
- // now (after some event like 'keypress' or 'input') and are
- // polling intensively.
- d.pollingFast = false;
- // Self-resetting timeout for the poller
- d.poll = new Delayed();
d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
- // Tracks when resetInput has punted to just putting a short
- // string into the textarea instead of the full selection.
- d.inaccurateSelection = false;
-
// Tracks the maximum line length so that the horizontal scrollbar
// can be kept static when scrolling.
d.maxLine = null;
@@ -232,6 +222,14 @@
// True when shift is held down.
d.shift = false;
+
+ // Used to track whether anything happened since the context menu
+ // was opened.
+ d.selForContextMenu = null;
+
+ d.activeTouch = null;
+
+ input.init(d);
}
// STATE UPDATES
@@ -256,10 +254,11 @@
function wrappingChanged(cm) {
if (cm.options.lineWrapping) {
- cm.display.wrapper.className += " CodeMirror-wrap";
+ addClass(cm.display.wrapper, "CodeMirror-wrap");
cm.display.sizer.style.minWidth = "";
+ cm.display.sizerWidth = null;
} else {
- cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
+ rmClass(cm.display.wrapper, "CodeMirror-wrap");
findMaxLine(cm);
}
estimateLineHeights(cm);
@@ -297,12 +296,6 @@
});
}
- function keyMapChanged(cm) {
- var map = keyMap[cm.options.keyMap], style = map.style;
- cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
- (style ? " cm-keymap-" + style : "");
- }
-
function themeChanged(cm) {
cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
@@ -329,9 +322,12 @@
}
}
gutters.style.display = i ? "" : "none";
- var width = gutters.offsetWidth;
+ updateGutterSpace(cm);
+ }
+
+ function updateGutterSpace(cm) {
+ var width = cm.display.gutters.offsetWidth;
cm.display.sizer.style.marginLeft = width + "px";
- if (i) cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
}
// Compute the character length of a line, taking into account
@@ -387,85 +383,203 @@
// Prepare DOM reads needed to update the scrollbars. Done in one
// shot to minimize update/measure roundtrips.
function measureForScrollbars(cm) {
- var scroll = cm.display.scroller;
+ var d = cm.display, gutterW = d.gutters.offsetWidth;
+ var docH = Math.round(cm.doc.height + paddingVert(cm.display));
return {
- clientHeight: scroll.clientHeight,
- barHeight: cm.display.scrollbarV.clientHeight,
- scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth,
- barWidth: cm.display.scrollbarH.clientWidth,
- docHeight: Math.round(cm.doc.height + paddingVert(cm.display))
+ clientHeight: d.scroller.clientHeight,
+ viewHeight: d.wrapper.clientHeight,
+ scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
+ viewWidth: d.wrapper.clientWidth,
+ barLeft: cm.options.fixedGutter ? gutterW : 0,
+ docHeight: docH,
+ scrollHeight: docH + scrollGap(cm) + d.barHeight,
+ nativeBarWidth: d.nativeBarWidth,
+ gutterWidth: gutterW
};
}
- // Re-synchronize the fake scrollbars with the actual size of the
- // content.
+ function NativeScrollbars(place, scroll, cm) {
+ this.cm = cm;
+ var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
+ var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
+ place(vert); place(horiz);
+
+ on(vert, "scroll", function() {
+ if (vert.clientHeight) scroll(vert.scrollTop, "vertical");
+ });
+ on(horiz, "scroll", function() {
+ if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal");
+ });
+
+ this.checkedZeroWidth = false;
+ // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
+ if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px";
+ }
+
+ NativeScrollbars.prototype = copyObj({
+ update: function(measure) {
+ var needsH = measure.scrollWidth > measure.clientWidth + 1;
+ var needsV = measure.scrollHeight > measure.clientHeight + 1;
+ var sWidth = measure.nativeBarWidth;
+
+ if (needsV) {
+ this.vert.style.display = "block";
+ this.vert.style.bottom = needsH ? sWidth + "px" : "0";
+ var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);
+ // A bug in IE8 can cause this value to be negative, so guard it.
+ this.vert.firstChild.style.height =
+ Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px";
+ } else {
+ this.vert.style.display = "";
+ this.vert.firstChild.style.height = "0";
+ }
+
+ if (needsH) {
+ this.horiz.style.display = "block";
+ this.horiz.style.right = needsV ? sWidth + "px" : "0";
+ this.horiz.style.left = measure.barLeft + "px";
+ var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);
+ this.horiz.firstChild.style.width =
+ (measure.scrollWidth - measure.clientWidth + totalWidth) + "px";
+ } else {
+ this.horiz.style.display = "";
+ this.horiz.firstChild.style.width = "0";
+ }
+
+ if (!this.checkedZeroWidth && measure.clientHeight > 0) {
+ if (sWidth == 0) this.zeroWidthHack();
+ this.checkedZeroWidth = true;
+ }
+
+ return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0};
+ },
+ setScrollLeft: function(pos) {
+ if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos;
+ if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz);
+ },
+ setScrollTop: function(pos) {
+ if (this.vert.scrollTop != pos) this.vert.scrollTop = pos;
+ if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert);
+ },
+ zeroWidthHack: function() {
+ var w = mac && !mac_geMountainLion ? "12px" : "18px";
+ this.horiz.style.height = this.vert.style.width = w;
+ this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
+ this.disableHoriz = new Delayed;
+ this.disableVert = new Delayed;
+ },
+ enableZeroWidthBar: function(bar, delay) {
+ bar.style.pointerEvents = "auto";
+ function maybeDisable() {
+ // To find out whether the scrollbar is still visible, we
+ // check whether the element under the pixel in the bottom
+ // left corner of the scrollbar box is the scrollbar box
+ // itself (when the bar is still visible) or its filler child
+ // (when the bar is hidden). If it is still visible, we keep
+ // it enabled, if it's hidden, we disable pointer events.
+ var box = bar.getBoundingClientRect();
+ var elt = document.elementFromPoint(box.left + 1, box.bottom - 1);
+ if (elt != bar) bar.style.pointerEvents = "none";
+ else delay.set(1000, maybeDisable);
+ }
+ delay.set(1000, maybeDisable);
+ },
+ clear: function() {
+ var parent = this.horiz.parentNode;
+ parent.removeChild(this.horiz);
+ parent.removeChild(this.vert);
+ }
+ }, NativeScrollbars.prototype);
+
+ function NullScrollbars() {}
+
+ NullScrollbars.prototype = copyObj({
+ update: function() { return {bottom: 0, right: 0}; },
+ setScrollLeft: function() {},
+ setScrollTop: function() {},
+ clear: function() {}
+ }, NullScrollbars.prototype);
+
+ CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars};
+
+ function initScrollbars(cm) {
+ if (cm.display.scrollbars) {
+ cm.display.scrollbars.clear();
+ if (cm.display.scrollbars.addClass)
+ rmClass(cm.display.wrapper, cm.display.scrollbars.addClass);
+ }
+
+ cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) {
+ cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);
+ // Prevent clicks in the scrollbars from killing focus
+ on(node, "mousedown", function() {
+ if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0);
+ });
+ node.setAttribute("cm-not-content", "true");
+ }, function(pos, axis) {
+ if (axis == "horizontal") setScrollLeft(cm, pos);
+ else setScrollTop(cm, pos);
+ }, cm);
+ if (cm.display.scrollbars.addClass)
+ addClass(cm.display.wrapper, cm.display.scrollbars.addClass);
+ }
+
function updateScrollbars(cm, measure) {
if (!measure) measure = measureForScrollbars(cm);
- var d = cm.display;
- var scrollHeight = measure.docHeight + scrollerCutOff;
- var needsH = measure.scrollWidth > measure.clientWidth;
- var needsV = scrollHeight > measure.clientHeight;
- if (needsV) {
- d.scrollbarV.style.display = "block";
- d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
- // A bug in IE8 can cause this value to be negative, so guard it.
- d.scrollbarV.firstChild.style.height =
- Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px";
- } else {
- d.scrollbarV.style.display = "";
- d.scrollbarV.firstChild.style.height = "0";
- }
- if (needsH) {
- d.scrollbarH.style.display = "block";
- d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
- d.scrollbarH.firstChild.style.width =
- (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px";
- } else {
- d.scrollbarH.style.display = "";
- d.scrollbarH.firstChild.style.width = "0";
+ var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;
+ updateScrollbarsInner(cm, measure);
+ for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {
+ if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
+ updateHeightsInViewport(cm);
+ updateScrollbarsInner(cm, measureForScrollbars(cm));
+ startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;
}
- if (needsH && needsV) {
+ }
+
+ // Re-synchronize the fake scrollbars with the actual size of the
+ // content.
+ function updateScrollbarsInner(cm, measure) {
+ var d = cm.display;
+ var sizes = d.scrollbars.update(measure);
+
+ d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
+ d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
+ d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
+
+ if (sizes.right && sizes.bottom) {
d.scrollbarFiller.style.display = "block";
- d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
+ d.scrollbarFiller.style.height = sizes.bottom + "px";
+ d.scrollbarFiller.style.width = sizes.right + "px";
} else d.scrollbarFiller.style.display = "";
- if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
+ if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
d.gutterFiller.style.display = "block";
- d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px";
- d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
+ d.gutterFiller.style.height = sizes.bottom + "px";
+ d.gutterFiller.style.width = measure.gutterWidth + "px";
} else d.gutterFiller.style.display = "";
-
- if (mac_geLion && scrollbarWidth(d.measure) === 0) {
- d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
- var barMouseDown = function(e) {
- if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
- operation(cm, onMouseDown)(e);
- };
- on(d.scrollbarV, "mousedown", barMouseDown);
- on(d.scrollbarH, "mousedown", barMouseDown);
- }
}
// Compute the lines that are visible in a given viewport (defaults
- // the the current scroll position). viewPort may contain top,
+ // the the current scroll position). viewport may contain top,
// height, and ensure (see op.scrollToPos) properties.
- function visibleLines(display, doc, viewPort) {
- var top = viewPort && viewPort.top != null ? viewPort.top : display.scroller.scrollTop;
+ function visibleLines(display, doc, viewport) {
+ var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;
top = Math.floor(top - paddingTop(display));
- var bottom = viewPort && viewPort.bottom != null ? viewPort.bottom : top + display.wrapper.clientHeight;
+ var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;
var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
// Ensure is a {from: {line, ch}, to: {line, ch}} object, and
// forces those lines into the viewport (if possible).
- if (viewPort && viewPort.ensure) {
- var ensureFrom = viewPort.ensure.from.line, ensureTo = viewPort.ensure.to.line;
- if (ensureFrom < from)
- return {from: ensureFrom,
- to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)};
- if (Math.min(ensureTo, doc.lastLine()) >= to)
- return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight),
- to: ensureTo};
+ if (viewport && viewport.ensure) {
+ var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;
+ if (ensureFrom < from) {
+ from = ensureFrom;
+ to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight);
+ } else if (Math.min(ensureTo, doc.lastLine()) >= to) {
+ from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight);
+ to = ensureTo;
+ }
}
- return {from: from, to: to};
+ return {from: from, to: Math.max(to, from + 1)};
}
// LINE NUMBERS
@@ -478,8 +592,12 @@
var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
var gutterW = display.gutters.offsetWidth, left = comp + "px";
for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
- if (cm.options.fixedGutter && view[i].gutter)
- view[i].gutter.style.left = left;
+ if (cm.options.fixedGutter) {
+ if (view[i].gutter)
+ view[i].gutter.style.left = left;
+ if (view[i].gutterBackground)
+ view[i].gutterBackground.style.left = left;
+ }
var align = view[i].alignable;
if (align) for (var j = 0; j < align.length; j++)
align[j].style.left = left;
@@ -499,13 +617,11 @@
"CodeMirror-linenumber CodeMirror-gutter-elt"));
var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
display.lineGutter.style.width = "";
- display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
+ display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;
display.lineNumWidth = display.lineNumInnerWidth + padding;
display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
display.lineGutter.style.width = display.lineNumWidth + "px";
- var width = display.gutters.offsetWidth;
- display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
- display.sizer.style.marginLeft = width + "px";
+ updateGutterSpace(cm);
return true;
}
return false;
@@ -524,76 +640,68 @@
// DISPLAY DRAWING
- // Updates the display, selection, and scrollbars, using the
- // information in display.view to find out which nodes are no longer
- // up-to-date. Tries to bail out early when no changes are needed,
- // unless forced is true.
- // Returns true if an actual update happened, false otherwise.
- function updateDisplay(cm, viewPort, forced) {
- var oldFrom = cm.display.viewFrom, oldTo = cm.display.viewTo, updated;
- var visible = visibleLines(cm.display, cm.doc, viewPort);
- for (var first = true;; first = false) {
- var oldWidth = cm.display.scroller.clientWidth;
- if (!updateDisplayInner(cm, visible, forced)) break;
- updated = true;
-
- // If the max line changed since it was last measured, measure it,
- // and ensure the document's width matches it.
- if (cm.display.maxLineChanged && !cm.options.lineWrapping)
- adjustContentWidth(cm);
-
- var barMeasure = measureForScrollbars(cm);
- updateSelection(cm);
- setDocumentHeight(cm, barMeasure);
- updateScrollbars(cm, barMeasure);
- if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) {
- forced = true;
- continue;
- }
- forced = false;
+ function DisplayUpdate(cm, viewport, force) {
+ var display = cm.display;
- // Clip forced viewport to actual scrollable area.
- if (viewPort && viewPort.top != null)
- viewPort = {top: Math.min(barMeasure.docHeight - scrollerCutOff - barMeasure.clientHeight, viewPort.top)};
- // Updated line heights might result in the drawn area not
- // actually covering the viewport. Keep looping until it does.
- visible = visibleLines(cm.display, cm.doc, viewPort);
- if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo)
- break;
- }
+ this.viewport = viewport;
+ // Store some values that we'll need later (but don't want to force a relayout for)
+ this.visible = visibleLines(display, cm.doc, viewport);
+ this.editorIsHidden = !display.wrapper.offsetWidth;
+ this.wrapperHeight = display.wrapper.clientHeight;
+ this.wrapperWidth = display.wrapper.clientWidth;
+ this.oldDisplayWidth = displayWidth(cm);
+ this.force = force;
+ this.dims = getDimensions(cm);
+ this.events = [];
+ }
+
+ DisplayUpdate.prototype.signal = function(emitter, type) {
+ if (hasHandler(emitter, type))
+ this.events.push(arguments);
+ };
+ DisplayUpdate.prototype.finish = function() {
+ for (var i = 0; i < this.events.length; i++)
+ signal.apply(null, this.events[i]);
+ };
- cm.display.updateLineNumbers = null;
- if (updated) {
- signalLater(cm, "update", cm);
- if (cm.display.viewFrom != oldFrom || cm.display.viewTo != oldTo)
- signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
+ function maybeClipScrollbars(cm) {
+ var display = cm.display;
+ if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
+ display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth;
+ display.heightForcer.style.height = scrollGap(cm) + "px";
+ display.sizer.style.marginBottom = -display.nativeBarWidth + "px";
+ display.sizer.style.borderRightWidth = scrollGap(cm) + "px";
+ display.scrollbarsClipped = true;
}
- return updated;
}
// Does the actual updating of the line display. Bails out
// (returning false) when there is nothing to be done and forced is
// false.
- function updateDisplayInner(cm, visible, forced) {
+ function updateDisplayIfNeeded(cm, update) {
var display = cm.display, doc = cm.doc;
- if (!display.wrapper.offsetWidth) {
+
+ if (update.editorIsHidden) {
resetView(cm);
- return;
+ return false;
}
// Bail out if the visible area is already rendered and nothing changed.
- if (!forced && visible.from >= display.viewFrom && visible.to <= display.viewTo &&
- countDirtyView(cm) == 0)
- return;
+ if (!update.force &&
+ update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
+ (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
+ display.renderedView == display.view && countDirtyView(cm) == 0)
+ return false;
- if (maybeUpdateLineNumberWidth(cm))
+ if (maybeUpdateLineNumberWidth(cm)) {
resetView(cm);
- var dims = getDimensions(cm);
+ update.dims = getDimensions(cm);
+ }
// Compute a suitable new viewport (from & to)
var end = doc.first + doc.size;
- var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
- var to = Math.min(end, visible.to + cm.options.viewportMargin);
+ var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
+ var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
if (sawCollapsedSpans) {
@@ -602,7 +710,7 @@
}
var different = from != display.viewFrom || to != display.viewTo ||
- display.lastSizeC != display.wrapper.clientHeight;
+ display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
adjustView(cm, from, to);
display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
@@ -610,47 +718,84 @@
cm.display.mover.style.top = display.viewOffset + "px";
var toUpdate = countDirtyView(cm);
- if (!different && toUpdate == 0 && !forced) return;
+ if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
+ (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
+ return false;
// For big changes, we hide the enclosing element during the
// update, since that speeds up the operations on most browsers.
var focused = activeElt();
if (toUpdate > 4) display.lineDiv.style.display = "none";
- patchDisplay(cm, display.updateLineNumbers, dims);
+ patchDisplay(cm, display.updateLineNumbers, update.dims);
if (toUpdate > 4) display.lineDiv.style.display = "";
+ display.renderedView = display.view;
// There might have been a widget with a focused element that got
// hidden or updated, if so re-focus it.
if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();
// Prevent selection and cursors from interfering with the scroll
- // width.
+ // width and height.
removeChildren(display.cursorDiv);
removeChildren(display.selectionDiv);
+ display.gutters.style.height = display.sizer.style.minHeight = 0;
if (different) {
- display.lastSizeC = display.wrapper.clientHeight;
+ display.lastWrapHeight = update.wrapperHeight;
+ display.lastWrapWidth = update.wrapperWidth;
startWorker(cm, 400);
}
- updateHeightsInViewport(cm);
+ display.updateLineNumbers = null;
return true;
}
- function adjustContentWidth(cm) {
- var display = cm.display;
- var width = measureChar(cm, display.maxLine, display.maxLine.text.length).left;
- display.maxLineChanged = false;
- var minWidth = Math.max(0, width + 3);
- var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + minWidth + scrollerCutOff - display.scroller.clientWidth);
- display.sizer.style.minWidth = minWidth + "px";
- if (maxScrollLeft < cm.doc.scrollLeft)
- setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
+ function postUpdateDisplay(cm, update) {
+ var viewport = update.viewport;
+
+ for (var first = true;; first = false) {
+ if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
+ // Clip forced viewport to actual scrollable area.
+ if (viewport && viewport.top != null)
+ viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)};
+ // Updated line heights might result in the drawn area not
+ // actually covering the viewport. Keep looping until it does.
+ update.visible = visibleLines(cm.display, cm.doc, viewport);
+ if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
+ break;
+ }
+ if (!updateDisplayIfNeeded(cm, update)) break;
+ updateHeightsInViewport(cm);
+ var barMeasure = measureForScrollbars(cm);
+ updateSelection(cm);
+ updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
+ }
+
+ update.signal(cm, "update", cm);
+ if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
+ update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
+ cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;
+ }
+ }
+
+ function updateDisplaySimple(cm, viewport) {
+ var update = new DisplayUpdate(cm, viewport);
+ if (updateDisplayIfNeeded(cm, update)) {
+ updateHeightsInViewport(cm);
+ postUpdateDisplay(cm, update);
+ var barMeasure = measureForScrollbars(cm);
+ updateSelection(cm);
+ updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
+ update.finish();
+ }
}
function setDocumentHeight(cm, measure) {
- cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px";
- cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px";
+ cm.display.sizer.style.minHeight = measure.docHeight + "px";
+ cm.display.heightForcer.style.top = measure.docHeight + "px";
+ cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px";
}
// Read the actual heights of the rendered lines, and update their
@@ -661,7 +806,7 @@
for (var i = 0; i < display.view.length; i++) {
var cur = display.view[i], height;
if (cur.hidden) continue;
- if (ie_upto7) {
+ if (ie && ie_version < 8) {
var bot = cur.node.offsetTop + cur.node.offsetHeight;
height = bot - prevBottom;
prevBottom = bot;
@@ -684,16 +829,17 @@
// given line.
function updateWidgetHeight(line) {
if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
- line.widgets[i].height = line.widgets[i].node.offsetHeight;
+ line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight;
}
// Do a bulk-read of the DOM positions and sizes needed to draw the
// view, so that we don't interleave reading and writing to the DOM.
function getDimensions(cm) {
var d = cm.display, left = {}, width = {};
+ var gutterLeft = d.gutters.clientLeft;
for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
- left[cm.options.gutters[i]] = n.offsetLeft;
- width[cm.options.gutters[i]] = n.offsetWidth;
+ left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft;
+ width[cm.options.gutters[i]] = n.clientWidth;
}
return {fixedPos: compensateForHScroll(d),
gutterTotalWidth: d.gutters.offsetWidth,
@@ -726,7 +872,7 @@
for (var i = 0; i < view.length; i++) {
var lineView = view[i];
if (lineView.hidden) {
- } else if (!lineView.node) { // Not drawn yet
+ } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
var node = buildLineElement(cm, lineView, lineN, dims);
container.insertBefore(node, cur);
} else { // Already drawn
@@ -757,7 +903,7 @@
if (type == "text") updateLineText(cm, lineView);
else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
else if (type == "class") updateLineClasses(lineView);
- else if (type == "widget") updateLineWidgets(lineView, dims);
+ else if (type == "widget") updateLineWidgets(cm, lineView, dims);
}
lineView.changes = null;
}
@@ -770,7 +916,7 @@
if (lineView.text.parentNode)
lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
lineView.node.appendChild(lineView.text);
- if (ie_upto7) lineView.node.style.zIndex = 2;
+ if (ie && ie_version < 8) lineView.node.style.zIndex = 2;
}
return lineView.node;
}
@@ -832,13 +978,26 @@
lineView.node.removeChild(lineView.gutter);
lineView.gutter = null;
}
+ if (lineView.gutterBackground) {
+ lineView.node.removeChild(lineView.gutterBackground);
+ lineView.gutterBackground = null;
+ }
+ if (lineView.line.gutterClass) {
+ var wrap = ensureLineWrapped(lineView);
+ lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
+ "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
+ "px; width: " + dims.gutterTotalWidth + "px");
+ wrap.insertBefore(lineView.gutterBackground, lineView.text);
+ }
var markers = lineView.line.gutterMarkers;
if (cm.options.lineNumbers || markers) {
var wrap = ensureLineWrapped(lineView);
- var gutterWrap = lineView.gutter =
- wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
- (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
- lineView.text);
+ var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px");
+ cm.display.input.setUneditable(gutterWrap);
+ wrap.insertBefore(gutterWrap, lineView.text);
+ if (lineView.line.gutterClass)
+ gutterWrap.className += " " + lineView.line.gutterClass;
if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
lineView.lineNumber = gutterWrap.appendChild(
elt("div", lineNumberFor(cm.options, lineN),
@@ -854,14 +1013,14 @@
}
}
- function updateLineWidgets(lineView, dims) {
+ function updateLineWidgets(cm, lineView, dims) {
if (lineView.alignable) lineView.alignable = null;
for (var node = lineView.node.firstChild, next; node; node = next) {
var next = node.nextSibling;
if (node.className == "CodeMirror-linewidget")
lineView.node.removeChild(node);
}
- insertLineWidgets(lineView, dims);
+ insertLineWidgets(cm, lineView, dims);
}
// Build a line's DOM representation from scratch
@@ -873,25 +1032,26 @@
updateLineClasses(lineView);
updateLineGutter(cm, lineView, lineN, dims);
- insertLineWidgets(lineView, dims);
+ insertLineWidgets(cm, lineView, dims);
return lineView.node;
}
// A lineView may contain multiple logical lines (when merged by
// collapsed spans). The widgets for all of them need to be drawn.
- function insertLineWidgets(lineView, dims) {
- insertLineWidgetsFor(lineView.line, lineView, dims, true);
+ function insertLineWidgets(cm, lineView, dims) {
+ insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);
if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
- insertLineWidgetsFor(lineView.rest[i], lineView, dims, false);
+ insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false);
}
- function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
+ function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
if (!line.widgets) return;
var wrap = ensureLineWrapped(lineView);
for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
- if (!widget.handleMouseEvents) node.ignoreEvents = true;
+ if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true");
positionLineWidget(widget, node, lineView, dims);
+ cm.display.input.setUneditable(node);
if (allowAbove && widget.above)
wrap.insertBefore(node, lineView.gutter || lineView.text);
else
@@ -909,30 +1069,956 @@
width -= dims.gutterTotalWidth;
node.style.paddingLeft = dims.gutterTotalWidth + "px";
}
- node.style.width = width + "px";
+ node.style.width = width + "px";
+ }
+ if (widget.coverGutter) {
+ node.style.zIndex = 5;
+ node.style.position = "relative";
+ if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
+ }
+ }
+
+ // POSITION OBJECT
+
+ // A Pos instance represents a position within the text.
+ var Pos = CodeMirror.Pos = function(line, ch) {
+ if (!(this instanceof Pos)) return new Pos(line, ch);
+ this.line = line; this.ch = ch;
+ };
+
+ // Compare two positions, return 0 if they are the same, a negative
+ // number when a is less, and a positive number otherwise.
+ var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
+
+ function copyPos(x) {return Pos(x.line, x.ch);}
+ function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
+ function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
+
+ // INPUT HANDLING
+
+ function ensureFocus(cm) {
+ if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
+ }
+
+ // This will be set to a {lineWise: bool, text: [string]} object, so
+ // that, when pasting, we know what kind of selections the copied
+ // text was made out of.
+ var lastCopied = null;
+
+ function applyTextInput(cm, inserted, deleted, sel, origin) {
+ var doc = cm.doc;
+ cm.display.shift = false;
+ if (!sel) sel = doc.sel;
+
+ var paste = cm.state.pasteIncoming || origin == "paste";
+ var textLines = doc.splitLines(inserted), multiPaste = null
+ // When pasing N lines into N selections, insert one line per selection
+ if (paste && sel.ranges.length > 1) {
+ if (lastCopied && lastCopied.text.join("\n") == inserted) {
+ if (sel.ranges.length % lastCopied.text.length == 0) {
+ multiPaste = [];
+ for (var i = 0; i < lastCopied.text.length; i++)
+ multiPaste.push(doc.splitLines(lastCopied.text[i]));
+ }
+ } else if (textLines.length == sel.ranges.length) {
+ multiPaste = map(textLines, function(l) { return [l]; });
+ }
+ }
+
+ // Normal behavior is to insert the new text into every selection
+ for (var i = sel.ranges.length - 1; i >= 0; i--) {
+ var range = sel.ranges[i];
+ var from = range.from(), to = range.to();
+ if (range.empty()) {
+ if (deleted && deleted > 0) // Handle deletion
+ from = Pos(from.line, from.ch - deleted);
+ else if (cm.state.overwrite && !paste) // Handle overwrite
+ to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
+ else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted)
+ from = to = Pos(from.line, 0)
+ }
+ var updateInput = cm.curOp.updateInput;
+ var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
+ origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")};
+ makeChange(cm.doc, changeEvent);
+ signalLater(cm, "inputRead", cm, changeEvent);
+ }
+ if (inserted && !paste)
+ triggerElectric(cm, inserted);
+
+ ensureCursorVisible(cm);
+ cm.curOp.updateInput = updateInput;
+ cm.curOp.typing = true;
+ cm.state.pasteIncoming = cm.state.cutIncoming = false;
+ }
+
+ function handlePaste(e, cm) {
+ var pasted = e.clipboardData && e.clipboardData.getData("Text");
+ if (pasted) {
+ e.preventDefault();
+ if (!cm.isReadOnly() && !cm.options.disableInput)
+ runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); });
+ return true;
+ }
+ }
+
+ function triggerElectric(cm, inserted) {
+ // When an 'electric' character is inserted, immediately trigger a reindent
+ if (!cm.options.electricChars || !cm.options.smartIndent) return;
+ var sel = cm.doc.sel;
+
+ for (var i = sel.ranges.length - 1; i >= 0; i--) {
+ var range = sel.ranges[i];
+ if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue;
+ var mode = cm.getModeAt(range.head);
+ var indented = false;
+ if (mode.electricChars) {
+ for (var j = 0; j < mode.electricChars.length; j++)
+ if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
+ indented = indentLine(cm, range.head.line, "smart");
+ break;
+ }
+ } else if (mode.electricInput) {
+ if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch)))
+ indented = indentLine(cm, range.head.line, "smart");
+ }
+ if (indented) signalLater(cm, "electricInput", cm, range.head.line);
+ }
+ }
+
+ function copyableRanges(cm) {
+ var text = [], ranges = [];
+ for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
+ var line = cm.doc.sel.ranges[i].head.line;
+ var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
+ ranges.push(lineRange);
+ text.push(cm.getRange(lineRange.anchor, lineRange.head));
+ }
+ return {text: text, ranges: ranges};
+ }
+
+ function disableBrowserMagic(field, spellcheck) {
+ field.setAttribute("autocorrect", "off");
+ field.setAttribute("autocapitalize", "off");
+ field.setAttribute("spellcheck", !!spellcheck);
+ }
+
+ // TEXTAREA INPUT STYLE
+
+ function TextareaInput(cm) {
+ this.cm = cm;
+ // See input.poll and input.reset
+ this.prevInput = "";
+
+ // Flag that indicates whether we expect input to appear real soon
+ // now (after some event like 'keypress' or 'input') and are
+ // polling intensively.
+ this.pollingFast = false;
+ // Self-resetting timeout for the poller
+ this.polling = new Delayed();
+ // Tracks when input.reset has punted to just putting a short
+ // string into the textarea instead of the full selection.
+ this.inaccurateSelection = false;
+ // Used to work around IE issue with selection being forgotten when focus moves away from textarea
+ this.hasSelection = false;
+ this.composing = null;
+ };
+
+ function hiddenTextarea() {
+ var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none");
+ var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
+ // The textarea is kept positioned near the cursor to prevent the
+ // fact that it'll be scrolled into view on input from scrolling
+ // our fake cursor out of view. On webkit, when wrap=off, paste is
+ // very slow. So make the area wide instead.
+ if (webkit) te.style.width = "1000px";
+ else te.setAttribute("wrap", "off");
+ // If border: 0; -- iOS fails to open keyboard (issue #1287)
+ if (ios) te.style.border = "1px solid black";
+ disableBrowserMagic(te);
+ return div;
+ }
+
+ TextareaInput.prototype = copyObj({
+ init: function(display) {
+ var input = this, cm = this.cm;
+
+ // Wraps and hides input textarea
+ var div = this.wrapper = hiddenTextarea();
+ // The semihidden textarea that is focused when the editor is
+ // focused, and receives input.
+ var te = this.textarea = div.firstChild;
+ display.wrapper.insertBefore(div, display.wrapper.firstChild);
+
+ // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
+ if (ios) te.style.width = "0px";
+
+ on(te, "input", function() {
+ if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null;
+ input.poll();
+ });
+
+ on(te, "paste", function(e) {
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return
+
+ cm.state.pasteIncoming = true;
+ input.fastPoll();
+ });
+
+ function prepareCopyCut(e) {
+ if (signalDOMEvent(cm, e)) return
+ if (cm.somethingSelected()) {
+ lastCopied = {lineWise: false, text: cm.getSelections()};
+ if (input.inaccurateSelection) {
+ input.prevInput = "";
+ input.inaccurateSelection = false;
+ te.value = lastCopied.text.join("\n");
+ selectInput(te);
+ }
+ } else if (!cm.options.lineWiseCopyCut) {
+ return;
+ } else {
+ var ranges = copyableRanges(cm);
+ lastCopied = {lineWise: true, text: ranges.text};
+ if (e.type == "cut") {
+ cm.setSelections(ranges.ranges, null, sel_dontScroll);
+ } else {
+ input.prevInput = "";
+ te.value = ranges.text.join("\n");
+ selectInput(te);
+ }
+ }
+ if (e.type == "cut") cm.state.cutIncoming = true;
+ }
+ on(te, "cut", prepareCopyCut);
+ on(te, "copy", prepareCopyCut);
+
+ on(display.scroller, "paste", function(e) {
+ if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return;
+ cm.state.pasteIncoming = true;
+ input.focus();
+ });
+
+ // Prevent normal selection in the editor (we handle our own)
+ on(display.lineSpace, "selectstart", function(e) {
+ if (!eventInWidget(display, e)) e_preventDefault(e);
+ });
+
+ on(te, "compositionstart", function() {
+ var start = cm.getCursor("from");
+ if (input.composing) input.composing.range.clear()
+ input.composing = {
+ start: start,
+ range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
+ };
+ });
+ on(te, "compositionend", function() {
+ if (input.composing) {
+ input.poll();
+ input.composing.range.clear();
+ input.composing = null;
+ }
+ });
+ },
+
+ prepareSelection: function() {
+ // Redraw the selection and/or cursor
+ var cm = this.cm, display = cm.display, doc = cm.doc;
+ var result = prepareSelection(cm);
+
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
+ if (cm.options.moveInputWithCursor) {
+ var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
+ var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
+ result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
+ headPos.top + lineOff.top - wrapOff.top));
+ result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
+ headPos.left + lineOff.left - wrapOff.left));
+ }
+
+ return result;
+ },
+
+ showSelection: function(drawn) {
+ var cm = this.cm, display = cm.display;
+ removeChildrenAndAdd(display.cursorDiv, drawn.cursors);
+ removeChildrenAndAdd(display.selectionDiv, drawn.selection);
+ if (drawn.teTop != null) {
+ this.wrapper.style.top = drawn.teTop + "px";
+ this.wrapper.style.left = drawn.teLeft + "px";
+ }
+ },
+
+ // Reset the input to correspond to the selection (or to be empty,
+ // when not typing and nothing is selected)
+ reset: function(typing) {
+ if (this.contextMenuPending) return;
+ var minimal, selected, cm = this.cm, doc = cm.doc;
+ if (cm.somethingSelected()) {
+ this.prevInput = "";
+ var range = doc.sel.primary();
+ minimal = hasCopyEvent &&
+ (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
+ var content = minimal ? "-" : selected || cm.getSelection();
+ this.textarea.value = content;
+ if (cm.state.focused) selectInput(this.textarea);
+ if (ie && ie_version >= 9) this.hasSelection = content;
+ } else if (!typing) {
+ this.prevInput = this.textarea.value = "";
+ if (ie && ie_version >= 9) this.hasSelection = null;
+ }
+ this.inaccurateSelection = minimal;
+ },
+
+ getField: function() { return this.textarea; },
+
+ supportsTouch: function() { return false; },
+
+ focus: function() {
+ if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
+ try { this.textarea.focus(); }
+ catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
+ }
+ },
+
+ blur: function() { this.textarea.blur(); },
+
+ resetPosition: function() {
+ this.wrapper.style.top = this.wrapper.style.left = 0;
+ },
+
+ receivedFocus: function() { this.slowPoll(); },
+
+ // Poll for input changes, using the normal rate of polling. This
+ // runs as long as the editor is focused.
+ slowPoll: function() {
+ var input = this;
+ if (input.pollingFast) return;
+ input.polling.set(this.cm.options.pollInterval, function() {
+ input.poll();
+ if (input.cm.state.focused) input.slowPoll();
+ });
+ },
+
+ // When an event has just come in that is likely to add or change
+ // something in the input textarea, we poll faster, to ensure that
+ // the change appears on the screen quickly.
+ fastPoll: function() {
+ var missed = false, input = this;
+ input.pollingFast = true;
+ function p() {
+ var changed = input.poll();
+ if (!changed && !missed) {missed = true; input.polling.set(60, p);}
+ else {input.pollingFast = false; input.slowPoll();}
+ }
+ input.polling.set(20, p);
+ },
+
+ // Read input from the textarea, and update the document to match.
+ // When something is selected, it is present in the textarea, and
+ // selected (unless it is huge, in which case a placeholder is
+ // used). When nothing is selected, the cursor sits after previously
+ // seen text (can be empty), which is stored in prevInput (we must
+ // not reset the textarea when typing, because that breaks IME).
+ poll: function() {
+ var cm = this.cm, input = this.textarea, prevInput = this.prevInput;
+ // Since this is called a *lot*, try to bail out as cheaply as
+ // possible when it is clear that nothing happened. hasSelection
+ // will be the case when there is a lot of text in the textarea,
+ // in which case reading its value would be expensive.
+ if (this.contextMenuPending || !cm.state.focused ||
+ (hasSelection(input) && !prevInput && !this.composing) ||
+ cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
+ return false;
+
+ var text = input.value;
+ // If nothing changed, bail.
+ if (text == prevInput && !cm.somethingSelected()) return false;
+ // Work around nonsensical selection resetting in IE9/10, and
+ // inexplicable appearance of private area unicode characters on
+ // some key combos in Mac (#2689).
+ if (ie && ie_version >= 9 && this.hasSelection === text ||
+ mac && /[\uf700-\uf7ff]/.test(text)) {
+ cm.display.input.reset();
+ return false;
+ }
+
+ if (cm.doc.sel == cm.display.selForContextMenu) {
+ var first = text.charCodeAt(0);
+ if (first == 0x200b && !prevInput) prevInput = "\u200b";
+ if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); }
+ }
+ // Find the part of the input that is actually new
+ var same = 0, l = Math.min(prevInput.length, text.length);
+ while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
+
+ var self = this;
+ runInOp(cm, function() {
+ applyTextInput(cm, text.slice(same), prevInput.length - same,
+ null, self.composing ? "*compose" : null);
+
+ // Don't leave long text in the textarea, since it makes further polling slow
+ if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = "";
+ else self.prevInput = text;
+
+ if (self.composing) {
+ self.composing.range.clear();
+ self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"),
+ {className: "CodeMirror-composing"});
+ }
+ });
+ return true;
+ },
+
+ ensurePolled: function() {
+ if (this.pollingFast && this.poll()) this.pollingFast = false;
+ },
+
+ onKeyPress: function() {
+ if (ie && ie_version >= 9) this.hasSelection = null;
+ this.fastPoll();
+ },
+
+ onContextMenu: function(e) {
+ var input = this, cm = input.cm, display = cm.display, te = input.textarea;
+ var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
+ if (!pos || presto) return; // Opera is difficult.
+
+ // Reset the current text selection only if the click is done outside of the selection
+ // and 'resetSelectionOnContextMenu' option is true.
+ var reset = cm.options.resetSelectionOnContextMenu;
+ if (reset && cm.doc.sel.contains(pos) == -1)
+ operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
+
+ var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;
+ input.wrapper.style.cssText = "position: absolute"
+ var wrapperBox = input.wrapper.getBoundingClientRect()
+ te.style.cssText = "position: absolute; width: 30px; height: 30px; top: " + (e.clientY - wrapperBox.top - 5) +
+ "px; left: " + (e.clientX - wrapperBox.left - 5) + "px; z-index: 1000; background: " +
+ (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
+ "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
+ if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
+ display.input.focus();
+ if (webkit) window.scrollTo(null, oldScrollY);
+ display.input.reset();
+ // Adds "Select all" to context menu in FF
+ if (!cm.somethingSelected()) te.value = input.prevInput = " ";
+ input.contextMenuPending = true;
+ display.selForContextMenu = cm.doc.sel;
+ clearTimeout(display.detectingSelectAll);
+
+ // Select-all will be greyed out if there's nothing to select, so
+ // this adds a zero-width space so that we can later check whether
+ // it got selected.
+ function prepareSelectAllHack() {
+ if (te.selectionStart != null) {
+ var selected = cm.somethingSelected();
+ var extval = "\u200b" + (selected ? te.value : "");
+ te.value = "\u21da"; // Used to catch context-menu undo
+ te.value = extval;
+ input.prevInput = selected ? "" : "\u200b";
+ te.selectionStart = 1; te.selectionEnd = extval.length;
+ // Re-set this, in case some other handler touched the
+ // selection in the meantime.
+ display.selForContextMenu = cm.doc.sel;
+ }
+ }
+ function rehide() {
+ input.contextMenuPending = false;
+ input.wrapper.style.cssText = oldWrapperCSS
+ te.style.cssText = oldCSS;
+ if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos);
+
+ // Try to detect the user choosing select-all
+ if (te.selectionStart != null) {
+ if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
+ var i = 0, poll = function() {
+ if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
+ te.selectionEnd > 0 && input.prevInput == "\u200b")
+ operation(cm, commands.selectAll)(cm);
+ else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
+ else display.input.reset();
+ };
+ display.detectingSelectAll = setTimeout(poll, 200);
+ }
+ }
+
+ if (ie && ie_version >= 9) prepareSelectAllHack();
+ if (captureRightClick) {
+ e_stop(e);
+ var mouseup = function() {
+ off(window, "mouseup", mouseup);
+ setTimeout(rehide, 20);
+ };
+ on(window, "mouseup", mouseup);
+ } else {
+ setTimeout(rehide, 50);
+ }
+ },
+
+ readOnlyChanged: function(val) {
+ if (!val) this.reset();
+ },
+
+ setUneditable: nothing,
+
+ needsContentAttribute: false
+ }, TextareaInput.prototype);
+
+ // CONTENTEDITABLE INPUT STYLE
+
+ function ContentEditableInput(cm) {
+ this.cm = cm;
+ this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
+ this.polling = new Delayed();
+ this.gracePeriod = false;
+ }
+
+ ContentEditableInput.prototype = copyObj({
+ init: function(display) {
+ var input = this, cm = input.cm;
+ var div = input.div = display.lineDiv;
+ disableBrowserMagic(div, cm.options.spellcheck);
+
+ on(div, "paste", function(e) {
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return
+ // IE doesn't fire input events, so we schedule a read for the pasted content in this way
+ if (ie_version <= 11) setTimeout(operation(cm, function() {
+ if (!input.pollContent()) regChange(cm);
+ }), 20)
+ })
+
+ on(div, "compositionstart", function(e) {
+ var data = e.data;
+ input.composing = {sel: cm.doc.sel, data: data, startData: data};
+ if (!data) return;
+ var prim = cm.doc.sel.primary();
+ var line = cm.getLine(prim.head.line);
+ var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length));
+ if (found > -1 && found <= prim.head.ch)
+ input.composing.sel = simpleSelection(Pos(prim.head.line, found),
+ Pos(prim.head.line, found + data.length));
+ });
+ on(div, "compositionupdate", function(e) {
+ input.composing.data = e.data;
+ });
+ on(div, "compositionend", function(e) {
+ var ours = input.composing;
+ if (!ours) return;
+ if (e.data != ours.startData && !/\u200b/.test(e.data))
+ ours.data = e.data;
+ // Need a small delay to prevent other code (input event,
+ // selection polling) from doing damage when fired right after
+ // compositionend.
+ setTimeout(function() {
+ if (!ours.handled)
+ input.applyComposition(ours);
+ if (input.composing == ours)
+ input.composing = null;
+ }, 50);
+ });
+
+ on(div, "touchstart", function() {
+ input.forceCompositionEnd();
+ });
+
+ on(div, "input", function() {
+ if (input.composing) return;
+ if (cm.isReadOnly() || !input.pollContent())
+ runInOp(input.cm, function() {regChange(cm);});
+ });
+
+ function onCopyCut(e) {
+ if (signalDOMEvent(cm, e)) return
+ if (cm.somethingSelected()) {
+ lastCopied = {lineWise: false, text: cm.getSelections()};
+ if (e.type == "cut") cm.replaceSelection("", null, "cut");
+ } else if (!cm.options.lineWiseCopyCut) {
+ return;
+ } else {
+ var ranges = copyableRanges(cm);
+ lastCopied = {lineWise: true, text: ranges.text};
+ if (e.type == "cut") {
+ cm.operation(function() {
+ cm.setSelections(ranges.ranges, 0, sel_dontScroll);
+ cm.replaceSelection("", null, "cut");
+ });
+ }
+ }
+ if (e.clipboardData) {
+ e.clipboardData.clearData();
+ var content = lastCopied.text.join("\n")
+ // iOS exposes the clipboard API, but seems to discard content inserted into it
+ e.clipboardData.setData("Text", content);
+ if (e.clipboardData.getData("Text") == content) {
+ e.preventDefault();
+ return
+ }
+ }
+ // Old-fashioned briefly-focus-a-textarea hack
+ var kludge = hiddenTextarea(), te = kludge.firstChild;
+ cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
+ te.value = lastCopied.text.join("\n");
+ var hadFocus = document.activeElement;
+ selectInput(te);
+ setTimeout(function() {
+ cm.display.lineSpace.removeChild(kludge);
+ hadFocus.focus();
+ if (hadFocus == div) input.showPrimarySelection()
+ }, 50);
+ }
+ on(div, "copy", onCopyCut);
+ on(div, "cut", onCopyCut);
+ },
+
+ prepareSelection: function() {
+ var result = prepareSelection(this.cm, false);
+ result.focus = this.cm.state.focused;
+ return result;
+ },
+
+ showSelection: function(info, takeFocus) {
+ if (!info || !this.cm.display.view.length) return;
+ if (info.focus || takeFocus) this.showPrimarySelection();
+ this.showMultipleSelections(info);
+ },
+
+ showPrimarySelection: function() {
+ var sel = window.getSelection(), prim = this.cm.doc.sel.primary();
+ var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset);
+ var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset);
+ if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
+ cmp(minPos(curAnchor, curFocus), prim.from()) == 0 &&
+ cmp(maxPos(curAnchor, curFocus), prim.to()) == 0)
+ return;
+
+ var start = posToDOM(this.cm, prim.from());
+ var end = posToDOM(this.cm, prim.to());
+ if (!start && !end) return;
+
+ var view = this.cm.display.view;
+ var old = sel.rangeCount && sel.getRangeAt(0);
+ if (!start) {
+ start = {node: view[0].measure.map[2], offset: 0};
+ } else if (!end) { // FIXME dangerously hacky
+ var measure = view[view.length - 1].measure;
+ var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;
+ end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]};
+ }
+
+ try { var rng = range(start.node, start.offset, end.offset, end.node); }
+ catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
+ if (rng) {
+ if (!gecko && this.cm.state.focused) {
+ sel.collapse(start.node, start.offset);
+ if (!rng.collapsed) sel.addRange(rng);
+ } else {
+ sel.removeAllRanges();
+ sel.addRange(rng);
+ }
+ if (old && sel.anchorNode == null) sel.addRange(old);
+ else if (gecko) this.startGracePeriod();
+ }
+ this.rememberSelection();
+ },
+
+ startGracePeriod: function() {
+ var input = this;
+ clearTimeout(this.gracePeriod);
+ this.gracePeriod = setTimeout(function() {
+ input.gracePeriod = false;
+ if (input.selectionChanged())
+ input.cm.operation(function() { input.cm.curOp.selectionChanged = true; });
+ }, 20);
+ },
+
+ showMultipleSelections: function(info) {
+ removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
+ removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
+ },
+
+ rememberSelection: function() {
+ var sel = window.getSelection();
+ this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;
+ this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;
+ },
+
+ selectionInEditor: function() {
+ var sel = window.getSelection();
+ if (!sel.rangeCount) return false;
+ var node = sel.getRangeAt(0).commonAncestorContainer;
+ return contains(this.div, node);
+ },
+
+ focus: function() {
+ if (this.cm.options.readOnly != "nocursor") this.div.focus();
+ },
+ blur: function() { this.div.blur(); },
+ getField: function() { return this.div; },
+
+ supportsTouch: function() { return true; },
+
+ receivedFocus: function() {
+ var input = this;
+ if (this.selectionInEditor())
+ this.pollSelection();
+ else
+ runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; });
+
+ function poll() {
+ if (input.cm.state.focused) {
+ input.pollSelection();
+ input.polling.set(input.cm.options.pollInterval, poll);
+ }
+ }
+ this.polling.set(this.cm.options.pollInterval, poll);
+ },
+
+ selectionChanged: function() {
+ var sel = window.getSelection();
+ return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
+ sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset;
+ },
+
+ pollSelection: function() {
+ if (!this.composing && !this.gracePeriod && this.selectionChanged()) {
+ var sel = window.getSelection(), cm = this.cm;
+ this.rememberSelection();
+ var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);
+ var head = domToPos(cm, sel.focusNode, sel.focusOffset);
+ if (anchor && head) runInOp(cm, function() {
+ setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);
+ if (anchor.bad || head.bad) cm.curOp.selectionChanged = true;
+ });
+ }
+ },
+
+ pollContent: function() {
+ var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();
+ var from = sel.from(), to = sel.to();
+ if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false;
+
+ var fromIndex;
+ if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
+ var fromLine = lineNo(display.view[0].line);
+ var fromNode = display.view[0].node;
+ } else {
+ var fromLine = lineNo(display.view[fromIndex].line);
+ var fromNode = display.view[fromIndex - 1].node.nextSibling;
+ }
+ var toIndex = findViewIndex(cm, to.line);
+ if (toIndex == display.view.length - 1) {
+ var toLine = display.viewTo - 1;
+ var toNode = display.lineDiv.lastChild;
+ } else {
+ var toLine = lineNo(display.view[toIndex + 1].line) - 1;
+ var toNode = display.view[toIndex + 1].node.previousSibling;
+ }
+
+ var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));
+ var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));
+ while (newText.length > 1 && oldText.length > 1) {
+ if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }
+ else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }
+ else break;
+ }
+
+ var cutFront = 0, cutEnd = 0;
+ var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);
+ while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
+ ++cutFront;
+ var newBot = lst(newText), oldBot = lst(oldText);
+ var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
+ oldBot.length - (oldText.length == 1 ? cutFront : 0));
+ while (cutEnd < maxCutEnd &&
+ newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
+ ++cutEnd;
+
+ newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd);
+ newText[0] = newText[0].slice(cutFront);
+
+ var chFrom = Pos(fromLine, cutFront);
+ var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);
+ if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
+ replaceRange(cm.doc, newText, chFrom, chTo, "+input");
+ return true;
+ }
+ },
+
+ ensurePolled: function() {
+ this.forceCompositionEnd();
+ },
+ reset: function() {
+ this.forceCompositionEnd();
+ },
+ forceCompositionEnd: function() {
+ if (!this.composing || this.composing.handled) return;
+ this.applyComposition(this.composing);
+ this.composing.handled = true;
+ this.div.blur();
+ this.div.focus();
+ },
+ applyComposition: function(composing) {
+ if (this.cm.isReadOnly())
+ operation(this.cm, regChange)(this.cm)
+ else if (composing.data && composing.data != composing.startData)
+ operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel);
+ },
+
+ setUneditable: function(node) {
+ node.contentEditable = "false"
+ },
+
+ onKeyPress: function(e) {
+ e.preventDefault();
+ if (!this.cm.isReadOnly())
+ operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
+ },
+
+ readOnlyChanged: function(val) {
+ this.div.contentEditable = String(val != "nocursor")
+ },
+
+ onContextMenu: nothing,
+ resetPosition: nothing,
+
+ needsContentAttribute: true
+ }, ContentEditableInput.prototype);
+
+ function posToDOM(cm, pos) {
+ var view = findViewForLine(cm, pos.line);
+ if (!view || view.hidden) return null;
+ var line = getLine(cm.doc, pos.line);
+ var info = mapFromLineView(view, line, pos.line);
+
+ var order = getOrder(line), side = "left";
+ if (order) {
+ var partPos = getBidiPartAt(order, pos.ch);
+ side = partPos % 2 ? "right" : "left";
+ }
+ var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);
+ result.offset = result.collapse == "right" ? result.end : result.start;
+ return result;
+ }
+
+ function badPos(pos, bad) { if (bad) pos.bad = true; return pos; }
+
+ function domToPos(cm, node, offset) {
+ var lineNode;
+ if (node == cm.display.lineDiv) {
+ lineNode = cm.display.lineDiv.childNodes[offset];
+ if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true);
+ node = null; offset = 0;
+ } else {
+ for (lineNode = node;; lineNode = lineNode.parentNode) {
+ if (!lineNode || lineNode == cm.display.lineDiv) return null;
+ if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break;
+ }
+ }
+ for (var i = 0; i < cm.display.view.length; i++) {
+ var lineView = cm.display.view[i];
+ if (lineView.node == lineNode)
+ return locateNodeInLineView(lineView, node, offset);
+ }
+ }
+
+ function locateNodeInLineView(lineView, node, offset) {
+ var wrapper = lineView.text.firstChild, bad = false;
+ if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true);
+ if (node == wrapper) {
+ bad = true;
+ node = wrapper.childNodes[offset];
+ offset = 0;
+ if (!node) {
+ var line = lineView.rest ? lst(lineView.rest) : lineView.line;
+ return badPos(Pos(lineNo(line), line.text.length), bad);
+ }
+ }
+
+ var textNode = node.nodeType == 3 ? node : null, topNode = node;
+ if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
+ textNode = node.firstChild;
+ if (offset) offset = textNode.nodeValue.length;
+ }
+ while (topNode.parentNode != wrapper) topNode = topNode.parentNode;
+ var measure = lineView.measure, maps = measure.maps;
+
+ function find(textNode, topNode, offset) {
+ for (var i = -1; i < (maps ? maps.length : 0); i++) {
+ var map = i < 0 ? measure.map : maps[i];
+ for (var j = 0; j < map.length; j += 3) {
+ var curNode = map[j + 2];
+ if (curNode == textNode || curNode == topNode) {
+ var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);
+ var ch = map[j] + offset;
+ if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)];
+ return Pos(line, ch);
+ }
+ }
+ }
+ }
+ var found = find(textNode, topNode, offset);
+ if (found) return badPos(found, bad);
+
+ // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
+ for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
+ found = find(after, after.firstChild, 0);
+ if (found)
+ return badPos(Pos(found.line, found.ch - dist), bad);
+ else
+ dist += after.textContent.length;
+ }
+ for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) {
+ found = find(before, before.firstChild, -1);
+ if (found)
+ return badPos(Pos(found.line, found.ch + dist), bad);
+ else
+ dist += before.textContent.length;
+ }
+ }
+
+ function domTextBetween(cm, from, to, fromLine, toLine) {
+ var text = "", closing = false, lineSep = cm.doc.lineSeparator();
+ function recognizeMarker(id) { return function(marker) { return marker.id == id; }; }
+ function walk(node) {
+ if (node.nodeType == 1) {
+ var cmText = node.getAttribute("cm-text");
+ if (cmText != null) {
+ if (cmText == "") cmText = node.textContent.replace(/\u200b/g, "");
+ text += cmText;
+ return;
+ }
+ var markerID = node.getAttribute("cm-marker"), range;
+ if (markerID) {
+ var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));
+ if (found.length && (range = found[0].find()))
+ text += getBetween(cm.doc, range.from, range.to).join(lineSep);
+ return;
+ }
+ if (node.getAttribute("contenteditable") == "false") return;
+ for (var i = 0; i < node.childNodes.length; i++)
+ walk(node.childNodes[i]);
+ if (/^(pre|div|p)$/i.test(node.nodeName))
+ closing = true;
+ } else if (node.nodeType == 3) {
+ var val = node.nodeValue;
+ if (!val) return;
+ if (closing) {
+ text += lineSep;
+ closing = false;
+ }
+ text += val;
+ }
}
- if (widget.coverGutter) {
- node.style.zIndex = 5;
- node.style.position = "relative";
- if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
+ for (;;) {
+ walk(from);
+ if (from == to) break;
+ from = from.nextSibling;
}
+ return text;
}
- // POSITION OBJECT
-
- // A Pos instance represents a position within the text.
- var Pos = CodeMirror.Pos = function(line, ch) {
- if (!(this instanceof Pos)) return new Pos(line, ch);
- this.line = line; this.ch = ch;
- };
-
- // Compare two positions, return 0 if they are the same, a negative
- // number when a is less, and a positive number otherwise.
- var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
-
- function copyPos(x) {return Pos(x.line, x.ch);}
- function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
- function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
+ CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
// SELECTION / CURSOR
@@ -1090,7 +2176,7 @@
// Give beforeSelectionChange handlers a change to influence a
// selection update.
- function filterSelectionChange(doc, sel) {
+ function filterSelectionChange(doc, sel, options) {
var obj = {
ranges: sel.ranges,
update: function(ranges) {
@@ -1098,7 +2184,8 @@
for (var i = 0; i < ranges.length; i++)
this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
clipPos(doc, ranges[i].head));
- }
+ },
+ origin: options && options.origin
};
signal(doc, "beforeSelectionChange", doc, obj);
if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
@@ -1124,9 +2211,10 @@
function setSelectionNoUndo(doc, sel, options) {
if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
- sel = filterSelectionChange(doc, sel);
+ sel = filterSelectionChange(doc, sel, options);
- var bias = cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1;
+ var bias = options && options.bias ||
+ (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
if (!(options && options.scroll === false) && doc.cm)
@@ -1138,9 +2226,10 @@
doc.sel = sel;
- if (doc.cm)
- doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
- doc.cm.curOp.cursorActivity = true;
+ if (doc.cm) {
+ doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
+ signalCursorActivity(doc.cm);
+ }
signalLater(doc, "cursorActivity", doc);
}
@@ -1156,8 +2245,9 @@
var out;
for (var i = 0; i < sel.ranges.length; i++) {
var range = sel.ranges[i];
- var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
- var newHead = skipAtomic(doc, range.head, bias, mayClear);
+ var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
+ var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
+ var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
if (out || newAnchor != range.anchor || newHead != range.head) {
if (!out) out = sel.ranges.slice(0, i);
out[i] = new Range(newAnchor, newHead);
@@ -1166,93 +2256,91 @@
return out ? normalizeSelection(out, sel.primIndex) : sel;
}
- // Ensure a given position is not inside an atomic range.
- function skipAtomic(doc, pos, bias, mayClear) {
- var flipped = false, curPos = pos;
- var dir = bias || 1;
- doc.cantEdit = false;
- search: for (;;) {
- var line = getLine(doc, curPos.line);
- if (line.markedSpans) {
- for (var i = 0; i < line.markedSpans.length; ++i) {
- var sp = line.markedSpans[i], m = sp.marker;
- if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
- (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
- if (mayClear) {
- signal(m, "beforeCursorEnter");
- if (m.explicitlyCleared) {
- if (!line.markedSpans) break;
- else {--i; continue;}
- }
- }
- if (!m.atomic) continue;
- var newPos = m.find(dir < 0 ? -1 : 1);
- if (cmp(newPos, curPos) == 0) {
- newPos.ch += dir;
- if (newPos.ch < 0) {
- if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
- else newPos = null;
- } else if (newPos.ch > line.text.length) {
- if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
- else newPos = null;
- }
- if (!newPos) {
- if (flipped) {
- // Driven in a corner -- no valid cursor position found at all
- // -- try again *with* clearing, if we didn't already
- if (!mayClear) return skipAtomic(doc, pos, bias, true);
- // Otherwise, turn off editing until further notice, and return the start of the doc
- doc.cantEdit = true;
- return Pos(doc.first, 0);
- }
- flipped = true; newPos = pos; dir = -dir;
- }
- }
- curPos = newPos;
- continue search;
+ function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
+ var line = getLine(doc, pos.line);
+ if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
+ var sp = line.markedSpans[i], m = sp.marker;
+ if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
+ (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
+ if (mayClear) {
+ signal(m, "beforeCursorEnter");
+ if (m.explicitlyCleared) {
+ if (!line.markedSpans) break;
+ else {--i; continue;}
}
}
+ if (!m.atomic) continue;
+
+ if (oldPos) {
+ var near = m.find(dir < 0 ? 1 : -1), diff;
+ if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft)
+ near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null);
+ if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
+ return skipAtomicInner(doc, near, pos, dir, mayClear);
+ }
+
+ var far = m.find(dir < 0 ? -1 : 1);
+ if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight)
+ far = movePos(doc, far, dir, far.line == pos.line ? line : null);
+ return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null;
}
- return curPos;
+ }
+ return pos;
+ }
+
+ // Ensure a given position is not inside an atomic range.
+ function skipAtomic(doc, pos, oldPos, bias, mayClear) {
+ var dir = bias || 1;
+ var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
+ skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
+ (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
+ if (!found) {
+ doc.cantEdit = true;
+ return Pos(doc.first, 0);
+ }
+ return found;
+ }
+
+ function movePos(doc, pos, dir, line) {
+ if (dir < 0 && pos.ch == 0) {
+ if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1));
+ else return null;
+ } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
+ if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0);
+ else return null;
+ } else {
+ return new Pos(pos.line, pos.ch + dir);
}
}
// SELECTION DRAWING
- // Redraw the selection and/or cursor
function updateSelection(cm) {
- var display = cm.display, doc = cm.doc;
- var curFragment = document.createDocumentFragment();
- var selFragment = document.createDocumentFragment();
+ cm.display.input.showSelection(cm.display.input.prepareSelection());
+ }
+
+ function prepareSelection(cm, primary) {
+ var doc = cm.doc, result = {};
+ var curFragment = result.cursors = document.createDocumentFragment();
+ var selFragment = result.selection = document.createDocumentFragment();
for (var i = 0; i < doc.sel.ranges.length; i++) {
+ if (primary === false && i == doc.sel.primIndex) continue;
var range = doc.sel.ranges[i];
+ if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue;
var collapsed = range.empty();
if (collapsed || cm.options.showCursorWhenSelecting)
- updateSelectionCursor(cm, range, curFragment);
+ drawSelectionCursor(cm, range.head, curFragment);
if (!collapsed)
- updateSelectionRange(cm, range, selFragment);
+ drawSelectionRange(cm, range, selFragment);
}
-
- // Move the hidden textarea near the cursor to prevent scrolling artifacts
- if (cm.options.moveInputWithCursor) {
- var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
- var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
- var top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
- headPos.top + lineOff.top - wrapOff.top));
- var left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
- headPos.left + lineOff.left - wrapOff.left));
- display.inputDiv.style.top = top + "px";
- display.inputDiv.style.left = left + "px";
- }
-
- removeChildrenAndAdd(display.cursorDiv, curFragment);
- removeChildrenAndAdd(display.selectionDiv, selFragment);
+ return result;
}
// Draws a cursor for the given range
- function updateSelectionCursor(cm, range, output) {
- var pos = cursorCoords(cm, range.head, "div");
+ function drawSelectionCursor(cm, head, output) {
+ var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine);
var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
cursor.style.left = pos.left + "px";
@@ -1270,13 +2358,16 @@
}
// Draws the given range as a highlighted selection
- function updateSelectionRange(cm, range, output) {
+ function drawSelectionRange(cm, range, output) {
var display = cm.display, doc = cm.doc;
var fragment = document.createDocumentFragment();
- var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right;
+ var padding = paddingH(cm.display), leftSide = padding.left;
+ var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;
function add(left, top, width, bottom) {
if (top < 0) top = 0;
+ top = Math.round(top);
+ bottom = Math.round(bottom);
fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
"px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
"px; height: " + (bottom - top) + "px"));
@@ -1352,6 +2443,8 @@
display.blinker = setInterval(function() {
display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
}, cm.options.cursorBlinkRate);
+ else if (cm.options.cursorBlinkRate < 0)
+ display.cursorDiv.style.visibility = "hidden";
}
// HIGHLIGHT WORKER
@@ -1367,18 +2460,24 @@
if (doc.frontier >= cm.display.viewTo) return;
var end = +new Date + cm.options.workTime;
var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
+ var changedLines = [];
- runInOp(cm, function() {
doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
if (doc.frontier >= cm.display.viewFrom) { // Visible
- var oldStyles = line.styles;
- line.styles = highlightLine(cm, line, state, true);
- var ischange = !oldStyles || oldStyles.length != line.styles.length;
+ var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength;
+ var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true);
+ line.styles = highlighted.styles;
+ var oldCls = line.styleClasses, newCls = highlighted.classes;
+ if (newCls) line.styleClasses = newCls;
+ else if (oldCls) line.styleClasses = null;
+ var ischange = !oldStyles || oldStyles.length != line.styles.length ||
+ oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
- if (ischange) regLineChange(cm, doc.frontier, "text");
- line.stateAfter = copyState(doc.mode, state);
+ if (ischange) changedLines.push(doc.frontier);
+ line.stateAfter = tooLong ? state : copyState(doc.mode, state);
} else {
- processLine(cm, line.text, state);
+ if (line.text.length <= cm.options.maxHighlightLength)
+ processLine(cm, line.text, state);
line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
}
++doc.frontier;
@@ -1387,6 +2486,9 @@
return true;
}
});
+ if (changedLines.length) runInOp(cm, function() {
+ for (var i = 0; i < changedLines.length; i++)
+ regLineChange(cm, changedLines[i], "text");
});
}
@@ -1435,8 +2537,17 @@
if (display.cachedPaddingH) return display.cachedPaddingH;
var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
- return display.cachedPaddingH = {left: parseInt(style.paddingLeft),
- right: parseInt(style.paddingRight)};
+ var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
+ if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
+ return data;
+ }
+
+ function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; }
+ function displayWidth(cm) {
+ return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth;
+ }
+ function displayHeight(cm) {
+ return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight;
}
// Ensure the lineView.wrapping.heights array is populated. This is
@@ -1445,7 +2556,7 @@
// height.
function ensureLineHeights(cm, lineView, rect) {
var wrapping = cm.options.lineWrapping;
- var curWidth = wrapping && cm.display.scroller.clientWidth;
+ var curWidth = wrapping && displayWidth(cm);
if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
var heights = lineView.measure.heights = [];
if (wrapping) {
@@ -1511,10 +2622,12 @@
function prepareMeasureForLine(cm, line) {
var lineN = lineNo(line);
var view = findViewForLine(cm, lineN);
- if (view && !view.text)
+ if (view && !view.text) {
view = null;
- else if (view && view.changes)
+ } else if (view && view.changes) {
updateLineForChanges(cm, view, lineN, getDimensions(cm));
+ cm.curOp.forceUpdate = true;
+ }
if (!view)
view = updateExternalMeasurement(cm, line);
@@ -1528,7 +2641,7 @@
// Given a prepared measurement object, measures the position of an
// actual character (or fetches it from the cache).
- function measureCharPrepared(cm, prepared, ch, bias) {
+ function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
if (prepared.before) ch = -1;
var key = ch + (bias || ""), found;
if (prepared.cache.hasOwnProperty(key)) {
@@ -1543,14 +2656,14 @@
found = measureCharInner(cm, prepared, ch, bias);
if (!found.bogus) prepared.cache[key] = found;
}
- return {left: found.left, right: found.right, top: found.top, bottom: found.bottom};
+ return {left: found.left, right: found.right,
+ top: varHeight ? found.rtop : found.top,
+ bottom: varHeight ? found.rbottom : found.bottom};
}
var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
- function measureCharInner(cm, prepared, ch, bias) {
- var map = prepared.map;
-
+ function nodeAndOffsetInLineMap(map, ch, bias) {
var node, start, end, collapse;
// First, search the line map for the text node corresponding to,
// or closest to, the target character.
@@ -1584,22 +2697,38 @@
break;
}
}
+ return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd};
+ }
+
+ function getUsefulRect(rects, bias) {
+ var rect = nullRect
+ if (bias == "left") for (var i = 0; i < rects.length; i++) {
+ if ((rect = rects[i]).left != rect.right) break
+ } else for (var i = rects.length - 1; i >= 0; i--) {
+ if ((rect = rects[i]).left != rect.right) break
+ }
+ return rect
+ }
+
+ function measureCharInner(cm, prepared, ch, bias) {
+ var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);
+ var node = place.node, start = place.start, end = place.end, collapse = place.collapse;
var rect;
if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
- while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
- while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
- if (ie_upto8 && start == 0 && end == mEnd - mStart) {
- rect = node.parentNode.getBoundingClientRect();
- } else if (ie && cm.options.lineWrapping) {
- var rects = range(node, start, end).getClientRects();
- if (rects.length)
- rect = rects[bias == "right" ? rects.length - 1 : 0];
+ for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
+ while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start;
+ while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end;
+ if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)
+ rect = node.parentNode.getBoundingClientRect();
else
- rect = nullRect;
- } else {
- rect = range(node, start, end).getBoundingClientRect();
+ rect = getUsefulRect(range(node, start, end).getClientRects(), bias)
+ if (rect.left || rect.right || start == 0) break;
+ end = start;
+ start = start - 1;
+ collapse = "right";
}
+ if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect);
} else { // If it is a widget, simply get the box for the whole widget.
if (start > 0) collapse = bias = "right";
var rects;
@@ -1608,7 +2737,7 @@
else
rect = node.getBoundingClientRect();
}
- if (ie_upto8 && !start && (!rect || !rect.left && !rect.right)) {
+ if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
var rSpan = node.parentNode.getClientRects()[0];
if (rSpan)
rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom};
@@ -1616,18 +2745,33 @@
rect = nullRect;
}
- var top, bot = (rect.bottom + rect.top) / 2 - prepared.rect.top;
+ var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;
+ var mid = (rtop + rbot) / 2;
var heights = prepared.view.measure.heights;
for (var i = 0; i < heights.length - 1; i++)
- if (bot < heights[i]) break;
- top = i ? heights[i - 1] : 0; bot = heights[i];
+ if (mid < heights[i]) break;
+ var top = i ? heights[i - 1] : 0, bot = heights[i];
var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
top: top, bottom: bot};
if (!rect.left && !rect.right) result.bogus = true;
+ if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }
+
return result;
}
+ // Work around problem with bounding client rects on ranges being
+ // returned incorrectly when zoomed on IE10 and below.
+ function maybeUpdateRectForZooming(measure, rect) {
+ if (!window.screen || screen.logicalXDPI == null ||
+ screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
+ return rect;
+ var scaleX = screen.logicalXDPI / screen.deviceXDPI;
+ var scaleY = screen.logicalYDPI / screen.deviceYDPI;
+ return {left: rect.left * scaleX, right: rect.right * scaleX,
+ top: rect.top * scaleY, bottom: rect.bottom * scaleY};
+ }
+
function clearLineMeasurementCacheFor(lineView) {
if (lineView.measure) {
lineView.measure.cache = {};
@@ -1656,7 +2800,8 @@
// Converts a {top, bottom, left, right} box from line-local
// coordinates into another coordinate system. Context may be one of
- // "line", "div" (display.lineDiv), "local"/null (editor), or "page".
+ // "line", "div" (display.lineDiv), "local"/null (editor), "window",
+ // or "page".
function intoCoordSystem(cm, lineObj, rect, context) {
if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
var size = widgetHeight(lineObj.widgets[i]);
@@ -1704,11 +2849,11 @@
// Returns a box for a given cursor position, which may have an
// 'other' property containing the position of the secondary cursor
// on a bidi boundary.
- function cursorCoords(cm, pos, context, lineObj, preparedMeasure) {
+ function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
lineObj = lineObj || getLine(cm.doc, pos.line);
if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
function get(ch, right) {
- var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left");
+ var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight);
if (right) m.left = m.right; else m.right = m.left;
return intoCoordSystem(cm, lineObj, m, context);
}
@@ -1803,10 +2948,23 @@
for (;;) {
if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
var ch = x < fromX || x - fromX <= toX - x ? from : to;
+ var outside = ch == from ? fromOutside : toOutside
var xDiff = x - (ch == from ? fromX : toX);
+ // This is a kludge to handle the case where the coordinates
+ // are after a line-wrapped line. We should replace it with a
+ // more general handling of cursor positions around line
+ // breaks. (Issue #4078)
+ if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 &&
+ ch < lineObj.text.length && preparedMeasure.view.measure.heights.length > 1) {
+ var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right");
+ if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.abs(x - charSize.right) < xDiff) {
+ outside = false
+ ch++
+ xDiff = x - charSize.right
+ }
+ }
while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
- var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
- xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
+ var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
return pos;
}
var step = Math.ceil(dist / 2), middle = from + step;
@@ -1860,67 +3018,175 @@
// error-prone). Instead, display updates are batched and then all
// combined and executed at once.
+ var operationGroup = null;
+
var nextOpId = 0;
// Start a new operation.
function startOperation(cm) {
cm.curOp = {
+ cm: cm,
viewChanged: false, // Flag that indicates that lines might need to be redrawn
startHeight: cm.doc.height, // Used to detect need to update scrollbar
forceUpdate: false, // Used to force a redraw
updateInput: null, // Whether to reset the input textarea
typing: false, // Whether this reset should be careful to leave existing text (for compositing)
changeObjs: null, // Accumulated changes, for firing change events
- cursorActivity: false, // Whether to fire a cursorActivity event
+ cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
+ cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
selectionChanged: false, // Whether the selection needs to be redrawn
updateMaxLine: false, // Set when the widest line needs to be determined anew
scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
scrollToPos: null, // Used to scroll to a specific position
+ focus: false,
id: ++nextOpId // Unique ID
};
- if (!delayedCallbackDepth++) delayedCallbacks = [];
+ if (operationGroup) {
+ operationGroup.ops.push(cm.curOp);
+ } else {
+ cm.curOp.ownsGroup = operationGroup = {
+ ops: [cm.curOp],
+ delayedCallbacks: []
+ };
+ }
+ }
+
+ function fireCallbacksForOps(group) {
+ // Calls delayed callbacks and cursorActivity handlers until no
+ // new ones appear
+ var callbacks = group.delayedCallbacks, i = 0;
+ do {
+ for (; i < callbacks.length; i++)
+ callbacks[i].call(null);
+ for (var j = 0; j < group.ops.length; j++) {
+ var op = group.ops[j];
+ if (op.cursorActivityHandlers)
+ while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
+ op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm);
+ }
+ } while (i < callbacks.length);
}
// Finish an operation, updating the display and signalling delayed events
function endOperation(cm) {
- var op = cm.curOp, doc = cm.doc, display = cm.display;
- cm.curOp = null;
-
+ var op = cm.curOp, group = op.ownsGroup;
+ if (!group) return;
+
+ try { fireCallbacksForOps(group); }
+ finally {
+ operationGroup = null;
+ for (var i = 0; i < group.ops.length; i++)
+ group.ops[i].cm.curOp = null;
+ endOperations(group);
+ }
+ }
+
+ // The DOM updates done when an operation finishes are batched so
+ // that the minimum number of relayouts are required.
+ function endOperations(group) {
+ var ops = group.ops;
+ for (var i = 0; i < ops.length; i++) // Read DOM
+ endOperation_R1(ops[i]);
+ for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
+ endOperation_W1(ops[i]);
+ for (var i = 0; i < ops.length; i++) // Read DOM
+ endOperation_R2(ops[i]);
+ for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
+ endOperation_W2(ops[i]);
+ for (var i = 0; i < ops.length; i++) // Read DOM
+ endOperation_finish(ops[i]);
+ }
+
+ function endOperation_R1(op) {
+ var cm = op.cm, display = cm.display;
+ maybeClipScrollbars(cm);
if (op.updateMaxLine) findMaxLine(cm);
- // If it looks like an update might be needed, call updateDisplay
- if (op.viewChanged || op.forceUpdate || op.scrollTop != null ||
- op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
- op.scrollToPos.to.line >= display.viewTo) ||
- display.maxLineChanged && cm.options.lineWrapping) {
- var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
- if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
+ op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
+ op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
+ op.scrollToPos.to.line >= display.viewTo) ||
+ display.maxLineChanged && cm.options.lineWrapping;
+ op.update = op.mustUpdate &&
+ new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
+ }
+
+ function endOperation_W1(op) {
+ op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
+ }
+
+ function endOperation_R2(op) {
+ var cm = op.cm, display = cm.display;
+ if (op.updatedDisplay) updateHeightsInViewport(cm);
+
+ op.barMeasure = measureForScrollbars(cm);
+
+ // If the max line changed since it was last measured, measure it,
+ // and ensure the document's width matches it.
+ // updateDisplay_W2 will use these properties to do the actual resizing
+ if (display.maxLineChanged && !cm.options.lineWrapping) {
+ op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
+ cm.display.sizerWidth = op.adjustWidthTo;
+ op.barMeasure.scrollWidth =
+ Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth);
+ op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));
}
- // If no update was run, but the selection changed, redraw that.
- if (!updated && op.selectionChanged) updateSelection(cm);
- if (!updated && op.startHeight != cm.doc.height) updateScrollbars(cm);
- // Propagate the scroll position to the actual DOM scroller
- if (op.scrollTop != null && display.scroller.scrollTop != op.scrollTop) {
- var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
- display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
+ if (op.updatedDisplay || op.selectionChanged)
+ op.preparedSelection = display.input.prepareSelection(op.focus);
+ }
+
+ function endOperation_W2(op) {
+ var cm = op.cm;
+
+ if (op.adjustWidthTo != null) {
+ cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
+ if (op.maxScrollLeft < cm.doc.scrollLeft)
+ setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true);
+ cm.display.maxLineChanged = false;
}
- if (op.scrollLeft != null && display.scroller.scrollLeft != op.scrollLeft) {
- var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
- display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
+
+ var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())
+ if (op.preparedSelection)
+ cm.display.input.showSelection(op.preparedSelection, takeFocus);
+ if (op.updatedDisplay || op.startHeight != cm.doc.height)
+ updateScrollbars(cm, op.barMeasure);
+ if (op.updatedDisplay)
+ setDocumentHeight(cm, op.barMeasure);
+
+ if (op.selectionChanged) restartBlink(cm);
+
+ if (cm.state.focused && op.updateInput)
+ cm.display.input.reset(op.typing);
+ if (takeFocus) ensureFocus(op.cm);
+ }
+
+ function endOperation_finish(op) {
+ var cm = op.cm, display = cm.display, doc = cm.doc;
+
+ if (op.updatedDisplay) postUpdateDisplay(cm, op.update);
+
+ // Abort mouse wheel delta measurement, when scrolling explicitly
+ if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
+ display.wheelStartX = display.wheelStartY = null;
+
+ // Propagate the scroll position to the actual DOM scroller
+ if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) {
+ doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
+ display.scrollbars.setScrollTop(doc.scrollTop);
+ display.scroller.scrollTop = doc.scrollTop;
+ }
+ if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
+ doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
+ display.scrollbars.setScrollLeft(doc.scrollLeft);
+ display.scroller.scrollLeft = doc.scrollLeft;
alignHorizontally(cm);
}
// If we need to scroll a specific position into view, do so.
if (op.scrollToPos) {
- var coords = scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from),
- clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin);
+ var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
+ clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
}
- if (op.selectionChanged) restartBlink(cm);
-
- if (cm.state.focused && op.updateInput)
- resetInput(cm, op.typing);
-
// Fire events for markers that are hidden/unidden by editing or
// undoing
var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
@@ -1929,19 +3195,14 @@
if (unhidden) for (var i = 0; i < unhidden.length; ++i)
if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
- var delayed;
- if (!--delayedCallbackDepth) {
- delayed = delayedCallbacks;
- delayedCallbacks = null;
- }
+ if (display.wrapper.offsetHeight)
+ doc.scrollTop = cm.display.scroller.scrollTop;
+
// Fire change events, and delayed event handlers
- if (op.changeObjs) {
- for (var i = 0; i < op.changeObjs.length; i++)
- signal(cm, "change", cm, op.changeObjs[i]);
+ if (op.changeObjs)
signal(cm, "changes", cm, op.changeObjs);
- }
- if (op.cursorActivity) signal(cm, "cursorActivity", cm);
- if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
+ if (op.update)
+ op.update.finish();
}
// Run the given function in an operation
@@ -2113,7 +3374,8 @@
function viewCuttingPoint(cm, oldN, newN, dir) {
var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
- if (!sawCollapsedSpans) return {index: index, lineN: newN};
+ if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
+ return {index: index, lineN: newN};
for (var i = 0, n = cm.display.viewFrom; i < index; i++)
n += view[i].size;
if (n != oldN) {
@@ -2166,138 +3428,6 @@
return dirty;
}
- // INPUT HANDLING
-
- // Poll for input changes, using the normal rate of polling. This
- // runs as long as the editor is focused.
- function slowPoll(cm) {
- if (cm.display.pollingFast) return;
- cm.display.poll.set(cm.options.pollInterval, function() {
- readInput(cm);
- if (cm.state.focused) slowPoll(cm);
- });
- }
-
- // When an event has just come in that is likely to add or change
- // something in the input textarea, we poll faster, to ensure that
- // the change appears on the screen quickly.
- function fastPoll(cm) {
- var missed = false;
- cm.display.pollingFast = true;
- function p() {
- var changed = readInput(cm);
- if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
- else {cm.display.pollingFast = false; slowPoll(cm);}
- }
- cm.display.poll.set(20, p);
- }
-
- // Read input from the textarea, and update the document to match.
- // When something is selected, it is present in the textarea, and
- // selected (unless it is huge, in which case a placeholder is
- // used). When nothing is selected, the cursor sits after previously
- // seen text (can be empty), which is stored in prevInput (we must
- // not reset the textarea when typing, because that breaks IME).
- function readInput(cm) {
- var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
- // Since this is called a *lot*, try to bail out as cheaply as
- // possible when it is clear that nothing happened. hasSelection
- // will be the case when there is a lot of text in the textarea,
- // in which case reading its value would be expensive.
- if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false;
- var text = input.value;
- // If nothing changed, bail.
- if (text == prevInput && !cm.somethingSelected()) return false;
- // Work around nonsensical selection resetting in IE9/10
- if (ie && !ie_upto8 && cm.display.inputHasSelection === text) {
- resetInput(cm);
- return false;
- }
-
- var withOp = !cm.curOp;
- if (withOp) startOperation(cm);
- cm.display.shift = false;
-
- // Find the part of the input that is actually new
- var same = 0, l = Math.min(prevInput.length, text.length);
- while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
- var inserted = text.slice(same), textLines = splitLines(inserted);
-
- // When pasing N lines into N selections, insert one line per selection
- var multiPaste = cm.state.pasteIncoming && textLines.length > 1 && doc.sel.ranges.length == textLines.length;
-
- // Normal behavior is to insert the new text into every selection
- for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
- var range = doc.sel.ranges[i];
- var from = range.from(), to = range.to();
- // Handle deletion
- if (same < prevInput.length)
- from = Pos(from.line, from.ch - (prevInput.length - same));
- // Handle overwrite
- else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
- to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
- var updateInput = cm.curOp.updateInput;
- var changeEvent = {from: from, to: to, text: multiPaste ? [textLines[i]] : textLines,
- origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
- makeChange(cm.doc, changeEvent);
- signalLater(cm, "inputRead", cm, changeEvent);
- // When an 'electric' character is inserted, immediately trigger a reindent
- if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
- cm.options.smartIndent && range.head.ch < 100 &&
- (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
- var electric = cm.getModeAt(range.head).electricChars;
- if (electric) for (var j = 0; j < electric.length; j++)
- if (inserted.indexOf(electric.charAt(j)) > -1) {
- indentLine(cm, range.head.line, "smart");
- break;
- }
- }
- }
- ensureCursorVisible(cm);
- cm.curOp.updateInput = updateInput;
- cm.curOp.typing = true;
-
- // Don't leave long text in the textarea, since it makes further polling slow
- if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
- else cm.display.prevInput = text;
- if (withOp) endOperation(cm);
- cm.state.pasteIncoming = cm.state.cutIncoming = false;
- return true;
- }
-
- // Reset the input to correspond to the selection (or to be empty,
- // when not typing and nothing is selected)
- function resetInput(cm, typing) {
- var minimal, selected, doc = cm.doc;
- if (cm.somethingSelected()) {
- cm.display.prevInput = "";
- var range = doc.sel.primary();
- minimal = hasCopyEvent &&
- (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
- var content = minimal ? "-" : selected || cm.getSelection();
- cm.display.input.value = content;
- if (cm.state.focused) selectInput(cm.display.input);
- if (ie && !ie_upto8) cm.display.inputHasSelection = content;
- } else if (!typing) {
- cm.display.prevInput = cm.display.input.value = "";
- if (ie && !ie_upto8) cm.display.inputHasSelection = null;
- }
- cm.display.inaccurateSelection = minimal;
- }
-
- function focusInput(cm) {
- if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input))
- cm.display.input.focus();
- }
-
- function ensureFocus(cm) {
- if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
- }
-
- function isReadOnly(cm) {
- return cm.options.readOnly || cm.doc.cantEdit;
- }
-
// EVENT HANDLERS
// Attach the necessary event handlers when initializing the editor
@@ -2305,26 +3435,75 @@
var d = cm.display;
on(d.scroller, "mousedown", operation(cm, onMouseDown));
// Older IE's will not fire a second mousedown for a double click
- if (ie_upto10)
+ if (ie && ie_version < 11)
on(d.scroller, "dblclick", operation(cm, function(e) {
if (signalDOMEvent(cm, e)) return;
var pos = posFromMouse(cm, e);
if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
e_preventDefault(e);
- var word = findWordAt(cm.doc, pos);
+ var word = cm.findWordAt(pos);
extendSelection(cm.doc, word.anchor, word.head);
}));
else
on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
- // Prevent normal selection in the editor (we handle our own)
- on(d.lineSpace, "selectstart", function(e) {
- if (!eventInWidget(d, e)) e_preventDefault(e);
- });
// Some browsers fire contextmenu *after* opening the menu, at
// which point we can't mess with it anymore. Context menu is
// handled in onMouseDown for these browsers.
if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
+ // Used to suppress mouse event handling when a touch happens
+ var touchFinished, prevTouch = {end: 0};
+ function finishTouch() {
+ if (d.activeTouch) {
+ touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000);
+ prevTouch = d.activeTouch;
+ prevTouch.end = +new Date;
+ }
+ };
+ function isMouseLikeTouchEvent(e) {
+ if (e.touches.length != 1) return false;
+ var touch = e.touches[0];
+ return touch.radiusX <= 1 && touch.radiusY <= 1;
+ }
+ function farAway(touch, other) {
+ if (other.left == null) return true;
+ var dx = other.left - touch.left, dy = other.top - touch.top;
+ return dx * dx + dy * dy > 20 * 20;
+ }
+ on(d.scroller, "touchstart", function(e) {
+ if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
+ clearTimeout(touchFinished);
+ var now = +new Date;
+ d.activeTouch = {start: now, moved: false,
+ prev: now - prevTouch.end <= 300 ? prevTouch : null};
+ if (e.touches.length == 1) {
+ d.activeTouch.left = e.touches[0].pageX;
+ d.activeTouch.top = e.touches[0].pageY;
+ }
+ }
+ });
+ on(d.scroller, "touchmove", function() {
+ if (d.activeTouch) d.activeTouch.moved = true;
+ });
+ on(d.scroller, "touchend", function(e) {
+ var touch = d.activeTouch;
+ if (touch && !eventInWidget(d, e) && touch.left != null &&
+ !touch.moved && new Date - touch.start < 300) {
+ var pos = cm.coordsChar(d.activeTouch, "page"), range;
+ if (!touch.prev || farAway(touch, touch.prev)) // Single tap
+ range = new Range(pos, pos);
+ else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap
+ range = cm.findWordAt(pos);
+ else // Triple tap
+ range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0)));
+ cm.setSelection(range.anchor, range.head);
+ cm.focus();
+ e_preventDefault(e);
+ }
+ finishTouch();
+ });
+ on(d.scroller, "touchcancel", finishTouch);
+
// Sync scrolling between fake scrollbars and real scrollable
// area, ensure viewport is updated when scrolling.
on(d.scroller, "scroll", function() {
@@ -2334,91 +3513,52 @@
signal(cm, "scroll", cm);
}
});
- on(d.scrollbarV, "scroll", function() {
- if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
- });
- on(d.scrollbarH, "scroll", function() {
- if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
- });
// Listen to wheel events in order to try and update the viewport on time.
on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
- // Prevent clicks in the scrollbars from killing focus
- function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
- on(d.scrollbarH, "mousedown", reFocus);
- on(d.scrollbarV, "mousedown", reFocus);
// Prevent wrapper from ever scrolling
on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
- // When the window resizes, we need to refresh active editors.
- var resizeTimer;
- function onResize() {
- if (resizeTimer == null) resizeTimer = setTimeout(function() {
- resizeTimer = null;
- // Might be a text scaling operation, clear size caches.
- d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = knownScrollbarWidth = null;
- cm.setSize();
- }, 100);
- }
- on(window, "resize", onResize);
- // The above handler holds on to the editor and its data
- // structures. Here we poll to unregister it when the editor is no
- // longer in the document, so that it can be garbage-collected.
- function unregister() {
- if (contains(document.body, d.wrapper)) setTimeout(unregister, 5000);
- else off(window, "resize", onResize);
- }
- setTimeout(unregister, 5000);
+ d.dragFunctions = {
+ enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);},
+ over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},
+ start: function(e){onDragStart(cm, e);},
+ drop: operation(cm, onDrop),
+ leave: function(e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}
+ };
- on(d.input, "keyup", operation(cm, onKeyUp));
- on(d.input, "input", function() {
- if (ie && !ie_upto8 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
- fastPoll(cm);
- });
- on(d.input, "keydown", operation(cm, onKeyDown));
- on(d.input, "keypress", operation(cm, onKeyPress));
- on(d.input, "focus", bind(onFocus, cm));
- on(d.input, "blur", bind(onBlur, cm));
-
- function drag_(e) {
- if (!signalDOMEvent(cm, e)) e_stop(e);
- }
- if (cm.options.dragDrop) {
- on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
- on(d.scroller, "dragenter", drag_);
- on(d.scroller, "dragover", drag_);
- on(d.scroller, "drop", operation(cm, onDrop));
- }
- on(d.scroller, "paste", function(e) {
- if (eventInWidget(d, e)) return;
- cm.state.pasteIncoming = true;
- focusInput(cm);
- fastPoll(cm);
- });
- on(d.input, "paste", function() {
- cm.state.pasteIncoming = true;
- fastPoll(cm);
- });
+ var inp = d.input.getField();
+ on(inp, "keyup", function(e) { onKeyUp.call(cm, e); });
+ on(inp, "keydown", operation(cm, onKeyDown));
+ on(inp, "keypress", operation(cm, onKeyPress));
+ on(inp, "focus", function (e) { onFocus(cm, e); });
+ on(inp, "blur", function (e) { onBlur(cm, e); });
+ }
- function prepareCopy(e) {
- if (d.inaccurateSelection) {
- d.prevInput = "";
- d.inaccurateSelection = false;
- d.input.value = cm.getSelection();
- selectInput(d.input);
- }
- if (e.type == "cut") cm.state.cutIncoming = true;
+ function dragDropChanged(cm, value, old) {
+ var wasOn = old && old != CodeMirror.Init;
+ if (!value != !wasOn) {
+ var funcs = cm.display.dragFunctions;
+ var toggle = value ? on : off;
+ toggle(cm.display.scroller, "dragstart", funcs.start);
+ toggle(cm.display.scroller, "dragenter", funcs.enter);
+ toggle(cm.display.scroller, "dragover", funcs.over);
+ toggle(cm.display.scroller, "dragleave", funcs.leave);
+ toggle(cm.display.scroller, "drop", funcs.drop);
}
- on(d.input, "cut", prepareCopy);
- on(d.input, "copy", prepareCopy);
+ }
- // Needed to handle Tab key in KHTML
- if (khtml) on(d.sizer, "mouseup", function() {
- if (activeElt() == d.input) d.input.blur();
- focusInput(cm);
- });
+ // Called when the window resizes
+ function onResize(cm) {
+ var d = cm.display;
+ if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth)
+ return;
+ // Might be a text scaling operation, clear size caches.
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
+ d.scrollbarsClipped = false;
+ cm.setSize();
}
// MOUSE EVENTS
@@ -2426,7 +3566,9 @@
// Return true when the given mouse event happened in a widget
function eventInWidget(display, e) {
for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
- if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
+ if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
+ (n.parentNode == display.sizer && n != display.mover))
+ return true;
}
}
@@ -2437,11 +3579,8 @@
// coordinates beyond the right of the text.
function posFromMouse(cm, e, liberal, forRect) {
var display = cm.display;
- if (!liberal) {
- var target = e_target(e);
- if (target == display.scrollbarH || target == display.scrollbarV ||
- target == display.scrollbarFiller || target == display.gutterFiller) return null;
- }
+ if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null;
+
var x, y, space = display.lineSpace.getBoundingClientRect();
// Fails unpredictably on IE[67] when mouse is dragged around quickly.
try { x = e.clientX - space.left; y = e.clientY - space.top; }
@@ -2449,7 +3588,7 @@
var coords = coordsChar(cm, x, y), line;
if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
- coords = Pos(coords.line, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff);
+ coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
}
return coords;
}
@@ -2460,8 +3599,8 @@
// middle-click-paste. Or it might be a click on something we should
// not interfere with, such as a scrollbar or widget.
function onMouseDown(e) {
- if (signalDOMEvent(this, e)) return;
var cm = this, display = cm.display;
+ if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return;
display.shift = e.shiftKey;
if (eventInWidget(display, e)) {
@@ -2479,7 +3618,10 @@
switch (e_button(e)) {
case 1:
- if (start)
+ // #3261: make sure, that we're not starting a second selection
+ if (cm.state.selectingText)
+ cm.state.selectingText(e);
+ else if (start)
leftButtonDown(cm, e, start);
else if (e_target(e) == display.scroller)
e_preventDefault(e);
@@ -2487,18 +3629,20 @@
case 2:
if (webkit) cm.state.lastMiddleDown = +new Date;
if (start) extendSelection(cm.doc, start);
- setTimeout(bind(focusInput, cm), 20);
+ setTimeout(function() {display.input.focus();}, 20);
e_preventDefault(e);
break;
case 3:
if (captureRightClick) onContextMenu(cm, e);
+ else delayBlurEvent(cm);
break;
}
}
var lastClick, lastDoubleClick;
function leftButtonDown(cm, e, start) {
- setTimeout(bind(ensureFocus, cm), 0);
+ if (ie) setTimeout(bind(ensureFocus, cm), 0);
+ else cm.curOp.focus = activeElt();
var now = +new Date, type;
if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
@@ -2511,18 +3655,20 @@
lastClick = {time: now, pos: start};
}
- var sel = cm.doc.sel, addNew = mac ? e.metaKey : e.ctrlKey;
- if (cm.options.dragDrop && dragAndDrop && !addNew && !isReadOnly(cm) &&
- type == "single" && sel.contains(start) > -1 && sel.somethingSelected())
- leftButtonStartDrag(cm, e, start);
+ var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
+ if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
+ type == "single" && (contained = sel.contains(start)) > -1 &&
+ (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) &&
+ (cmp(contained.to(), start) > 0 || start.xRel < 0))
+ leftButtonStartDrag(cm, e, start, modifier);
else
- leftButtonSelect(cm, e, start, type, addNew);
+ leftButtonSelect(cm, e, start, type, modifier);
}
// Start a text drag. When it ends, see if any dragging actually
// happen, and treat as a click if it didn't.
- function leftButtonStartDrag(cm, e, start) {
- var display = cm.display;
+ function leftButtonStartDrag(cm, e, start, modifier) {
+ var display = cm.display, startTime = +new Date;
var dragEnd = operation(cm, function(e2) {
if (webkit) display.scroller.draggable = false;
cm.state.draggingText = false;
@@ -2530,16 +3676,19 @@
off(display.scroller, "drop", dragEnd);
if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
e_preventDefault(e2);
- extendSelection(cm.doc, start);
- focusInput(cm);
- // Work around unexplainable focus problem in IE9 (#2127)
- if (ie_upto10 && !ie_upto8)
- setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
+ if (!modifier && +new Date - 200 < startTime)
+ extendSelection(cm.doc, start);
+ // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
+ if (webkit || ie && ie_version == 9)
+ setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
+ else
+ display.input.focus();
}
});
// Let the drag handler handle this.
if (webkit) display.scroller.draggable = true;
cm.state.draggingText = dragEnd;
+ dragEnd.copy = mac ? e.altKey : e.ctrlKey
// IE's approach to draggable
if (display.scroller.dragDrop) display.scroller.dragDrop();
on(document, "mouseup", dragEnd);
@@ -2551,24 +3700,25 @@
var display = cm.display, doc = cm.doc;
e_preventDefault(e);
- var ourRange, ourIndex, startSel = doc.sel;
- if (addNew) {
+ var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;
+ if (addNew && !e.shiftKey) {
ourIndex = doc.sel.contains(start);
if (ourIndex > -1)
- ourRange = doc.sel.ranges[ourIndex];
+ ourRange = ranges[ourIndex];
else
ourRange = new Range(start, start);
} else {
ourRange = doc.sel.primary();
+ ourIndex = doc.sel.primIndex;
}
- if (e.altKey) {
+ if (chromeOS ? e.shiftKey && e.metaKey : e.altKey) {
type = "rect";
if (!addNew) ourRange = new Range(start, start);
start = posFromMouse(cm, e, true, true);
ourIndex = -1;
} else if (type == "double") {
- var word = findWordAt(doc, start);
+ var word = cm.findWordAt(start);
if (cm.display.shift || doc.extend)
ourRange = extendRange(doc, ourRange, word.anchor, word.head);
else
@@ -2586,12 +3736,17 @@
if (!addNew) {
ourIndex = 0;
setSelection(doc, new Selection([ourRange], 0), sel_mouse);
- } else if (ourIndex > -1) {
- replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
- } else {
- ourIndex = doc.sel.ranges.length;
- setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex),
+ startSel = doc.sel;
+ } else if (ourIndex == -1) {
+ ourIndex = ranges.length;
+ setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex),
+ {scroll: false, origin: "*mouse"});
+ } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) {
+ setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),
{scroll: false, origin: "*mouse"});
+ startSel = doc.sel;
+ } else {
+ replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
}
var lastPos = start;
@@ -2613,13 +3768,15 @@
ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
}
if (!ranges.length) ranges.push(new Range(start, start));
- setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), sel_mouse);
+ setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
+ {origin: "*mouse", scroll: false});
+ cm.scrollIntoView(pos);
} else {
var oldRange = ourRange;
var anchor = oldRange.anchor, head = pos;
if (type != "single") {
if (type == "double")
- var range = findWordAt(doc, pos);
+ var range = cm.findWordAt(pos);
else
var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
if (cmp(range.anchor, anchor) > 0) {
@@ -2648,7 +3805,7 @@
var cur = posFromMouse(cm, e, true, type == "rect");
if (!cur) return;
if (cmp(cur, lastPos) != 0) {
- ensureFocus(cm);
+ cm.curOp.focus = activeElt();
extendTo(cur);
var visible = visibleLines(display, doc);
if (cur.line >= visible.to || cur.line < visible.from)
@@ -2664,26 +3821,28 @@
}
function done(e) {
+ cm.state.selectingText = false;
counter = Infinity;
e_preventDefault(e);
- focusInput(cm);
+ display.input.focus();
off(document, "mousemove", move);
off(document, "mouseup", up);
doc.history.lastSelOrigin = null;
}
var move = operation(cm, function(e) {
- if ((ie && !ie_upto9) ? !e.buttons : !e_button(e)) done(e);
+ if (!e_button(e)) done(e);
else extend(e);
});
var up = operation(cm, done);
+ cm.state.selectingText = up;
on(document, "mousemove", move);
on(document, "mouseup", up);
}
// Determines whether an event happened in the gutter, and fires the
// handlers for the corresponding event.
- function gutterEvent(cm, e, type, prevent, signalfn) {
+ function gutterEvent(cm, e, type, prevent) {
try { var mX = e.clientX, mY = e.clientY; }
catch(e) { return false; }
if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
@@ -2700,14 +3859,14 @@
if (g && g.getBoundingClientRect().right >= mX) {
var line = lineAtHeight(cm.doc, mY);
var gutter = cm.options.gutters[i];
- signalfn(cm, type, cm, line, gutter, e);
+ signal(cm, type, cm, line, gutter, e);
return e_defaultPrevented(e);
}
}
}
function clickInGutter(cm, e) {
- return gutterEvent(cm, e, "gutterClick", true, signalLater);
+ return gutterEvent(cm, e, "gutterClick", true);
}
// Kludge to work around strange IE behavior where it'll sometimes
@@ -2716,27 +3875,36 @@
function onDrop(e) {
var cm = this;
+ clearDragCursor(cm);
if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
return;
e_preventDefault(e);
- if (ie_upto10) lastDrop = +new Date;
+ if (ie) lastDrop = +new Date;
var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
- if (!pos || isReadOnly(cm)) return;
+ if (!pos || cm.isReadOnly()) return;
// Might be a file drop, in which case we simply extract the text
// and insert it.
if (files && files.length && window.FileReader && window.File) {
var n = files.length, text = Array(n), read = 0;
var loadFile = function(file, i) {
+ if (cm.options.allowDropFileTypes &&
+ indexOf(cm.options.allowDropFileTypes, file.type) == -1)
+ return;
+
var reader = new FileReader;
- reader.onload = function() {
- text[i] = reader.result;
+ reader.onload = operation(cm, function() {
+ var content = reader.result;
+ if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = "";
+ text[i] = content;
if (++read == n) {
pos = clipPos(cm.doc, pos);
- var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"};
+ var change = {from: pos, to: pos,
+ text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),
+ origin: "paste"};
makeChange(cm.doc, change);
setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
}
- };
+ });
reader.readAsText(file);
};
for (var i = 0; i < n; ++i) loadFile(files[i], i);
@@ -2745,18 +3913,19 @@
if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
cm.state.draggingText(e);
// Ensure the editor is re-focused
- setTimeout(bind(focusInput, cm), 20);
+ setTimeout(function() {cm.display.input.focus();}, 20);
return;
}
try {
var text = e.dataTransfer.getData("Text");
if (text) {
- var selected = cm.state.draggingText && cm.listSelections();
+ if (cm.state.draggingText && !cm.state.draggingText.copy)
+ var selected = cm.listSelections();
setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
if (selected) for (var i = 0; i < selected.length; ++i)
replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
cm.replaceSelection(text, "around", "paste");
- focusInput(cm);
+ cm.display.input.focus();
}
}
catch(e){}
@@ -2764,10 +3933,11 @@
}
function onDragStart(cm, e) {
- if (ie_upto10 && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
+ if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
e.dataTransfer.setData("Text", cm.getSelection());
+ e.dataTransfer.effectAllowed = "copyMove"
// Use dummy image instead of default browsers image.
// Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
@@ -2785,6 +3955,25 @@
}
}
+ function onDragOver(cm, e) {
+ var pos = posFromMouse(cm, e);
+ if (!pos) return;
+ var frag = document.createDocumentFragment();
+ drawSelectionCursor(cm, pos, frag);
+ if (!cm.display.dragCursor) {
+ cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors");
+ cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);
+ }
+ removeChildrenAndAdd(cm.display.dragCursor, frag);
+ }
+
+ function clearDragCursor(cm) {
+ if (cm.display.dragCursor) {
+ cm.display.lineSpace.removeChild(cm.display.dragCursor);
+ cm.display.dragCursor = null;
+ }
+ }
+
// SCROLL EVENTS
// Sync the scrollable area and scrollbars, ensure the viewport
@@ -2792,10 +3981,10 @@
function setScrollTop(cm, val) {
if (Math.abs(cm.doc.scrollTop - val) < 2) return;
cm.doc.scrollTop = val;
- if (!gecko) updateDisplay(cm, {top: val});
+ if (!gecko) updateDisplaySimple(cm, {top: val});
if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
- if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
- if (gecko) updateDisplay(cm);
+ cm.display.scrollbars.setScrollTop(val);
+ if (gecko) updateDisplaySimple(cm);
startWorker(cm, 100);
}
// Sync scroller and scrollbar, ensure the gutter elements are
@@ -2806,7 +3995,7 @@
cm.doc.scrollLeft = val;
alignHorizontally(cm);
if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
- if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
+ cm.display.scrollbars.setScrollLeft(val);
}
// Since the delta values reported on mouse wheel events are
@@ -2830,16 +4019,28 @@
else if (chrome) wheelPixelsPerUnit = -.7;
else if (safari) wheelPixelsPerUnit = -1/3;
- function onScrollWheel(cm, e) {
+ var wheelEventDelta = function(e) {
var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
else if (dy == null) dy = e.wheelDelta;
+ return {x: dx, y: dy};
+ };
+ CodeMirror.wheelEventPixels = function(e) {
+ var delta = wheelEventDelta(e);
+ delta.x *= wheelPixelsPerUnit;
+ delta.y *= wheelPixelsPerUnit;
+ return delta;
+ };
+
+ function onScrollWheel(cm, e) {
+ var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
var display = cm.display, scroll = display.scroller;
// Quit if there's nothing to scroll here
- if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
- dy && scroll.scrollHeight > scroll.clientHeight)) return;
+ var canScrollX = scroll.scrollWidth > scroll.clientWidth;
+ var canScrollY = scroll.scrollHeight > scroll.clientHeight;
+ if (!(dx && canScrollX || dy && canScrollY)) return;
// Webkit browsers on OS X abort momentum scrolls when the target
// of the scroll event is removed from the scrollable element.
@@ -2863,10 +4064,15 @@
// scrolling entirely here. It'll be slightly off from native, but
// better than glitching out.
if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
- if (dy)
+ if (dy && canScrollY)
setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
- e_preventDefault(e);
+ // Only prevent default scrolling if vertical scrolling is
+ // actually possible. Otherwise, it causes vertical scroll
+ // jitter on OSX trackpads when deltaX is small and deltaY
+ // is large (issue #3579)
+ if (!dy || (dy && canScrollY))
+ e_preventDefault(e);
display.wheelStartX = null; // Abort measurement, if in progress
return;
}
@@ -2878,7 +4084,7 @@
var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
if (pixels < 0) top = Math.max(0, top + pixels - 50);
else bot = Math.min(cm.doc.height, bot + pixels + 50);
- updateDisplay(cm, {top: top, bottom: bot});
+ updateDisplaySimple(cm, {top: top, bottom: bot});
}
if (wheelSamples < 20) {
@@ -2912,10 +4118,10 @@
}
// Ensure previous input has been read, so that the handler sees a
// consistent view of the document
- if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
+ cm.display.input.ensurePolled();
var prevShift = cm.display.shift, done = false;
try {
- if (isReadOnly(cm)) cm.state.suppressEdits = true;
+ if (cm.isReadOnly()) cm.state.suppressEdits = true;
if (dropShift) cm.display.shift = false;
done = bound(cm) != Pass;
} finally {
@@ -2925,71 +4131,79 @@
return done;
}
- // Collect the currently active keymaps.
- function allKeyMaps(cm) {
- var maps = cm.state.keyMaps.slice(0);
- if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
- maps.push(cm.options.keyMap);
- return maps;
+ function lookupKeyForEditor(cm, name, handle) {
+ for (var i = 0; i < cm.state.keyMaps.length; i++) {
+ var result = lookupKey(name, cm.state.keyMaps[i], handle, cm);
+ if (result) return result;
+ }
+ return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))
+ || lookupKey(name, cm.options.keyMap, handle, cm);
+ }
+
+ var stopSeq = new Delayed;
+ function dispatchKey(cm, name, e, handle) {
+ var seq = cm.state.keySeq;
+ if (seq) {
+ if (isModifierKey(name)) return "handled";
+ stopSeq.set(50, function() {
+ if (cm.state.keySeq == seq) {
+ cm.state.keySeq = null;
+ cm.display.input.reset();
+ }
+ });
+ name = seq + " " + name;
+ }
+ var result = lookupKeyForEditor(cm, name, handle);
+
+ if (result == "multi")
+ cm.state.keySeq = name;
+ if (result == "handled")
+ signalLater(cm, "keyHandled", cm, name, e);
+
+ if (result == "handled" || result == "multi") {
+ e_preventDefault(e);
+ restartBlink(cm);
+ }
+
+ if (seq && !result && /\'$/.test(name)) {
+ e_preventDefault(e);
+ return true;
+ }
+ return !!result;
}
- var maybeTransition;
// Handle a key from the keydown event.
function handleKeyBinding(cm, e) {
- // Handle automatic keymap transitions
- var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
- clearTimeout(maybeTransition);
- if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
- if (getKeyMap(cm.options.keyMap) == startMap) {
- cm.options.keyMap = (next.call ? next.call(null, cm) : next);
- keyMapChanged(cm);
- }
- }, 50);
-
- var name = keyName(e, true), handled = false;
+ var name = keyName(e, true);
if (!name) return false;
- var keymaps = allKeyMaps(cm);
- if (e.shiftKey) {
+ if (e.shiftKey && !cm.state.keySeq) {
// First try to resolve full name (including 'Shift-'). Failing
// that, see if there is a cursor-motion command (starting with
// 'go') bound to the keyname without 'Shift-'.
- handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
- || lookupKey(name, keymaps, function(b) {
- if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
- return doHandleBinding(cm, b);
- });
+ return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);})
+ || dispatchKey(cm, name, e, function(b) {
+ if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
+ return doHandleBinding(cm, b);
+ });
} else {
- handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
- }
-
- if (handled) {
- e_preventDefault(e);
- restartBlink(cm);
- signalLater(cm, "keyHandled", cm, name, e);
+ return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); });
}
- return handled;
}
// Handle a key from the keypress event
function handleCharBinding(cm, e, ch) {
- var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
- function(b) { return doHandleBinding(cm, b, true); });
- if (handled) {
- e_preventDefault(e);
- restartBlink(cm);
- signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
- }
- return handled;
+ return dispatchKey(cm, "'" + ch + "'", e,
+ function(b) { return doHandleBinding(cm, b, true); });
}
var lastStoppedKey = null;
function onKeyDown(e) {
var cm = this;
- ensureFocus(cm);
+ cm.curOp.focus = activeElt();
if (signalDOMEvent(cm, e)) return;
// IE does strange things with escape.
- if (ie_upto10 && e.keyCode == 27) e.returnValue = false;
+ if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false;
var code = e.keyCode;
cm.display.shift = code == 16 || e.shiftKey;
var handled = handleKeyBinding(cm, e);
@@ -2999,47 +4213,81 @@
if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
cm.replaceSelection("", null, "cut");
}
+
+ // Turn mouse into crosshair when Alt is held on Mac.
+ if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
+ showCrossHair(cm);
+ }
+
+ function showCrossHair(cm) {
+ var lineDiv = cm.display.lineDiv;
+ addClass(lineDiv, "CodeMirror-crosshair");
+
+ function up(e) {
+ if (e.keyCode == 18 || !e.altKey) {
+ rmClass(lineDiv, "CodeMirror-crosshair");
+ off(document, "keyup", up);
+ off(document, "mouseover", up);
+ }
+ }
+ on(document, "keyup", up);
+ on(document, "mouseover", up);
}
function onKeyUp(e) {
- if (signalDOMEvent(this, e)) return;
if (e.keyCode == 16) this.doc.sel.shift = false;
+ signalDOMEvent(this, e);
}
function onKeyPress(e) {
var cm = this;
- if (signalDOMEvent(cm, e)) return;
+ if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return;
var keyCode = e.keyCode, charCode = e.charCode;
if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
- if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
+ if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return;
var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
if (handleCharBinding(cm, e, ch)) return;
- if (ie && !ie_upto8) cm.display.inputHasSelection = null;
- fastPoll(cm);
+ cm.display.input.onKeyPress(e);
}
// FOCUS/BLUR EVENTS
- function onFocus(cm) {
+ function delayBlurEvent(cm) {
+ cm.state.delayingBlurEvent = true;
+ setTimeout(function() {
+ if (cm.state.delayingBlurEvent) {
+ cm.state.delayingBlurEvent = false;
+ onBlur(cm);
+ }
+ }, 100);
+ }
+
+ function onFocus(cm, e) {
+ if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false;
+
if (cm.options.readOnly == "nocursor") return;
if (!cm.state.focused) {
- signal(cm, "focus", cm);
+ signal(cm, "focus", cm, e);
cm.state.focused = true;
- if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
- cm.display.wrapper.className += " CodeMirror-focused";
- if (!cm.curOp) {
- resetInput(cm);
- if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
+ addClass(cm.display.wrapper, "CodeMirror-focused");
+ // This test prevents this from firing when a context
+ // menu is closed (since the input reset would kill the
+ // select-all detection hack)
+ if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
+ cm.display.input.reset();
+ if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730
}
+ cm.display.input.receivedFocus();
}
- slowPoll(cm);
restartBlink(cm);
}
- function onBlur(cm) {
+ function onBlur(cm, e) {
+ if (cm.state.delayingBlurEvent) return;
+
if (cm.state.focused) {
- signal(cm, "blur", cm);
+ signal(cm, "blur", cm, e);
cm.state.focused = false;
- cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
+ rmClass(cm.display.wrapper, "CodeMirror-focused");
}
clearInterval(cm.display.blinker);
setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150);
@@ -3047,81 +4295,18 @@
// CONTEXT MENU HANDLING
- var detectingSelectAll;
// To make the context menu work, we need to briefly unhide the
- // textarea (making it as unobtrusive as possible) to let the
- // right-click take effect on it.
- function onContextMenu(cm, e) {
- if (signalDOMEvent(cm, e, "contextmenu")) return;
- var display = cm.display;
- if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
-
- var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
- if (!pos || presto) return; // Opera is difficult.
-
- // Reset the current text selection only if the click is done outside of the selection
- // and 'resetSelectionOnContextMenu' option is true.
- var reset = cm.options.resetSelectionOnContextMenu;
- if (reset && cm.doc.sel.contains(pos) == -1)
- operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
-
- var oldCSS = display.input.style.cssText;
- display.inputDiv.style.position = "absolute";
- display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
- (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
- "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
- focusInput(cm);
- resetInput(cm);
- // Adds "Select all" to context menu in FF
- if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
-
- // Select-all will be greyed out if there's nothing to select, so
- // this adds a zero-width space so that we can later check whether
- // it got selected.
- function prepareSelectAllHack() {
- if (display.input.selectionStart != null) {
- var extval = display.input.value = "\u200b" + (cm.somethingSelected() ? display.input.value : "");
- display.prevInput = "\u200b";
- display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
- }
- }
- function rehide() {
- display.inputDiv.style.position = "relative";
- display.input.style.cssText = oldCSS;
- if (ie_upto8) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
- slowPoll(cm);
-
- // Try to detect the user choosing select-all
- if (display.input.selectionStart != null) {
- if (!ie || ie_upto8) prepareSelectAllHack();
- clearTimeout(detectingSelectAll);
- var i = 0, poll = function(){
- if (display.prevInput == "\u200b" && display.input.selectionStart == 0)
- operation(cm, commands.selectAll)(cm);
- else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
- else resetInput(cm);
- };
- detectingSelectAll = setTimeout(poll, 200);
- }
- }
-
- if (ie && !ie_upto8) prepareSelectAllHack();
- if (captureRightClick) {
- e_stop(e);
- var mouseup = function() {
- off(window, "mouseup", mouseup);
- setTimeout(rehide, 20);
- };
- on(window, "mouseup", mouseup);
- } else {
- setTimeout(rehide, 50);
- }
+ // textarea (making it as unobtrusive as possible) to let the
+ // right-click take effect on it.
+ function onContextMenu(cm, e) {
+ if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return;
+ if (signalDOMEvent(cm, e, "contextmenu")) return;
+ cm.display.input.onContextMenu(e);
}
function contextMenuInGutter(cm, e) {
if (!hasHandler(cm, "gutterContextMenu")) return false;
- return gutterEvent(cm, e, "gutterContextMenu", false, signal);
+ return gutterEvent(cm, e, "gutterContextMenu", false);
}
// UPDATING
@@ -3249,7 +4434,7 @@
// Revert a change stored in a document's history.
function makeChangeFromHistory(doc, type, allowSelectionOnly) {
- if (doc.cm && doc.cm.state.suppressEdits) return;
+ if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) return;
var hist = doc.history, event, selAfter = doc.sel;
var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;
@@ -3296,9 +4481,9 @@
antiChanges.push(historyChangeFromChange(doc, change));
- var after = i ? computeSelAfterChange(doc, change, null) : lst(source);
+ var after = i ? computeSelAfterChange(doc, change) : lst(source);
makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
- if (doc.cm) ensureCursorVisible(doc.cm);
+ if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)});
var rebased = [];
// Propagate to the linked documents
@@ -3315,12 +4500,17 @@
// Sub-views need their line numbers shifted when text is added
// above or below them in the parent document.
function shiftDoc(doc, distance) {
+ if (distance == 0) return;
doc.first += distance;
doc.sel = new Selection(map(doc.sel.ranges, function(range) {
return new Range(Pos(range.anchor.line + distance, range.anchor.ch),
Pos(range.head.line + distance, range.head.ch));
}), doc.sel.primIndex);
- if (doc.cm) regChange(doc.cm, doc.first, doc.first - distance, distance);
+ if (doc.cm) {
+ regChange(doc.cm, doc.first, doc.first - distance, distance);
+ for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
+ regLineChange(doc.cm, l, "gutter");
+ }
}
// More lower-level change function, handling only a single document
@@ -3350,7 +4540,7 @@
change.removed = getBetween(doc, change.from, change.to);
- if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
+ if (!selAfter) selAfter = computeSelAfterChange(doc, change);
if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans);
else updateDoc(doc, change, spans);
setSelectionNoUndo(doc, selAfter, sel_dontScroll);
@@ -3373,7 +4563,7 @@
}
if (doc.sel.contains(change.from, change.to) > -1)
- cm.curOp.cursorActivity = true;
+ signalCursorActivity(cm);
updateDoc(doc, change, spans, estimateHeight(cm));
@@ -3396,24 +4586,31 @@
var lendiff = change.text.length - (to.line - from.line) - 1;
// Remember that these lines changed, for updating the display
- if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
+ if (change.full)
+ regChange(cm);
+ else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
regLineChange(cm, from.line, "text");
else
regChange(cm, from.line, to.line + 1, lendiff);
- if (hasHandler(cm, "change") || hasHandler(cm, "changes"))
- (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push({
+ var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
+ if (changeHandler || changesHandler) {
+ var obj = {
from: from, to: to,
text: change.text,
removed: change.removed,
origin: change.origin
- });
+ };
+ if (changeHandler) signalLater(cm, "change", cm, obj);
+ if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj);
+ }
+ cm.display.selForContextMenu = null;
}
function replaceRange(doc, code, from, to, origin) {
if (!to) to = from;
if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; }
- if (typeof code == "string") code = splitLines(code);
+ if (typeof code == "string") code = doc.splitLines(code);
makeChange(doc, {from: from, to: to, text: code, origin: origin});
}
@@ -3422,13 +4619,15 @@
// If an editor sits on the top or bottom of the window, partially
// scrolled out of view, this ensures that the cursor is visible.
function maybeScrollWindow(cm, coords) {
+ if (signalDOMEvent(cm, "scrollCursorIntoView")) return;
+
var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
if (coords.top + box.top < 0) doScroll = true;
else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
if (doScroll != null && !phantom) {
var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
(coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " +
- (coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
+ (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " +
coords.left + "px; width: 2px;");
cm.display.lineSpace.appendChild(scrollNode);
scrollNode.scrollIntoView(doScroll);
@@ -3441,7 +4640,7 @@
// measured, the position of something may 'drift' during drawing).
function scrollPosIntoView(cm, pos, end, margin) {
if (margin == null) margin = 0;
- for (;;) {
+ for (var limit = 0; limit < 5; limit++) {
var changed = false, coords = cursorCoords(cm, pos);
var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left),
@@ -3457,8 +4656,9 @@
setScrollLeft(cm, scrollPos.scrollLeft);
if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
}
- if (!changed) return coords;
+ if (!changed) break;
}
+ return coords;
}
// Scroll a given set of coordinates into view (immediately).
@@ -3476,7 +4676,8 @@
var display = cm.display, snapMargin = textHeight(cm.display);
if (y1 < 0) y1 = 0;
var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
- var screen = display.scroller.clientHeight - scrollerCutOff, result = {};
+ var screen = displayHeight(cm), result = {};
+ if (y2 - y1 > screen) y2 = y1 + screen;
var docBottom = cm.doc.height + paddingVert(display);
var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
if (y1 < screentop) {
@@ -3487,16 +4688,15 @@
}
var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
- var screenw = display.scroller.clientWidth - scrollerCutOff;
- x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
- var gutterw = display.gutters.offsetWidth;
- var atLeft = x1 < gutterw + 10;
- if (x1 < screenleft + gutterw || atLeft) {
- if (atLeft) x1 = 0;
- result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
- } else if (x2 > screenw + screenleft - 3) {
- result.scrollLeft = x2 + 10 - screenw;
- }
+ var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0);
+ var tooWide = x2 - x1 > screenw;
+ if (tooWide) x2 = x1 + screenw;
+ if (x1 < 10)
+ result.scrollLeft = 0;
+ else if (x1 < screenleft)
+ result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
+ else if (x2 > screenw + screenleft - 3)
+ result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw;
return result;
}
@@ -3552,7 +4752,7 @@
if (how == "smart") {
// Fall back to "prev" when the mode doesn't have an indentation
// method.
- if (!cm.doc.mode.indent) how = "prev";
+ if (!doc.mode.indent) how = "prev";
else state = getStateBefore(cm, n);
}
@@ -3564,8 +4764,8 @@
indentation = 0;
how = "not";
} else if (how == "smart") {
- indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
- if (indentation == Pass) {
+ indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
+ if (indentation == Pass || indentation > 150) {
if (!aggressive) return;
how = "prev";
}
@@ -3588,7 +4788,9 @@
if (pos < indentation) indentString += spaceStr(indentation - pos);
if (indentString != curSpaceString) {
- replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+ replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+ line.stateAfter = null;
+ return true;
} else {
// Ensure that, if the cursor was in the whitespace at the start
// of the line, it is moved to the end of that space.
@@ -3601,19 +4803,17 @@
}
}
}
- line.stateAfter = null;
}
// Utility for applying a change to a line by handle or number,
// returning the number and optionally registering the line as
// changed.
- function changeLine(cm, handle, changeType, op) {
- var no = handle, line = handle, doc = cm.doc;
+ function changeLine(doc, handle, changeType, op) {
+ var no = handle, line = handle;
if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
else no = lineNo(handle);
if (no == null) return null;
- if (op(line, no)) regLineChange(cm, no, changeType);
- else return null;
+ if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType);
return line;
}
@@ -3654,10 +4854,9 @@
function findPosH(doc, pos, dir, unit, visually) {
var line = pos.line, ch = pos.ch, origDir = dir;
var lineObj = getLine(doc, line);
- var possible = true;
function findNextLine() {
var l = line + dir;
- if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
+ if (l < doc.first || l >= doc.first + doc.size) return false
line = l;
return lineObj = getLine(doc, l);
}
@@ -3667,19 +4866,22 @@
if (!boundToLine && findNextLine()) {
if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
else ch = dir < 0 ? lineObj.text.length : 0;
- } else return (possible = false);
+ } else return false
} else ch = next;
return true;
}
- if (unit == "char") moveOnce();
- else if (unit == "column") moveOnce(true);
- else if (unit == "word" || unit == "group") {
+ if (unit == "char") {
+ moveOnce()
+ } else if (unit == "column") {
+ moveOnce(true)
+ } else if (unit == "word" || unit == "group") {
var sawType = null, group = unit == "group";
+ var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
for (var first = true;; first = false) {
if (dir < 0 && !moveOnce(!first)) break;
var cur = lineObj.text.charAt(ch) || "\n";
- var type = isWordChar(cur) ? "w"
+ var type = isWordChar(cur, helper) ? "w"
: group && cur == "\n" ? "n"
: !group || /\s/.test(cur) ? null
: "p";
@@ -3693,8 +4895,8 @@
if (dir > 0 && !moveOnce(!first)) break;
}
}
- var result = skipAtomic(doc, Pos(line, ch), origDir, true);
- if (!possible) result.hitSide = true;
+ var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true);
+ if (!cmp(pos, result)) result.hitSide = true;
return result;
}
@@ -3705,7 +4907,8 @@
var doc = cm.doc, x = pos.left, y;
if (unit == "page") {
var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
- y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
+ var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);
+ y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;
} else if (unit == "line") {
y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
}
@@ -3718,22 +4921,6 @@
return target;
}
- // Find the word at the given position (as returned by coordsChar).
- function findWordAt(doc, pos) {
- var line = getLine(doc, pos.line).text;
- var start = pos.ch, end = pos.ch;
- if (line) {
- if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
- var startChar = line.charAt(start);
- var check = isWordChar(startChar) ? isWordChar
- : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
- : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
- while (start > 0 && check(line.charAt(start - 1))) --start;
- while (end < line.length && check(line.charAt(end))) ++end;
- }
- return new Range(Pos(pos.line, start), Pos(pos.line, end));
- }
-
// EDITOR METHODS
// The publicly visible API. Note that methodOp(f) means
@@ -3746,7 +4933,7 @@
CodeMirror.prototype = {
constructor: CodeMirror,
- focus: function(){window.focus(); focusInput(this); fastPoll(this);},
+ focus: function(){window.focus(); this.display.input.focus();},
setOption: function(option, value) {
var options = this.options, old = options[option];
@@ -3760,12 +4947,12 @@
getDoc: function() {return this.doc;},
addKeyMap: function(map, bottom) {
- this.state.keyMaps[bottom ? "push" : "unshift"](map);
+ this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map));
},
removeKeyMap: function(map) {
var maps = this.state.keyMaps;
for (var i = 0; i < maps.length; ++i)
- if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
+ if (maps[i] == map || maps[i].name == map) {
maps.splice(i, 1);
return true;
}
@@ -3774,7 +4961,10 @@
addOverlay: methodOp(function(spec, options) {
var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
if (mode.startState) throw new Error("Overlays may not be stateful.");
- this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
+ insertSorted(this.state.overlays,
+ {mode: mode, modeSpec: spec, opaque: options && options.opaque,
+ priority: (options && options.priority) || 0},
+ function(overlay) { return overlay.priority })
this.state.modeGen++;
regChange(this);
}),
@@ -3803,11 +4993,14 @@
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (!range.empty()) {
- var start = Math.max(end, range.from().line);
- var to = range.to();
+ var from = range.from(), to = range.to();
+ var start = Math.max(end, from.line);
end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
for (var j = start; j < end; ++j)
indentLine(this, j, how);
+ var newRanges = this.doc.sel.ranges;
+ if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
+ replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll);
} else if (range.head.line > end) {
indentLine(this, range.head.line, how, true);
end = range.head.line;
@@ -3819,33 +5012,27 @@
// Fetch the parser token for a given character. Useful for hacks
// that want to inspect the mode state (say, for completion).
getTokenAt: function(pos, precise) {
- var doc = this.doc;
- pos = clipPos(doc, pos);
- var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
- var line = getLine(doc, pos.line);
- var stream = new StringStream(line.text, this.options.tabSize);
- while (stream.pos < pos.ch && !stream.eol()) {
- stream.start = stream.pos;
- var style = mode.token(stream, state);
- }
- return {start: stream.start,
- end: stream.pos,
- string: stream.current(),
- type: style || null,
- state: state};
+ return takeToken(this, pos, precise);
+ },
+
+ getLineTokens: function(line, precise) {
+ return takeToken(this, Pos(line), precise, true);
},
getTokenTypeAt: function(pos) {
pos = clipPos(this.doc, pos);
var styles = getLineStyles(this, getLine(this.doc, pos.line));
var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
- if (ch == 0) return styles[2];
- for (;;) {
+ var type;
+ if (ch == 0) type = styles[2];
+ else for (;;) {
var mid = (before + after) >> 1;
if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
else if (styles[mid * 2 + 1] < ch) before = mid + 1;
- else return styles[mid * 2 + 2];
+ else { type = styles[mid * 2 + 2]; break; }
}
+ var cut = type ? type.indexOf("cm-overlay ") : -1;
+ return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
},
getModeAt: function(pos) {
@@ -3860,7 +5047,7 @@
getHelpers: function(pos, type) {
var found = [];
- if (!helpers.hasOwnProperty(type)) return helpers;
+ if (!helpers.hasOwnProperty(type)) return found;
var help = helpers[type], mode = this.getModeAt(pos);
if (typeof mode[type] == "string") {
if (help[mode[type]]) found.push(help[mode[type]]);
@@ -3910,10 +5097,15 @@
return lineAtHeight(this.doc, height + this.display.viewOffset);
},
heightAtLine: function(line, mode) {
- var end = false, last = this.doc.first + this.doc.size - 1;
- if (line < this.doc.first) line = this.doc.first;
- else if (line > last) { line = last; end = true; }
- var lineObj = getLine(this.doc, line);
+ var end = false, lineObj;
+ if (typeof line == "number") {
+ var last = this.doc.first + this.doc.size - 1;
+ if (line < this.doc.first) line = this.doc.first;
+ else if (line > last) { line = last; end = true; }
+ lineObj = getLine(this.doc, line);
+ } else {
+ lineObj = line;
+ }
return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top +
(end ? this.doc.height - heightAtLine(lineObj) : 0);
},
@@ -3922,7 +5114,7 @@
defaultCharWidth: function() { return charWidth(this.display); },
setGutterMarker: methodOp(function(line, gutterID, value) {
- return changeLine(this, line, "gutter", function(line) {
+ return changeLine(this.doc, line, "gutter", function(line) {
var markers = line.gutterMarkers || (line.gutterMarkers = {});
markers[gutterID] = value;
if (!value && isEmpty(markers)) line.gutterMarkers = null;
@@ -3942,38 +5134,6 @@
});
}),
- addLineClass: methodOp(function(handle, where, cls) {
- return changeLine(this, handle, "class", function(line) {
- var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
- if (!line[prop]) line[prop] = cls;
- else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
- else line[prop] += " " + cls;
- return true;
- });
- }),
-
- removeLineClass: methodOp(function(handle, where, cls) {
- return changeLine(this, handle, "class", function(line) {
- var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
- var cur = line[prop];
- if (!cur) return false;
- else if (cls == null) line[prop] = null;
- else {
- var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
- if (!found) return false;
- var end = found.index + found[0].length;
- line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
- }
- return true;
- });
- }),
-
- addLineWidget: methodOp(function(handle, node, options) {
- return addLineWidget(this, handle, node, options);
- }),
-
- removeLineWidget: function(widget) { widget.clear(); },
-
lineInfo: function(line) {
if (typeof line == "number") {
if (!isLine(this.doc, line)) return null;
@@ -3996,6 +5156,8 @@
pos = cursorCoords(this, clipPos(this.doc, pos));
var top = pos.bottom, left = pos.left;
node.style.position = "absolute";
+ node.setAttribute("cm-ignore-events", "true");
+ this.display.input.setUneditable(node);
display.sizer.appendChild(node);
if (vert == "over") {
top = pos.top;
@@ -4026,13 +5188,15 @@
triggerOnKeyDown: methodOp(onKeyDown),
triggerOnKeyPress: methodOp(onKeyPress),
- triggerOnKeyUp: methodOp(onKeyUp),
+ triggerOnKeyUp: onKeyUp,
execCommand: function(cmd) {
if (commands.hasOwnProperty(cmd))
- return commands[cmd](this);
+ return commands[cmd].call(null, this);
},
+ triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),
+
findPosH: function(from, amount, unit, visually) {
var dir = 1;
if (amount < 0) { dir = -1; amount = -amount; }
@@ -4095,16 +5259,35 @@
doc.sel.ranges[i].goalColumn = goals[i];
}),
+ // Find the word at the given position (as returned by coordsChar).
+ findWordAt: function(pos) {
+ var doc = this.doc, line = getLine(doc, pos.line).text;
+ var start = pos.ch, end = pos.ch;
+ if (line) {
+ var helper = this.getHelper(pos, "wordChars");
+ if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
+ var startChar = line.charAt(start);
+ var check = isWordChar(startChar, helper)
+ ? function(ch) { return isWordChar(ch, helper); }
+ : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
+ : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
+ while (start > 0 && check(line.charAt(start - 1))) --start;
+ while (end < line.length && check(line.charAt(end))) ++end;
+ }
+ return new Range(Pos(pos.line, start), Pos(pos.line, end));
+ },
+
toggleOverwrite: function(value) {
if (value != null && value == this.state.overwrite) return;
if (this.state.overwrite = !this.state.overwrite)
- this.display.cursorDiv.className += " CodeMirror-overwrite";
+ addClass(this.display.cursorDiv, "CodeMirror-overwrite");
else
- this.display.cursorDiv.className = this.display.cursorDiv.className.replace(" CodeMirror-overwrite", "");
+ rmClass(this.display.cursorDiv, "CodeMirror-overwrite");
signal(this, "overwriteToggle", this, this.state.overwrite);
},
- hasFocus: function() { return activeElt() == this.display.input; },
+ hasFocus: function() { return this.display.input.getField() == activeElt(); },
+ isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); },
scrollTo: methodOp(function(x, y) {
if (x != null || y != null) resolveScrollToPos(this);
@@ -4112,10 +5295,11 @@
if (y != null) this.curOp.scrollTop = y;
}),
getScrollInfo: function() {
- var scroller = this.display.scroller, co = scrollerCutOff;
+ var scroller = this.display.scroller;
return {left: scroller.scrollLeft, top: scroller.scrollTop,
- height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
- clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
+ height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,
+ width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,
+ clientHeight: displayHeight(this), clientWidth: displayWidth(this)};
},
scrollIntoView: methodOp(function(range, margin) {
@@ -4143,14 +5327,21 @@
}),
setSize: methodOp(function(width, height) {
+ var cm = this;
function interpret(val) {
return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
}
- if (width != null) this.display.wrapper.style.width = interpret(width);
- if (height != null) this.display.wrapper.style.height = interpret(height);
- if (this.options.lineWrapping) clearLineMeasurementCache(this);
- this.curOp.forceUpdate = true;
- signal(this, "refresh", this);
+ if (width != null) cm.display.wrapper.style.width = interpret(width);
+ if (height != null) cm.display.wrapper.style.height = interpret(height);
+ if (cm.options.lineWrapping) clearLineMeasurementCache(this);
+ var lineNo = cm.display.viewFrom;
+ cm.doc.iter(lineNo, cm.display.viewTo, function(line) {
+ if (line.widgets) for (var i = 0; i < line.widgets.length; i++)
+ if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; }
+ ++lineNo;
+ });
+ cm.curOp.forceUpdate = true;
+ signal(cm, "refresh", this);
}),
operation: function(f){return runInOp(this, f);},
@@ -4158,8 +5349,10 @@
refresh: methodOp(function() {
var oldHeight = this.display.cachedTextHeight;
regChange(this);
+ this.curOp.forceUpdate = true;
clearCaches(this);
this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop);
+ updateGutterSpace(this);
if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
estimateLineHeights(this);
signal(this, "refresh", this);
@@ -4170,13 +5363,14 @@
old.cm = null;
attachDoc(this, doc);
clearCaches(this);
- resetInput(this);
+ this.display.input.reset();
this.scrollTo(doc.scrollLeft, doc.scrollTop);
+ this.curOp.forceScroll = true;
signalLater(this, "swapDoc", this, old);
return old;
}),
- getInputField: function(){return this.display.input;},
+ getInputField: function(){return this.display.input.getField();},
getWrapperElement: function(){return this.display.wrapper;},
getScrollerElement: function(){return this.display.scroller;},
getGutterElement: function(){return this.display.gutters;}
@@ -4217,12 +5411,34 @@
clearCaches(cm);
regChange(cm);
}, true);
- option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) {
- cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
- cm.refresh();
- }, true);
+ option("lineSeparator", null, function(cm, val) {
+ cm.doc.lineSep = val;
+ if (!val) return;
+ var newBreaks = [], lineNo = cm.doc.first;
+ cm.doc.iter(function(line) {
+ for (var pos = 0;;) {
+ var found = line.text.indexOf(val, pos);
+ if (found == -1) break;
+ pos = found + val.length;
+ newBreaks.push(Pos(lineNo, found));
+ }
+ lineNo++;
+ });
+ for (var i = newBreaks.length - 1; i >= 0; i--)
+ replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length))
+ });
+ option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) {
+ cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
+ if (old != CodeMirror.Init) cm.refresh();
+ });
option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
option("electricChars", true);
+ option("inputStyle", mobile ? "contenteditable" : "textarea", function() {
+ throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME
+ }, true);
+ option("spellcheck", false, function(cm, val) {
+ cm.getInputField().spellcheck = val
+ }, true);
option("rtlMoveVisually", !windows);
option("wholeLineUpdateBefore", true);
@@ -4230,7 +5446,12 @@
themeChanged(cm);
guttersChanged(cm);
}, true);
- option("keyMap", "default", keyMapChanged);
+ option("keyMap", "default", function(cm, val, old) {
+ var next = getKeyMap(val);
+ var prev = old != CodeMirror.Init && getKeyMap(old);
+ if (prev && prev.detach) prev.detach(cm, next);
+ if (next.attach) next.attach(cm, prev || null);
+ });
option("extraKeys", null);
option("lineWrapping", false, wrappingChanged, true);
@@ -4242,7 +5463,13 @@
cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
cm.refresh();
}, true);
- option("coverGutterNextToScrollbar", false, updateScrollbars, true);
+ option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true);
+ option("scrollbarStyle", "native", function(cm) {
+ initScrollbars(cm);
+ updateScrollbars(cm);
+ cm.display.scrollbars.setScrollTop(cm.doc.scrollTop);
+ cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft);
+ }, true);
option("lineNumbers", false, function(cm) {
setGuttersForLineNumbers(cm.options);
guttersChanged(cm);
@@ -4252,6 +5479,7 @@
option("showCursorWhenSelecting", false, updateSelection, true);
option("resetSelectionOnContextMenu", true);
+ option("lineWiseCopyCut", true);
option("readOnly", false, function(cm, val) {
if (val == "nocursor") {
@@ -4260,15 +5488,17 @@
cm.display.disabled = true;
} else {
cm.display.disabled = false;
- if (!val) resetInput(cm);
}
+ cm.display.input.readOnlyChanged(val)
});
- option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true);
- option("dragDrop", true);
+ option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
+ option("dragDrop", true, dragDropChanged);
+ option("allowDropFileTypes", null);
option("cursorBlinkRate", 530);
option("cursorScrollMargin", 0);
- option("cursorHeight", 1);
+ option("cursorHeight", 1, updateSelection, true);
+ option("singleCursorHeightPerLine", true, updateSelection, true);
option("workTime", 100);
option("workDelay", 100);
option("flattenSpans", true, resetModeState, true);
@@ -4279,11 +5509,11 @@
option("viewportMargin", 10, function(cm){cm.refresh();}, true);
option("maxHighlightLength", 10000, resetModeState, true);
option("moveInputWithCursor", true, function(cm, val) {
- if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
+ if (!val) cm.display.input.resetPosition();
});
option("tabindex", null, function(cm, val) {
- cm.display.input.tabIndex = val || "";
+ cm.display.input.getField().tabIndex = val || "";
});
option("autofocus", null);
@@ -4297,10 +5527,8 @@
// load a mode. (Preferred mechanism is the require/define calls.)
CodeMirror.defineMode = function(name, mode) {
if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
- if (arguments.length > 2) {
- mode.dependencies = [];
- for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
- }
+ if (arguments.length > 2)
+ mode.dependencies = Array.prototype.slice.call(arguments, 2);
modes[name] = mode;
};
@@ -4320,6 +5548,8 @@
spec.name = found.name;
} else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
return CodeMirror.resolveMode("application/xml");
+ } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) {
+ return CodeMirror.resolveMode("application/json");
}
if (typeof spec == "string") return {name: spec};
else return spec || {name: "null"};
@@ -4451,6 +5681,20 @@
return {from: Pos(range.from().line, 0), to: range.from()};
});
},
+ delWrappedLineLeft: function(cm) {
+ deleteNearSelection(cm, function(range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ var leftPos = cm.coordsChar({left: 0, top: top}, "div");
+ return {from: leftPos, to: range.from()};
+ });
+ },
+ delWrappedLineRight: function(cm) {
+ deleteNearSelection(cm, function(range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
+ return {from: range.from(), to: rightPos };
+ });
+ },
undo: function(cm) {cm.undo();},
redo: function(cm) {cm.redo();},
undoSelection: function(cm) {cm.undoSelection();},
@@ -4458,23 +5702,17 @@
goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
goLineStart: function(cm) {
- cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, sel_move);
+ cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); },
+ {origin: "+move", bias: 1});
},
goLineStartSmart: function(cm) {
cm.extendSelectionsBy(function(range) {
- var start = lineStart(cm, range.head.line);
- var line = cm.getLineHandle(start.line);
- var order = getOrder(line);
- if (!order || order[0].level == 0) {
- var firstNonWS = Math.max(0, line.text.search(/\S/));
- var inWS = range.head.line == start.line && range.head.ch <= firstNonWS && range.head.ch;
- return Pos(start.line, inWS ? 0 : firstNonWS);
- }
- return start;
- }, sel_move);
+ return lineStartSmart(cm, range.head);
+ }, {origin: "+move", bias: 1});
},
goLineEnd: function(cm) {
- cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, sel_move);
+ cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); },
+ {origin: "+move", bias: -1});
},
goLineRight: function(cm) {
cm.extendSelectionsBy(function(range) {
@@ -4488,6 +5726,14 @@
return cm.coordsChar({left: 0, top: top}, "div");
}, sel_move);
},
+ goLineLeftSmart: function(cm) {
+ cm.extendSelectionsBy(function(range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ var pos = cm.coordsChar({left: 0, top: top}, "div");
+ if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head);
+ return pos;
+ }, sel_move);
+ },
goLineUp: function(cm) {cm.moveV(-1, "line");},
goLineDown: function(cm) {cm.moveV(1, "line");},
goPageUp: function(cm) {cm.moveV(-1, "page");},
@@ -4510,19 +5756,41 @@
indentMore: function(cm) {cm.indentSelection("add");},
indentLess: function(cm) {cm.indentSelection("subtract");},
insertTab: function(cm) {cm.replaceSelection("\t");},
+ insertSoftTab: function(cm) {
+ var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;
+ for (var i = 0; i < ranges.length; i++) {
+ var pos = ranges[i].from();
+ var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
+ spaces.push(spaceStr(tabSize - col % tabSize));
+ }
+ cm.replaceSelections(spaces);
+ },
defaultTab: function(cm) {
if (cm.somethingSelected()) cm.indentSelection("add");
else cm.execCommand("insertTab");
},
transposeChars: function(cm) {
runInOp(cm, function() {
- var ranges = cm.listSelections();
+ var ranges = cm.listSelections(), newSel = [];
for (var i = 0; i < ranges.length; i++) {
var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
- if (cur.ch > 0 && cur.ch < line.length - 1)
- cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
- Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
+ if (line) {
+ if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1);
+ if (cur.ch > 0) {
+ cur = new Pos(cur.line, cur.ch + 1);
+ cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
+ Pos(cur.line, cur.ch - 2), cur, "+transpose");
+ } else if (cur.line > cm.doc.first) {
+ var prev = getLine(cm.doc, cur.line - 1).text;
+ if (prev)
+ cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
+ prev.charAt(prev.length - 1),
+ Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose");
+ }
+ }
+ newSel.push(new Range(cur, cur));
}
+ cm.setSelections(newSel);
});
},
newlineAndIndent: function(cm) {
@@ -4530,18 +5798,21 @@
var len = cm.listSelections().length;
for (var i = 0; i < len; i++) {
var range = cm.listSelections()[i];
- cm.replaceRange("\n", range.anchor, range.head, "+input");
+ cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input");
cm.indentLine(range.from().line + 1, null, true);
- ensureCursorVisible(cm);
}
+ ensureCursorVisible(cm);
});
},
+ openLine: function(cm) {cm.replaceSelection("\n", "start")},
toggleOverwrite: function(cm) {cm.toggleOverwrite();}
};
+
// STANDARD KEYMAPS
var keyMap = CodeMirror.keyMap = {};
+
keyMap.basic = {
"Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
"End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
@@ -4555,7 +5826,7 @@
// are simply ignored.
keyMap.pcDefault = {
"Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
- "Ctrl-Home": "goDocStart", "Ctrl-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
+ "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
"Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
"Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
"Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
@@ -4563,87 +5834,125 @@
"Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
fallthrough: "basic"
};
- keyMap.macDefault = {
- "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
- "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
- "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore",
- "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
- "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
- "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
- "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection",
- fallthrough: ["basic", "emacsy"]
- };
// Very basic readline/emacs-style bindings, which are standard on Mac.
keyMap.emacsy = {
"Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
"Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
"Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
- "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
+ "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars",
+ "Ctrl-O": "openLine"
+ };
+ keyMap.macDefault = {
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
+ "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
+ "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
+ "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
+ fallthrough: ["basic", "emacsy"]
};
keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
// KEYMAP DISPATCH
- function getKeyMap(val) {
- if (typeof val == "string") return keyMap[val];
- else return val;
- }
-
- // Given an array of keymaps and a key name, call handle on any
- // bindings found, until that returns a truthy value, at which point
- // we consider the key handled. Implements things like binding a key
- // to false stopping further handling and keymap fallthrough.
- var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) {
- function lookup(map) {
- map = getKeyMap(map);
- var found = map[name];
- if (found === false) return "stop";
- if (found != null && handle(found)) return true;
- if (map.nofallthrough) return "stop";
-
- var fallthrough = map.fallthrough;
- if (fallthrough == null) return false;
- if (Object.prototype.toString.call(fallthrough) != "[object Array]")
- return lookup(fallthrough);
- for (var i = 0; i < fallthrough.length; ++i) {
- var done = lookup(fallthrough[i]);
- if (done) return done;
+ function normalizeKeyName(name) {
+ var parts = name.split(/-(?!$)/), name = parts[parts.length - 1];
+ var alt, ctrl, shift, cmd;
+ for (var i = 0; i < parts.length - 1; i++) {
+ var mod = parts[i];
+ if (/^(cmd|meta|m)$/i.test(mod)) cmd = true;
+ else if (/^a(lt)?$/i.test(mod)) alt = true;
+ else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true;
+ else if (/^s(hift)$/i.test(mod)) shift = true;
+ else throw new Error("Unrecognized modifier name: " + mod);
+ }
+ if (alt) name = "Alt-" + name;
+ if (ctrl) name = "Ctrl-" + name;
+ if (cmd) name = "Cmd-" + name;
+ if (shift) name = "Shift-" + name;
+ return name;
+ }
+
+ // This is a kludge to keep keymaps mostly working as raw objects
+ // (backwards compatibility) while at the same time support features
+ // like normalization and multi-stroke key bindings. It compiles a
+ // new normalized keymap, and then updates the old object to reflect
+ // this.
+ CodeMirror.normalizeKeyMap = function(keymap) {
+ var copy = {};
+ for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) {
+ var value = keymap[keyname];
+ if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue;
+ if (value == "...") { delete keymap[keyname]; continue; }
+
+ var keys = map(keyname.split(" "), normalizeKeyName);
+ for (var i = 0; i < keys.length; i++) {
+ var val, name;
+ if (i == keys.length - 1) {
+ name = keys.join(" ");
+ val = value;
+ } else {
+ name = keys.slice(0, i + 1).join(" ");
+ val = "...";
+ }
+ var prev = copy[name];
+ if (!prev) copy[name] = val;
+ else if (prev != val) throw new Error("Inconsistent bindings for " + name);
}
- return false;
+ delete keymap[keyname];
}
+ for (var prop in copy) keymap[prop] = copy[prop];
+ return keymap;
+ };
+
+ var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) {
+ map = getKeyMap(map);
+ var found = map.call ? map.call(key, context) : map[key];
+ if (found === false) return "nothing";
+ if (found === "...") return "multi";
+ if (found != null && handle(found)) return "handled";
- for (var i = 0; i < maps.length; ++i) {
- var done = lookup(maps[i]);
- if (done) return done != "stop";
+ if (map.fallthrough) {
+ if (Object.prototype.toString.call(map.fallthrough) != "[object Array]")
+ return lookupKey(key, map.fallthrough, handle, context);
+ for (var i = 0; i < map.fallthrough.length; i++) {
+ var result = lookupKey(key, map.fallthrough[i], handle, context);
+ if (result) return result;
+ }
}
};
// Modifier key presses don't count as 'real' key presses for the
// purpose of keymap fallthrough.
- var isModifierKey = CodeMirror.isModifierKey = function(event) {
- var name = keyNames[event.keyCode];
+ var isModifierKey = CodeMirror.isModifierKey = function(value) {
+ var name = typeof value == "string" ? value : keyNames[value.keyCode];
return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
};
// Look up the name of a key as indicated by an event object.
var keyName = CodeMirror.keyName = function(event, noShift) {
if (presto && event.keyCode == 34 && event["char"]) return false;
- var name = keyNames[event.keyCode];
+ var base = keyNames[event.keyCode], name = base;
if (name == null || event.altGraphKey) return false;
- if (event.altKey) name = "Alt-" + name;
- if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
- if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
- if (!noShift && event.shiftKey) name = "Shift-" + name;
+ if (event.altKey && base != "Alt") name = "Alt-" + name;
+ if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name;
+ if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name;
+ if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name;
return name;
};
+ function getKeyMap(val) {
+ return typeof val == "string" ? keyMap[val] : val;
+ }
+
// FROMTEXTAREA
CodeMirror.fromTextArea = function(textarea, options) {
- if (!options) options = {};
+ options = options ? copyObj(options) : {};
options.value = textarea.value;
- if (!options.tabindex && textarea.tabindex)
- options.tabindex = textarea.tabindex;
+ if (!options.tabindex && textarea.tabIndex)
+ options.tabindex = textarea.tabIndex;
if (!options.placeholder && textarea.placeholder)
options.placeholder = textarea.placeholder;
// Set autofocus to true if this textarea is focused, or if it has
@@ -4671,22 +5980,26 @@
}
}
+ options.finishInit = function(cm) {
+ cm.save = save;
+ cm.getTextArea = function() { return textarea; };
+ cm.toTextArea = function() {
+ cm.toTextArea = isNaN; // Prevent this from being ran twice
+ save();
+ textarea.parentNode.removeChild(cm.getWrapperElement());
+ textarea.style.display = "";
+ if (textarea.form) {
+ off(textarea.form, "submit", save);
+ if (typeof textarea.form.submit == "function")
+ textarea.form.submit = realSubmit;
+ }
+ };
+ };
+
textarea.style.display = "none";
var cm = CodeMirror(function(node) {
textarea.parentNode.insertBefore(node, textarea.nextSibling);
}, options);
- cm.save = save;
- cm.getTextArea = function() { return textarea; };
- cm.toTextArea = function() {
- save();
- textarea.parentNode.removeChild(cm.getWrapperElement());
- textarea.style.display = "";
- if (textarea.form) {
- off(textarea.form, "submit", save);
- if (typeof textarea.form.submit == "function")
- textarea.form.submit = realSubmit;
- }
- };
return cm;
};
@@ -4779,10 +6092,13 @@
// marker continues beyond the start/end of the line. Markers have
// links back to the lines they currently touch.
+ var nextMarkerId = 0;
+
var TextMarker = CodeMirror.TextMarker = function(doc, type) {
this.lines = [];
this.type = type;
this.doc = doc;
+ this.id = ++nextMarkerId;
};
eventMixin(TextMarker);
@@ -4826,6 +6142,7 @@
}
if (cm) signalLater(cm, "markerCleared", cm, this);
if (withOp) endOperation(cm);
+ if (this.parent) this.parent.clear();
};
// Find the position of the marker in the document. Returns a {from,
@@ -4905,7 +6222,7 @@
if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
var marker = new TextMarker(doc, type), diff = cmp(from, to);
- if (options) copyObj(options, marker);
+ if (options) copyObj(options, marker, false);
// Don't connect empty markers unless clearWhenEmpty is false
if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
return marker;
@@ -4913,7 +6230,7 @@
// Showing up as a widget implies collapsed (widget replaces text)
marker.collapsed = true;
marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget");
- if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true;
+ if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true");
if (options.insertLeft) marker.widgetNode.insertLeft = true;
}
if (marker.collapsed) {
@@ -4957,7 +6274,7 @@
if (updateMaxLine) cm.curOp.updateMaxLine = true;
if (marker.collapsed)
regChange(cm, from.line, to.line + 1);
- else if (marker.className || marker.title || marker.startStyle || marker.endStyle)
+ else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css)
for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text");
if (marker.atomic) reCheckSelection(cm.doc);
signalLater(cm, "markerAdded", cm, marker);
@@ -4973,10 +6290,8 @@
var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) {
this.markers = markers;
this.primary = primary;
- for (var i = 0, me = this; i < markers.length; ++i) {
+ for (var i = 0; i < markers.length; ++i)
markers[i].parent = this;
- on(markers[i], "clear", function(){me.clear();});
- }
};
eventMixin(SharedTextMarker);
@@ -5006,6 +6321,37 @@
return new SharedTextMarker(markers, primary);
}
+ function findSharedMarkers(doc) {
+ return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())),
+ function(m) { return m.parent; });
+ }
+
+ function copySharedMarkers(doc, markers) {
+ for (var i = 0; i < markers.length; i++) {
+ var marker = markers[i], pos = marker.find();
+ var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);
+ if (cmp(mFrom, mTo)) {
+ var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);
+ marker.markers.push(subMark);
+ subMark.parent = marker;
+ }
+ }
+ }
+
+ function detachSharedMarkers(markers) {
+ for (var i = 0; i < markers.length; i++) {
+ var marker = markers[i], linked = [marker.primary.doc];;
+ linkedDocs(marker.primary.doc, function(d) { linked.push(d); });
+ for (var j = 0; j < marker.markers.length; j++) {
+ var subMarker = marker.markers[j];
+ if (indexOf(linked, subMarker.doc) == -1) {
+ subMarker.parent = null;
+ marker.markers.splice(j--, 1);
+ }
+ }
+ }
+ }
+
// TEXTMARKER SPANS
function MarkedSpan(marker, from, to) {
@@ -5068,6 +6414,7 @@
// spans partially within the change. Returns an array of span
// arrays with one element for each line in (after) the change.
function stretchSpansOverChange(doc, change) {
+ if (change.full) return null;
var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
if (!oldFirst && !oldLast) return null;
@@ -5255,8 +6602,8 @@
var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
- if (fromCmp <= 0 && (cmp(found.to, from) || extraRight(sp.marker) - extraLeft(marker)) > 0 ||
- fromCmp >= 0 && (cmp(found.from, to) || extraLeft(sp.marker) - extraRight(marker)) < 0)
+ if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||
+ fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))
return true;
}
}
@@ -5335,10 +6682,10 @@
// Line widgets are block elements displayed above or below a line.
- var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
+ var LineWidget = CodeMirror.LineWidget = function(doc, node, options) {
if (options) for (var opt in options) if (options.hasOwnProperty(opt))
this[opt] = options[opt];
- this.cm = cm;
+ this.doc = doc;
this.node = node;
};
eventMixin(LineWidget);
@@ -5349,46 +6696,55 @@
}
LineWidget.prototype.clear = function() {
- var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
+ var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
if (no == null || !ws) return;
for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
if (!ws.length) line.widgets = null;
var height = widgetHeight(this);
- runInOp(cm, function() {
+ updateLineHeight(line, Math.max(0, line.height - height));
+ if (cm) runInOp(cm, function() {
adjustScrollWhenAboveVisible(cm, line, -height);
regLineChange(cm, no, "widget");
- updateLineHeight(line, Math.max(0, line.height - height));
});
};
LineWidget.prototype.changed = function() {
- var oldH = this.height, cm = this.cm, line = this.line;
+ var oldH = this.height, cm = this.doc.cm, line = this.line;
this.height = null;
var diff = widgetHeight(this) - oldH;
if (!diff) return;
- runInOp(cm, function() {
+ updateLineHeight(line, line.height + diff);
+ if (cm) runInOp(cm, function() {
cm.curOp.forceUpdate = true;
adjustScrollWhenAboveVisible(cm, line, diff);
- updateLineHeight(line, line.height + diff);
});
};
function widgetHeight(widget) {
if (widget.height != null) return widget.height;
- if (!contains(document.body, widget.node))
- removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
- return widget.height = widget.node.offsetHeight;
- }
-
- function addLineWidget(cm, handle, node, options) {
- var widget = new LineWidget(cm, node, options);
- if (widget.noHScroll) cm.display.alignWidgets = true;
- changeLine(cm, handle, "widget", function(line) {
+ var cm = widget.doc.cm;
+ if (!cm) return 0;
+ if (!contains(document.body, widget.node)) {
+ var parentStyle = "position: relative;";
+ if (widget.coverGutter)
+ parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;";
+ if (widget.noHScroll)
+ parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;";
+ removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
+ }
+ return widget.height = widget.node.parentNode.offsetHeight;
+ }
+
+ function addLineWidget(doc, handle, node, options) {
+ var widget = new LineWidget(doc, node, options);
+ var cm = doc.cm;
+ if (cm && widget.noHScroll) cm.display.alignWidgets = true;
+ changeLine(doc, handle, "widget", function(line) {
var widgets = line.widgets || (line.widgets = []);
if (widget.insertAt == null) widgets.push(widget);
else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
widget.line = line;
- if (!lineIsHidden(cm.doc, line)) {
- var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
+ if (cm && !lineIsHidden(doc, line)) {
+ var aboveVisible = heightAtLine(line) < doc.scrollTop;
updateLineHeight(line, line.height + widgetHeight(widget));
if (aboveVisible) addToScrollPos(cm, null, widget.height);
cm.curOp.forceUpdate = true;
@@ -5430,13 +6786,66 @@
detachMarkedSpans(line);
}
+ function extractLineClasses(type, output) {
+ if (type) for (;;) {
+ var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/);
+ if (!lineClass) break;
+ type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);
+ var prop = lineClass[1] ? "bgClass" : "textClass";
+ if (output[prop] == null)
+ output[prop] = lineClass[2];
+ else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop]))
+ output[prop] += " " + lineClass[2];
+ }
+ return type;
+ }
+
+ function callBlankLine(mode, state) {
+ if (mode.blankLine) return mode.blankLine(state);
+ if (!mode.innerMode) return;
+ var inner = CodeMirror.innerMode(mode, state);
+ if (inner.mode.blankLine) return inner.mode.blankLine(inner.state);
+ }
+
+ function readToken(mode, stream, state, inner) {
+ for (var i = 0; i < 10; i++) {
+ if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode;
+ var style = mode.token(stream, state);
+ if (stream.pos > stream.start) return style;
+ }
+ throw new Error("Mode " + mode.name + " failed to advance stream.");
+ }
+
+ // Utility for getTokenAt and getLineTokens
+ function takeToken(cm, pos, precise, asArray) {
+ function getObj(copy) {
+ return {start: stream.start, end: stream.pos,
+ string: stream.current(),
+ type: style || null,
+ state: copy ? copyState(doc.mode, state) : state};
+ }
+
+ var doc = cm.doc, mode = doc.mode, style;
+ pos = clipPos(doc, pos);
+ var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise);
+ var stream = new StringStream(line.text, cm.options.tabSize), tokens;
+ if (asArray) tokens = [];
+ while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
+ stream.start = stream.pos;
+ style = readToken(mode, stream, state);
+ if (asArray) tokens.push(getObj(true));
+ }
+ return asArray ? tokens : getObj();
+ }
+
// Run the given mode's parser over a line, calling f for each token.
- function runMode(cm, text, mode, state, f, forceToEnd) {
+ function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) {
var flattenSpans = mode.flattenSpans;
if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
var curStart = 0, curStyle = null;
var stream = new StringStream(text, cm.options.tabSize), style;
- if (text == "" && mode.blankLine) mode.blankLine(state);
+ var inner = cm.options.addModeClass && [null];
+ if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses);
while (!stream.eol()) {
if (stream.pos > cm.options.maxHighlightLength) {
flattenSpans = false;
@@ -5444,21 +6853,26 @@
stream.pos = text.length;
style = null;
} else {
- style = mode.token(stream, state);
+ style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses);
}
- if (cm.options.addModeClass) {
- var mName = CodeMirror.innerMode(mode, state).mode.name;
+ if (inner) {
+ var mName = inner[0].name;
if (mName) style = "m-" + (style ? mName + " " + style : mName);
}
if (!flattenSpans || curStyle != style) {
- if (curStart < stream.start) f(stream.start, curStyle);
- curStart = stream.start; curStyle = style;
+ while (curStart < stream.start) {
+ curStart = Math.min(stream.start, curStart + 5000);
+ f(curStart, curStyle);
+ }
+ curStyle = style;
}
stream.start = stream.pos;
}
while (curStart < stream.pos) {
- // Webkit seems to refuse to render text nodes longer than 57444 characters
- var pos = Math.min(stream.pos, curStart + 50000);
+ // Webkit seems to refuse to render text nodes longer than 57444
+ // characters, and returns inaccurate measurements in nodes
+ // starting around 5000 chars.
+ var pos = Math.min(stream.pos, curStart + 5000);
f(pos, curStyle);
curStart = pos;
}
@@ -5471,11 +6885,11 @@
function highlightLine(cm, line, state, forceToEnd) {
// A styles array always starts with a number identifying the
// mode/overlays that it is based on (for easy invalidation).
- var st = [cm.state.modeGen];
+ var st = [cm.state.modeGen], lineClasses = {};
// Compute the base array of styles
runMode(cm, line.text, cm.doc.mode, state, function(end, style) {
st.push(end, style);
- }, forceToEnd);
+ }, lineClasses, forceToEnd);
// Run overlays, adjust style array.
for (var o = 0; o < cm.state.overlays.length; ++o) {
@@ -5492,23 +6906,30 @@
}
if (!style) return;
if (overlay.opaque) {
- st.splice(start, i - start, end, style);
+ st.splice(start, i - start, end, "cm-overlay " + style);
i = start + 2;
} else {
for (; start < i; start += 2) {
var cur = st[start+1];
- st[start+1] = cur ? cur + " " + style : style;
+ st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
}
}
- });
+ }, lineClasses);
}
- return st;
+ return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null};
}
- function getLineStyles(cm, line) {
- if (!line.styles || line.styles[0] != cm.state.modeGen)
- line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
+ function getLineStyles(cm, line, updateFrontier) {
+ if (!line.styles || line.styles[0] != cm.state.modeGen) {
+ var state = getStateBefore(cm, lineNo(line));
+ var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state);
+ line.stateAfter = state;
+ line.styles = result.styles;
+ if (result.classes) line.styleClasses = result.classes;
+ else if (line.styleClasses) line.styleClasses = null;
+ if (updateFrontier === cm.doc.frontier) cm.doc.frontier++;
+ }
return line.styles;
}
@@ -5519,9 +6940,9 @@
var mode = cm.doc.mode;
var stream = new StringStream(text, cm.options.tabSize);
stream.start = stream.pos = startAt || 0;
- if (text == "" && mode.blankLine) mode.blankLine(state);
- while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
- mode.token(stream, state);
+ if (text == "") callBlankLine(mode, state);
+ while (!stream.eol()) {
+ readToken(mode, stream, state);
stream.start = stream.pos;
}
}
@@ -5530,20 +6951,9 @@
// containing one or more styles) to a CSS style. This is cached,
// and also looks for line-wide styles.
var styleToClassCache = {}, styleToClassCacheWithMode = {};
- function interpretTokenStyle(style, builder) {
- if (!style) return null;
- for (;;) {
- var lineClass = style.match(/(?:^|\s+)line-(background-)?(\S+)/);
- if (!lineClass) break;
- style = style.slice(0, lineClass.index) + style.slice(lineClass.index + lineClass[0].length);
- var prop = lineClass[1] ? "bgClass" : "textClass";
- if (builder[prop] == null)
- builder[prop] = lineClass[2];
- else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop]))
- builder[prop] += " " + lineClass[2];
- }
- if (/^\s*$/.test(style)) return null;
- var cache = builder.cm.options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
+ function interpretTokenStyle(style, options) {
+ if (!style || /^\s*$/.test(style)) return null;
+ var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
return cache[style] ||
(cache[style] = style.replace(/\S+/g, "cm-$&"));
}
@@ -5558,7 +6968,10 @@
// is needed on Webkit to be able to get line-level bounding
// rectangles for it (in measureChar).
var content = elt("span", null, null, webkit ? "padding-right: .1px" : null);
- var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm};
+ var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content,
+ col: 0, pos: 0, cm: cm,
+ trailingSpace: false,
+ splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")};
lineView.measure = {};
// Iterate over the logical lines that make up this visual line.
@@ -5568,12 +6981,17 @@
builder.addToken = buildToken;
// Optionally wire in some hacks into the token-rendering
// algorithm, to deal with browser quirks.
- if ((ie || webkit) && cm.getOption("lineWrapping"))
- builder.addToken = buildTokenSplitSpaces(builder.addToken);
if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
builder.addToken = buildTokenBadBidi(builder.addToken, order);
builder.map = [];
- insertLineContent(line, builder, getLineStyles(cm, line));
+ var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line);
+ insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate));
+ if (line.styleClasses) {
+ if (line.styleClasses.bgClass)
+ builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "");
+ if (line.styleClasses.textClass)
+ builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "");
+ }
// Ensure at least a single node is present, for measuring.
if (builder.map.length == 0)
@@ -5589,26 +7007,38 @@
}
}
+ // See issue #2901
+ if (webkit) {
+ var last = builder.content.lastChild
+ if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab")))
+ builder.content.className = "cm-tab-wrap-hack";
+ }
+
signal(cm, "renderLine", cm, lineView.line, builder.pre);
+ if (builder.pre.className)
+ builder.textClass = joinClasses(builder.pre.className, builder.textClass || "");
+
return builder;
}
function defaultSpecialCharPlaceholder(ch) {
var token = elt("span", "\u2022", "cm-invalidchar");
token.title = "\\u" + ch.charCodeAt(0).toString(16);
+ token.setAttribute("aria-label", token.title);
return token;
}
// Build up the DOM representation for a single token, and add it to
// the line map. Takes care to render special characters separately.
- function buildToken(builder, text, style, startStyle, endStyle, title) {
+ function buildToken(builder, text, style, startStyle, endStyle, title, css) {
if (!text) return;
- var special = builder.cm.options.specialChars, mustWrap = false;
+ var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text
+ var special = builder.cm.state.specialChars, mustWrap = false;
if (!special.test(text)) {
builder.col += text.length;
- var content = document.createTextNode(text);
+ var content = document.createTextNode(displayText);
builder.map.push(builder.pos, builder.pos + text.length, content);
- if (ie_upto8) mustWrap = true;
+ if (ie && ie_version < 9) mustWrap = true;
builder.pos += text.length;
} else {
var content = document.createDocumentFragment(), pos = 0;
@@ -5617,8 +7047,8 @@
var m = special.exec(text);
var skipped = m ? m.index - pos : text.length - pos;
if (skipped) {
- var txt = document.createTextNode(text.slice(pos, pos + skipped));
- if (ie_upto8) content.appendChild(elt("span", [txt]));
+ var txt = document.createTextNode(displayText.slice(pos, pos + skipped));
+ if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
else content.appendChild(txt);
builder.map.push(builder.pos, builder.pos + skipped, txt);
builder.col += skipped;
@@ -5629,10 +7059,17 @@
if (m[0] == "\t") {
var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
+ txt.setAttribute("role", "presentation");
+ txt.setAttribute("cm-text", "\t");
builder.col += tabWidth;
+ } else if (m[0] == "\r" || m[0] == "\n") {
+ var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar"));
+ txt.setAttribute("cm-text", m[0]);
+ builder.col += 1;
} else {
var txt = builder.cm.options.specialCharPlaceholder(m[0]);
- if (ie_upto8) content.appendChild(elt("span", [txt]));
+ txt.setAttribute("cm-text", m[0]);
+ if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
else content.appendChild(txt);
builder.col += 1;
}
@@ -5640,33 +7077,35 @@
builder.pos++;
}
}
- if (style || startStyle || endStyle || mustWrap) {
+ builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32
+ if (style || startStyle || endStyle || mustWrap || css) {
var fullStyle = style || "";
if (startStyle) fullStyle += startStyle;
if (endStyle) fullStyle += endStyle;
- var token = elt("span", [content], fullStyle);
+ var token = elt("span", [content], fullStyle, css);
if (title) token.title = title;
return builder.content.appendChild(token);
}
builder.content.appendChild(content);
}
- function buildTokenSplitSpaces(inner) {
- function split(old) {
- var out = " ";
- for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
- out += " ";
- return out;
+ function splitSpaces(text, trailingBefore) {
+ if (text.length > 1 && !/ /.test(text)) return text
+ var spaceBefore = trailingBefore, result = ""
+ for (var i = 0; i < text.length; i++) {
+ var ch = text.charAt(i)
+ if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))
+ ch = "\u00a0"
+ result += ch
+ spaceBefore = ch == " "
}
- return function(builder, text, style, startStyle, endStyle, title) {
- inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
- };
+ return result
}
// Work around nonsense dimensions being reported for stretches of
// right-to-left text.
function buildTokenBadBidi(inner, order) {
- return function(builder, text, style, startStyle, endStyle, title) {
+ return function(builder, text, style, startStyle, endStyle, title, css) {
style = style ? style + " cm-force-border" : "cm-force-border";
var start = builder.pos, end = start + text.length;
for (;;) {
@@ -5675,8 +7114,8 @@
var part = order[i];
if (part.to > start && part.from <= start) break;
}
- if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title);
- inner(builder, text.slice(0, part.to - start), style, startStyle, null, title);
+ if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css);
+ inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css);
startStyle = null;
text = text.slice(part.to - start);
start = part.to;
@@ -5686,11 +7125,18 @@
function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
var widget = !ignoreWidget && marker.widgetNode;
+ if (widget) builder.map.push(builder.pos, builder.pos + size, widget);
+ if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
+ if (!widget)
+ widget = builder.content.appendChild(document.createElement("span"));
+ widget.setAttribute("cm-marker", marker.id);
+ }
if (widget) {
- builder.map.push(builder.pos, builder.pos + size, widget);
+ builder.cm.display.input.setUneditable(widget);
builder.content.appendChild(widget);
}
builder.pos += size;
+ builder.trailingSpace = false
}
// Outputs a number of spans to make up a line, taking highlighting
@@ -5699,39 +7145,48 @@
var spans = line.markedSpans, allText = line.text, at = 0;
if (!spans) {
for (var i = 1; i < styles.length; i+=2)
- builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder));
+ builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options));
return;
}
- var len = allText.length, pos = 0, i = 1, text = "", style;
+ var len = allText.length, pos = 0, i = 1, text = "", style, css;
var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
for (;;) {
if (nextChange == pos) { // Update current marker set
- spanStyle = spanEndStyle = spanStartStyle = title = "";
+ spanStyle = spanEndStyle = spanStartStyle = title = css = "";
collapsed = null; nextChange = Infinity;
- var foundBookmarks = [];
+ var foundBookmarks = [], endStyles
for (var j = 0; j < spans.length; ++j) {
var sp = spans[j], m = sp.marker;
- if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
- if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
+ if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
+ foundBookmarks.push(m);
+ } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {
+ if (sp.to != null && sp.to != pos && nextChange > sp.to) {
+ nextChange = sp.to;
+ spanEndStyle = "";
+ }
if (m.className) spanStyle += " " + m.className;
+ if (m.css) css = (css ? css + ";" : "") + m.css;
if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
- if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
+ if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to)
if (m.title && !title) title = m.title;
if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
collapsed = sp;
} else if (sp.from > pos && nextChange > sp.from) {
nextChange = sp.from;
}
- if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m);
}
+ if (endStyles) for (var j = 0; j < endStyles.length; j += 2)
+ if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j]
+
+ if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookmarks.length; ++j)
+ buildCollapsedSpan(builder, 0, foundBookmarks[j]);
if (collapsed && (collapsed.from || 0) == pos) {
buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
collapsed.marker, collapsed.from == null);
if (collapsed.to == null) return;
+ if (collapsed.to == pos) collapsed = false;
}
- if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
- buildCollapsedSpan(builder, 0, foundBookmarks[j]);
}
if (pos >= len) break;
@@ -5742,14 +7197,14 @@
if (!collapsed) {
var tokenText = end > upto ? text.slice(0, upto - pos) : text;
builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
- spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
+ spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css);
}
if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
pos = end;
spanStartStyle = "";
}
text = allText.slice(at, at = styles[i++]);
- style = interpretTokenStyle(styles[i++], builder);
+ style = interpretTokenStyle(styles[i++], builder.cm.options);
}
}
}
@@ -5771,17 +7226,24 @@
updateLine(line, text, spans, estimateHeight);
signalLater(line, "change", line, change);
}
+ function linesFor(start, end) {
+ for (var i = start, result = []; i < end; ++i)
+ result.push(new Line(text[i], spansFor(i), estimateHeight));
+ return result;
+ }
var from = change.from, to = change.to, text = change.text;
var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
// Adjust the line structure
- if (isWholeLineUpdate(doc, change)) {
+ if (change.full) {
+ doc.insert(0, linesFor(0, text.length));
+ doc.remove(text.length, doc.size - text.length);
+ } else if (isWholeLineUpdate(doc, change)) {
// This is a whole-line replace. Treated specially to make
// sure line objects move the way they are supposed to.
- for (var i = 0, added = []; i < text.length - 1; ++i)
- added.push(new Line(text[i], spansFor(i), estimateHeight));
+ var added = linesFor(0, text.length - 1);
update(lastLine, lastLine.text, lastSpans);
if (nlines) doc.remove(from.line, nlines);
if (added.length) doc.insert(from.line, added);
@@ -5789,8 +7251,7 @@
if (text.length == 1) {
update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
} else {
- for (var added = [], i = 1; i < text.length - 1; ++i)
- added.push(new Line(text[i], spansFor(i), estimateHeight));
+ var added = linesFor(1, text.length - 1);
added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
doc.insert(from.line + 1, added);
@@ -5801,8 +7262,7 @@
} else {
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
- for (var i = 1, added = []; i < text.length - 1; ++i)
- added.push(new Line(text[i], spansFor(i), estimateHeight));
+ var added = linesFor(1, text.length - 1);
if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
doc.insert(from.line + 1, added);
}
@@ -5912,13 +7372,16 @@
if (at <= sz) {
child.insertInner(at, lines, height);
if (child.lines && child.lines.length > 50) {
- while (child.lines.length > 50) {
- var spilled = child.lines.splice(child.lines.length - 25, 25);
- var newleaf = new LeafChunk(spilled);
- child.height -= newleaf.height;
- this.children.splice(i + 1, 0, newleaf);
- newleaf.parent = this;
+ // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.
+ // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.
+ var remaining = child.lines.length % 25 + 25
+ for (var pos = remaining; pos < child.lines.length;) {
+ var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));
+ child.height -= leaf.height;
+ this.children.splice(++i, 0, leaf);
+ leaf.parent = this;
}
+ child.lines = child.lines.slice(0, remaining);
this.maybeSpill();
}
break;
@@ -5938,7 +7401,7 @@
copy.parent = me;
me.children = [copy, sibling];
me = copy;
- } else {
+ } else {
me.size -= sibling.size;
me.height -= sibling.height;
var myIndex = indexOf(me.parent.children, me);
@@ -5962,8 +7425,8 @@
};
var nextDocId = 0;
- var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
- if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
+ var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) {
+ if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep);
if (firstLine == null) firstLine = 0;
BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
@@ -5977,8 +7440,10 @@
this.history = new History(null);
this.id = ++nextDocId;
this.modeOption = mode;
+ this.lineSep = lineSep;
+ this.extend = false;
- if (typeof text == "string") text = splitLines(text);
+ if (typeof text == "string") text = this.splitLines(text);
updateDoc(this, {from: start, to: start, text: text});
setSelection(this, simpleSelection(start), sel_dontScroll);
};
@@ -6008,12 +7473,12 @@
getValue: function(lineSep) {
var lines = getLines(this, this.first, this.first + this.size);
if (lineSep === false) return lines;
- return lines.join(lineSep || "\n");
+ return lines.join(lineSep || this.lineSeparator());
},
setValue: docMethodOp(function(code) {
var top = Pos(this.first, 0), last = this.first + this.size - 1;
makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
- text: splitLines(code), origin: "setValue"}, true);
+ text: this.splitLines(code), origin: "setValue", full: true}, true);
setSelection(this, simpleSelection(top));
}),
replaceRange: function(code, from, to, origin) {
@@ -6024,7 +7489,7 @@
getRange: function(from, to, lineSep) {
var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
if (lineSep === false) return lines;
- return lines.join(lineSep || "\n");
+ return lines.join(lineSep || this.lineSeparator());
},
getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
@@ -6064,10 +7529,11 @@
extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
}),
extendSelections: docMethodOp(function(heads, options) {
- extendSelections(this, clipPosArray(this, heads, options));
+ extendSelections(this, clipPosArray(this, heads), options);
}),
extendSelectionsBy: docMethodOp(function(f, options) {
- extendSelections(this, map(this.sel.ranges, f), options);
+ var heads = map(this.sel.ranges, f);
+ extendSelections(this, clipPosArray(this, heads), options);
}),
setSelections: docMethodOp(function(ranges, primary, options) {
if (!ranges.length) return;
@@ -6090,35 +7556,35 @@
lines = lines ? lines.concat(sel) : sel;
}
if (lineSep === false) return lines;
- else return lines.join(lineSep || "\n");
+ else return lines.join(lineSep || this.lineSeparator());
},
getSelections: function(lineSep) {
var parts = [], ranges = this.sel.ranges;
for (var i = 0; i < ranges.length; i++) {
var sel = getBetween(this, ranges[i].from(), ranges[i].to());
- if (lineSep !== false) sel = sel.join(lineSep || "\n");
+ if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator());
parts[i] = sel;
}
return parts;
},
- replaceSelection: docMethodOp(function(code, collapse, origin) {
+ replaceSelection: function(code, collapse, origin) {
var dup = [];
for (var i = 0; i < this.sel.ranges.length; i++)
dup[i] = code;
this.replaceSelections(dup, collapse, origin || "+input");
- }),
- replaceSelections: function(code, collapse, origin) {
+ },
+ replaceSelections: docMethodOp(function(code, collapse, origin) {
var changes = [], sel = this.sel;
for (var i = 0; i < sel.ranges.length; i++) {
var range = sel.ranges[i];
- changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin};
+ changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin};
}
var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
for (var i = changes.length - 1; i >= 0; i--)
makeChange(this, changes[i]);
if (newSel) setSelectionReplaceHistory(this, newSel);
else if (this.cm) ensureCursorVisible(this.cm);
- },
+ }),
undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
@@ -6140,7 +7606,7 @@
},
changeGeneration: function(forceSplit) {
if (forceSplit)
- this.history.lastOp = this.history.lastOrigin = null;
+ this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null;
return this.history.generation;
},
isClean: function (gen) {
@@ -6157,13 +7623,48 @@
hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
},
+ addLineClass: docMethodOp(function(handle, where, cls) {
+ return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) {
+ var prop = where == "text" ? "textClass"
+ : where == "background" ? "bgClass"
+ : where == "gutter" ? "gutterClass" : "wrapClass";
+ if (!line[prop]) line[prop] = cls;
+ else if (classTest(cls).test(line[prop])) return false;
+ else line[prop] += " " + cls;
+ return true;
+ });
+ }),
+ removeLineClass: docMethodOp(function(handle, where, cls) {
+ return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) {
+ var prop = where == "text" ? "textClass"
+ : where == "background" ? "bgClass"
+ : where == "gutter" ? "gutterClass" : "wrapClass";
+ var cur = line[prop];
+ if (!cur) return false;
+ else if (cls == null) line[prop] = null;
+ else {
+ var found = cur.match(classTest(cls));
+ if (!found) return false;
+ var end = found.index + found[0].length;
+ line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
+ }
+ return true;
+ });
+ }),
+
+ addLineWidget: docMethodOp(function(handle, node, options) {
+ return addLineWidget(this, handle, node, options);
+ }),
+ removeLineWidget: function(widget) { widget.clear(); },
+
markText: function(from, to, options) {
- return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
+ return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range");
},
setBookmark: function(pos, options) {
var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
insertLeft: options && options.insertLeft,
- clearWhenEmpty: false, shared: options && options.shared};
+ clearWhenEmpty: false, shared: options && options.shared,
+ handleMouseEvents: options && options.handleMouseEvents};
pos = clipPos(this, pos);
return markText(this, pos, pos, realOpts, "bookmark");
},
@@ -6178,16 +7679,17 @@
}
return markers;
},
- findMarks: function(from, to) {
+ findMarks: function(from, to, filter) {
from = clipPos(this, from); to = clipPos(this, to);
var found = [], lineNo = from.line;
this.iter(from.line, to.line + 1, function(line) {
var spans = line.markedSpans;
if (spans) for (var i = 0; i < spans.length; i++) {
var span = spans[i];
- if (!(lineNo == from.line && from.ch > span.to ||
- span.from == null && lineNo != from.line||
- lineNo == to.line && span.from > to.ch))
+ if (!(span.to != null && lineNo == from.line && from.ch >= span.to ||
+ span.from == null && lineNo != from.line ||
+ span.from != null && lineNo == to.line && span.from >= to.ch) &&
+ (!filter || filter(span.marker)))
found.push(span.marker.parent || span.marker);
}
++lineNo;
@@ -6205,9 +7707,9 @@
},
posFromIndex: function(off) {
- var ch, lineNo = this.first;
+ var ch, lineNo = this.first, sepSize = this.lineSeparator().length;
this.iter(function(line) {
- var sz = line.text.length + 1;
+ var sz = line.text.length + sepSize;
if (sz > off) { ch = off; return true; }
off -= sz;
++lineNo;
@@ -6218,14 +7720,16 @@
coords = clipPos(this, coords);
var index = coords.ch;
if (coords.line < this.first || coords.ch < 0) return 0;
+ var sepSize = this.lineSeparator().length;
this.iter(this.first, coords.line, function (line) {
- index += line.text.length + 1;
+ index += line.text.length + sepSize;
});
return index;
},
copy: function(copyHistory) {
- var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
+ var doc = new Doc(getLines(this, this.first, this.first + this.size),
+ this.modeOption, this.first, this.lineSep);
doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
doc.sel = this.sel;
doc.extend = false;
@@ -6241,10 +7745,11 @@
var from = this.first, to = this.first + this.size;
if (options.from != null && options.from > from) from = options.from;
if (options.to != null && options.to < to) to = options.to;
- var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
+ var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep);
if (options.sharedHist) copy.history = this.history;
(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
+ copySharedMarkers(copy, findSharedMarkers(this));
return copy;
},
unlinkDoc: function(other) {
@@ -6254,6 +7759,7 @@
if (link.doc != other) continue;
this.linked.splice(i, 1);
other.unlinkDoc(this);
+ detachSharedMarkers(findSharedMarkers(this));
break;
}
// If the histories were shared, split them again
@@ -6268,14 +7774,20 @@
iterLinkedDocs: function(f) {linkedDocs(this, f);},
getMode: function() {return this.mode;},
- getEditor: function() {return this.cm;}
+ getEditor: function() {return this.cm;},
+
+ splitLines: function(str) {
+ if (this.lineSep) return str.split(this.lineSep);
+ return splitLinesAuto(str);
+ },
+ lineSeparator: function() { return this.lineSep || "\n"; }
});
// Public alias.
Doc.prototype.eachLine = Doc.prototype.iter;
// Set up methods on CodeMirror's prototype to redirect to the editor's document.
- var dontDelegate = "iter insert remove copy getEditor".split(" ");
+ var dontDelegate = "iter insert remove copy getEditor constructor".split(" ");
for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
CodeMirror.prototype[prop] = (function(method) {
return function() {return method.apply(this.doc, arguments);};
@@ -6429,7 +7941,7 @@
// Used to track when changes can be merged into a single undo
// event
this.lastModTime = this.lastSelTime = 0;
- this.lastOp = null;
+ this.lastOp = this.lastSelOp = null;
this.lastOrigin = this.lastSelOrigin = null;
// Used by the isClean() method
this.generation = this.maxGeneration = startGen || 1;
@@ -6469,7 +7981,7 @@
}
// Register a change in the history. Merges changes that are within
- // a single operation, ore are close together with an origin that
+ // a single operation, or are close together with an origin that
// allows merging (starting with "+") into a single event.
function addChangeToHistory(doc, change, selAfter, opId) {
var hist = doc.history;
@@ -6507,7 +8019,7 @@
hist.done.push(selAfter);
hist.generation = ++hist.maxGeneration;
hist.lastModTime = hist.lastSelTime = time;
- hist.lastOp = opId;
+ hist.lastOp = hist.lastSelOp = opId;
hist.lastOrigin = hist.lastSelOrigin = change.origin;
if (!last) signal(doc, "historyAdded");
@@ -6533,7 +8045,7 @@
// the current, or the origins don't allow matching. Origins
// starting with * are always merged, those starting with + are
// merged when similar and close together in time.
- if (opId == hist.lastOp ||
+ if (opId == hist.lastSelOp ||
(origin && hist.lastSelOrigin == origin &&
(hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
@@ -6543,7 +8055,7 @@
hist.lastSelTime = +new Date;
hist.lastSelOrigin = origin;
- hist.lastOp = opId;
+ hist.lastSelOp = opId;
if (options && options.clearRedo !== false)
clearSelectionEvents(hist.undone);
}
@@ -6708,26 +8220,34 @@
}
};
+ var noHandlers = []
+ function getHandlers(emitter, type, copy) {
+ var arr = emitter._handlers && emitter._handlers[type]
+ if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers
+ else return arr || noHandlers
+ }
+
var off = CodeMirror.off = function(emitter, type, f) {
if (emitter.removeEventListener)
emitter.removeEventListener(type, f, false);
else if (emitter.detachEvent)
emitter.detachEvent("on" + type, f);
else {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
- for (var i = 0; i < arr.length; ++i)
- if (arr[i] == f) { arr.splice(i, 1); break; }
+ var handlers = getHandlers(emitter, type, false)
+ for (var i = 0; i < handlers.length; ++i)
+ if (handlers[i] == f) { handlers.splice(i, 1); break; }
}
};
var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
+ var handlers = getHandlers(emitter, type, true)
+ if (!handlers.length) return;
var args = Array.prototype.slice.call(arguments, 2);
- for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+ for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args);
};
+ var orphanDelayedCallbacks = null;
+
// Often, we want to signal events at a point where we are in the
// middle of some work, but don't want the handler to start calling
// other methods on the editor, which might be in an inconsistent
@@ -6735,25 +8255,26 @@
// signalLater looks whether there are any handlers, and schedules
// them to be executed when the last operation ends, or, if no
// operation is active, when a timeout fires.
- var delayedCallbacks, delayedCallbackDepth = 0;
function signalLater(emitter, type /*, values...*/) {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
- var args = Array.prototype.slice.call(arguments, 2);
- if (!delayedCallbacks) {
- ++delayedCallbackDepth;
- delayedCallbacks = [];
- setTimeout(fireDelayed, 0);
+ var arr = getHandlers(emitter, type, false)
+ if (!arr.length) return;
+ var args = Array.prototype.slice.call(arguments, 2), list;
+ if (operationGroup) {
+ list = operationGroup.delayedCallbacks;
+ } else if (orphanDelayedCallbacks) {
+ list = orphanDelayedCallbacks;
+ } else {
+ list = orphanDelayedCallbacks = [];
+ setTimeout(fireOrphanDelayed, 0);
}
function bnd(f) {return function(){f.apply(null, args);};};
for (var i = 0; i < arr.length; ++i)
- delayedCallbacks.push(bnd(arr[i]));
+ list.push(bnd(arr[i]));
}
- function fireDelayed() {
- --delayedCallbackDepth;
- var delayed = delayedCallbacks;
- delayedCallbacks = null;
+ function fireOrphanDelayed() {
+ var delayed = orphanDelayedCallbacks;
+ orphanDelayedCallbacks = null;
for (var i = 0; i < delayed.length; ++i) delayed[i]();
}
@@ -6761,13 +8282,22 @@
// registering a (non-DOM) handler on the editor for the event name,
// and preventDefault-ing the event in that handler.
function signalDOMEvent(cm, e, override) {
+ if (typeof e == "string")
+ e = {type: e, preventDefault: function() { this.defaultPrevented = true; }};
signal(cm, override || e.type, cm, e);
return e_defaultPrevented(e) || e.codemirrorIgnore;
}
+ function signalCursorActivity(cm) {
+ var arr = cm._handlers && cm._handlers.cursorActivity;
+ if (!arr) return;
+ var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
+ for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1)
+ set.push(arr[i]);
+ }
+
function hasHandler(emitter, type) {
- var arr = emitter._handlers && emitter._handlers[type];
- return arr && arr.length > 0;
+ return getHandlers(emitter, type).length > 0
}
// Add on and off methods to a constructor's prototype, to make
@@ -6780,7 +8310,7 @@
// MISC UTILITIES
// Number of pixels added to scroller and sizer to hide scrollbar
- var scrollerCutOff = 30;
+ var scrollerGap = 30;
// Returned or thrown by various protocols to signal 'I'm not
// handling this'.
@@ -6814,7 +8344,7 @@
// The inverse of countColumn -- find the offset that corresponds to
// a particular column.
- function findColumn(string, goal, tabSize) {
+ var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) {
for (var pos = 0, col = 0;;) {
var nextTab = string.indexOf("\t", pos);
if (nextTab == -1) nextTab = string.length;
@@ -6848,30 +8378,37 @@
if (array[i] == elt) return i;
return -1;
}
- if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); };
function map(array, f) {
var out = [];
for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
return out;
}
- if ([].map) map = function(array, f) { return array.map(f); };
+
+ function insertSorted(array, value, score) {
+ var pos = 0, priority = score(value)
+ while (pos < array.length && score(array[pos]) <= priority) pos++
+ array.splice(pos, 0, value)
+ }
+
+ function nothing() {}
function createObj(base, props) {
var inst;
if (Object.create) {
inst = Object.create(base);
} else {
- var ctor = function() {};
- ctor.prototype = base;
- inst = new ctor();
+ nothing.prototype = base;
+ inst = new nothing();
}
if (props) copyObj(props, inst);
return inst;
};
- function copyObj(obj, target) {
+ function copyObj(obj, target, overwrite) {
if (!target) target = {};
- for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
+ for (var prop in obj)
+ if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
+ target[prop] = obj[prop];
return target;
}
@@ -6880,11 +8417,16 @@
return function(){return f.apply(null, args);};
}
- var nonASCIISingleCaseWordChar = /[\u00df\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
- var isWordChar = CodeMirror.isWordChar = function(ch) {
+ var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
+ var isWordCharBasic = CodeMirror.isWordChar = function(ch) {
return /\w/.test(ch) || ch > "\x80" &&
(ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
};
+ function isWordChar(ch, helper) {
+ if (!helper) return isWordCharBasic(ch);
+ if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true;
+ return helper.test(ch);
+ }
function isEmpty(obj) {
for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
@@ -6911,15 +8453,16 @@
}
var range;
- if (document.createRange) range = function(node, start, end) {
+ if (document.createRange) range = function(node, start, end, endNode) {
var r = document.createRange();
- r.setEnd(node, end);
+ r.setEnd(endNode || node, end);
r.setStart(node, start);
return r;
};
else range = function(node, start, end) {
var r = document.body.createTextRange();
- r.moveToElementText(node.parentNode);
+ try { r.moveToElementText(node.parentNode); }
+ catch(e) { return r; }
r.collapse(true);
r.moveEnd("character", end);
r.moveStart("character", start);
@@ -6936,52 +8479,109 @@
return removeChildren(parent).appendChild(e);
}
- function contains(parent, child) {
+ var contains = CodeMirror.contains = function(parent, child) {
+ if (child.nodeType == 3) // Android browser always returns false when child is a textnode
+ child = child.parentNode;
if (parent.contains)
return parent.contains(child);
- while (child = child.parentNode)
+ do {
+ if (child.nodeType == 11) child = child.host;
if (child == parent) return true;
- }
+ } while (child = child.parentNode);
+ };
- function activeElt() { return document.activeElement; }
+ function activeElt() {
+ var activeElement = document.activeElement;
+ while (activeElement && activeElement.root && activeElement.root.activeElement)
+ activeElement = activeElement.root.activeElement;
+ return activeElement;
+ }
// Older versions of IE throws unspecified error when touching
// document.activeElement in some cases (during loading, in iframe)
- if (ie_upto10) activeElt = function() {
+ if (ie && ie_version < 11) activeElt = function() {
try { return document.activeElement; }
catch(e) { return document.body; }
};
+ function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); }
+ var rmClass = CodeMirror.rmClass = function(node, cls) {
+ var current = node.className;
+ var match = classTest(cls).exec(current);
+ if (match) {
+ var after = current.slice(match.index + match[0].length);
+ node.className = current.slice(0, match.index) + (after ? match[1] + after : "");
+ }
+ };
+ var addClass = CodeMirror.addClass = function(node, cls) {
+ var current = node.className;
+ if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls;
+ };
+ function joinClasses(a, b) {
+ var as = a.split(" ");
+ for (var i = 0; i < as.length; i++)
+ if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i];
+ return b;
+ }
+
+ // WINDOW-WIDE EVENTS
+
+ // These must be handled carefully, because naively registering a
+ // handler for each editor will cause the editors to never be
+ // garbage collected.
+
+ function forEachCodeMirror(f) {
+ if (!document.body.getElementsByClassName) return;
+ var byClass = document.body.getElementsByClassName("CodeMirror");
+ for (var i = 0; i < byClass.length; i++) {
+ var cm = byClass[i].CodeMirror;
+ if (cm) f(cm);
+ }
+ }
+
+ var globalsRegistered = false;
+ function ensureGlobalHandlers() {
+ if (globalsRegistered) return;
+ registerGlobalHandlers();
+ globalsRegistered = true;
+ }
+ function registerGlobalHandlers() {
+ // When the window resizes, we need to refresh active editors.
+ var resizeTimer;
+ on(window, "resize", function() {
+ if (resizeTimer == null) resizeTimer = setTimeout(function() {
+ resizeTimer = null;
+ forEachCodeMirror(onResize);
+ }, 100);
+ });
+ // When the window loses focus, we want to show the editor as blurred
+ on(window, "blur", function() {
+ forEachCodeMirror(onBlur);
+ });
+ }
+
// FEATURE DETECTION
// Detect drag-and-drop
var dragAndDrop = function() {
// There is *some* kind of drag-and-drop support in IE6-8, but I
// couldn't get it to work yet.
- if (ie_upto8) return false;
+ if (ie && ie_version < 9) return false;
var div = elt('div');
return "draggable" in div || "dragDrop" in div;
}();
- var knownScrollbarWidth;
- function scrollbarWidth(measure) {
- if (knownScrollbarWidth != null) return knownScrollbarWidth;
- var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
- removeChildrenAndAdd(measure, test);
- if (test.offsetWidth)
- knownScrollbarWidth = test.offsetHeight - test.clientHeight;
- return knownScrollbarWidth || 0;
- }
-
var zwspSupported;
function zeroWidthElement(measure) {
if (zwspSupported == null) {
var test = elt("span", "\u200b");
removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
if (measure.firstChild.offsetHeight != 0)
- zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_upto7;
+ zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8);
}
- if (zwspSupported) return elt("span", "\u200b");
- else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
+ var node = zwspSupported ? elt("span", "\u200b") :
+ elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
+ node.setAttribute("cm-text", "");
+ return node;
}
// Feature-detect IE's crummy client rect reporting for bidi text
@@ -6990,14 +8590,15 @@
if (badBidiRects != null) return badBidiRects;
var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
var r0 = range(txt, 0, 1).getBoundingClientRect();
- if (r0.left == r0.right) return false;
var r1 = range(txt, 1, 2).getBoundingClientRect();
+ removeChildren(measure);
+ if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780)
return badBidiRects = (r1.right - r0.right < 3);
}
// See if "".split is the broken IE version, if so, provide an
// alternative way to split lines.
- var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
+ var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
var pos = 0, result = [], l = string.length;
while (pos <= l) {
var nl = string.indexOf("\n", pos);
@@ -7032,16 +8633,27 @@
return typeof e.oncopy == "function";
})();
+ var badZoomedRects = null;
+ function hasBadZoomedRects(measure) {
+ if (badZoomedRects != null) return badZoomedRects;
+ var node = removeChildrenAndAdd(measure, elt("span", "x"));
+ var normal = node.getBoundingClientRect();
+ var fromRange = range(node, 0, 1).getBoundingClientRect();
+ return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1;
+ }
+
// KEY NAMES
- var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
- 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
- 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
- 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
- 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
- 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
- 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
- CodeMirror.keyNames = keyNames;
+ var keyNames = CodeMirror.keyNames = {
+ 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
+ 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
+ 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete",
+ 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
+ 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
+ 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"
+ };
(function() {
// Number keys
for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
@@ -7094,6 +8706,17 @@
var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
return Pos(lineN == null ? lineNo(line) : lineN, ch);
}
+ function lineStartSmart(cm, pos) {
+ var start = lineStart(cm, pos.line);
+ var line = getLine(cm.doc, start.line);
+ var order = getOrder(line);
+ if (!order || order[0].level == 0) {
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
+ var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
+ return Pos(start.line, inWS ? 0 : firstNonWS);
+ }
+ return start;
+ }
function compareBidiLevel(order, a, b) {
var linedir = order[0].level;
@@ -7324,6 +8947,8 @@
lst(order).to -= m[0].length;
order.push(new BidiSpan(0, len - m[0].length, len));
}
+ if (order[0].level == 2)
+ order.unshift(new BidiSpan(1, order[0].to, order[0].to));
if (order[0].level != lst(order).level)
order.push(new BidiSpan(order[0].level, len, len));
@@ -7333,7 +8958,7 @@
// THE END
- CodeMirror.version = "4.0.3";
+ CodeMirror.version = "5.19.0";
return CodeMirror;
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/clike.js b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/clike.js
index 0e61020845..a37921fdae 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/clike.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/clike.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -8,19 +11,63 @@
})(function(CodeMirror) {
"use strict";
+function Context(indented, column, type, info, align, prev) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.info = info;
+ this.align = align;
+ this.prev = prev;
+}
+function pushContext(state, col, type, info) {
+ var indent = state.indented;
+ if (state.context && state.context.type != "statement" && type != "statement")
+ indent = state.context.indented;
+ return state.context = new Context(indent, col, type, info, null, state.context);
+}
+function popContext(state) {
+ var t = state.context.type;
+ if (t == ")" || t == "]" || t == "}")
+ state.indented = state.context.indented;
+ return state.context = state.context.prev;
+}
+
+function typeBefore(stream, state, pos) {
+ if (state.prevToken == "variable" || state.prevToken == "variable-3") return true;
+ if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true;
+ if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true;
+}
+
+function isTopScope(context) {
+ for (;;) {
+ if (!context || context.type == "top") return true;
+ if (context.type == "}" && context.prev.info != "namespace") return false;
+ context = context.prev;
+ }
+}
+
CodeMirror.defineMode("clike", function(config, parserConfig) {
var indentUnit = config.indentUnit,
statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
dontAlignCalls = parserConfig.dontAlignCalls,
keywords = parserConfig.keywords || {},
+ types = parserConfig.types || {},
builtin = parserConfig.builtin || {},
blockKeywords = parserConfig.blockKeywords || {},
+ defKeywords = parserConfig.defKeywords || {},
atoms = parserConfig.atoms || {},
hooks = parserConfig.hooks || {},
- multiLineStrings = parserConfig.multiLineStrings;
- var isOperatorChar = /[+\-*&%=<>!?|\/]/;
+ multiLineStrings = parserConfig.multiLineStrings,
+ indentStatements = parserConfig.indentStatements !== false,
+ indentSwitch = parserConfig.indentSwitch !== false,
+ namespaceSeparator = parserConfig.namespaceSeparator,
+ isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/,
+ 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 || /[+\-*&%=<>!?|\/]/,
+ endStatement = parserConfig.endStatement || /^[;:,]$/;
- var curPunc;
+ var curPunc, isDefKeyword;
function tokenBase(stream, state) {
var ch = stream.next();
@@ -32,13 +79,14 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
- if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
+ if (isPunctuationChar.test(ch)) {
curPunc = ch;
return null;
}
- if (/\d/.test(ch)) {
- stream.eatWhile(/[\w\.]/);
- return "number";
+ if (numberStart.test(ch)) {
+ stream.backUp(1)
+ if (stream.match(number)) return "number"
+ stream.next()
}
if (ch == "/") {
if (stream.eat("*")) {
@@ -51,20 +99,25 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
}
if (isOperatorChar.test(ch)) {
- stream.eatWhile(isOperatorChar);
+ while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {}
return "operator";
}
- stream.eatWhile(/[\w\$_]/);
+ stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ if (namespaceSeparator) while (stream.match(namespaceSeparator))
+ stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+
var cur = stream.current();
- if (keywords.propertyIsEnumerable(cur)) {
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+ if (contains(keywords, cur)) {
+ if (contains(blockKeywords, cur)) curPunc = "newstatement";
+ if (contains(defKeywords, cur)) isDefKeyword = true;
return "keyword";
}
- if (builtin.propertyIsEnumerable(cur)) {
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+ if (contains(types, cur)) return "variable-3";
+ if (contains(builtin, cur)) {
+ if (contains(blockKeywords, cur)) curPunc = "newstatement";
return "builtin";
}
- if (atoms.propertyIsEnumerable(cur)) return "atom";
+ if (contains(atoms, cur)) return "atom";
return "variable";
}
@@ -93,24 +146,9 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "comment";
}
- function Context(indented, column, type, align, prev) {
- this.indented = indented;
- this.column = column;
- this.type = type;
- this.align = align;
- this.prev = prev;
- }
- function pushContext(state, col, type) {
- var indent = state.indented;
- if (state.context && state.context.type == "statement")
- indent = state.context.indented;
- return state.context = new Context(indent, col, type, null, state.context);
- }
- function popContext(state) {
- var t = state.context.type;
- if (t == ")" || t == "]" || t == "}")
- state.indented = state.context.indented;
- return state.context = state.context.prev;
+ function maybeEOL(stream, state) {
+ if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context))
+ state.typeAtEndOfLine = typeBefore(stream, state, stream.pos)
}
// Interface
@@ -119,9 +157,10 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
startState: function(basecolumn) {
return {
tokenize: null,
- context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
+ context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false),
indented: 0,
- startOfLine: true
+ startOfLine: true,
+ prevToken: null
};
},
@@ -132,13 +171,13 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
state.indented = stream.indentation();
state.startOfLine = true;
}
- if (stream.eatSpace()) return null;
- curPunc = null;
+ if (stream.eatSpace()) { maybeEOL(stream, state); return null; }
+ curPunc = isDefKeyword = null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style == "comment" || style == "meta") return style;
if (ctx.align == null) ctx.align = true;
- if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state);
+ if (endStatement.test(curPunc)) while (state.context.type == "statement") popContext(state);
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
@@ -148,24 +187,60 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
while (ctx.type == "statement") ctx = popContext(state);
}
else if (curPunc == ctx.type) popContext(state);
- else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))
- pushContext(state, stream.column(), "statement");
+ else if (indentStatements &&
+ (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") ||
+ (ctx.type == "statement" && curPunc == "newstatement"))) {
+ pushContext(state, stream.column(), "statement", stream.current());
+ }
+
+ if (style == "variable" &&
+ ((state.prevToken == "def" ||
+ (parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) &&
+ isTopScope(state.context) && stream.match(/^\s*\(/, false)))))
+ style = "def";
+
+ if (hooks.token) {
+ var result = hooks.token(stream, state, style);
+ if (result !== undefined) style = result;
+ }
+
+ if (style == "def" && parserConfig.styleDefs === false) style = "variable";
+
state.startOfLine = false;
+ state.prevToken = isDefKeyword ? "def" : style || curPunc;
+ maybeEOL(stream, state);
return style;
},
indent: function(state, textAfter) {
- if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
+ if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
+ if (parserConfig.dontIndentStatements)
+ while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info))
+ ctx = ctx.prev
+ if (hooks.indent) {
+ var hook = hooks.indent(state, ctx, textAfter);
+ if (typeof hook == "number") return hook
+ }
var closing = firstChar == ctx.type;
- if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
- else if (ctx.align && (!dontAlignCalls || ctx.type != ")")) return ctx.column + (closing ? 0 : 1);
- else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit;
- else return ctx.indented + (closing ? 0 : indentUnit);
+ var switchBlock = ctx.prev && ctx.prev.info == "switch";
+ if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) {
+ while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev
+ return ctx.indented
+ }
+ if (ctx.type == "statement")
+ return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
+ if (ctx.align && (!dontAlignCalls || ctx.type != ")"))
+ return ctx.column + (closing ? 0 : 1);
+ if (ctx.type == ")" && !closing)
+ return ctx.indented + statementIndentUnit;
+
+ return ctx.indented + (closing ? 0 : indentUnit) +
+ (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0);
},
- electricChars: "{}",
+ electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/,
blockCommentStart: "/*",
blockCommentEnd: "*/",
lineComment: "//",
@@ -173,39 +248,52 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
};
});
-(function() {
function words(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
- var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
- "double static else struct entry switch extern typedef float union for unsigned " +
- "goto while enum void const signed volatile";
+ function contains(words, word) {
+ if (typeof words === "function") {
+ return words(word);
+ } else {
+ return words.propertyIsEnumerable(word);
+ }
+ }
+ 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";
function cppHook(stream, state) {
- if (!state.startOfLine) return false;
- for (;;) {
- if (stream.skipTo("\\")) {
- stream.next();
- if (stream.eol()) {
- state.tokenize = cppHook;
- break;
- }
- } else {
- stream.skipToEnd();
- state.tokenize = null;
- break;
+ if (!state.startOfLine) return false
+ for (var ch, next = null; ch = stream.peek();) {
+ if (ch == "\\" && stream.match(/^.$/)) {
+ next = cppHook
+ break
+ } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) {
+ break
}
+ stream.next()
}
- return "meta";
+ state.tokenize = next
+ return "meta"
+ }
+
+ function pointerHook(_stream, state) {
+ if (state.prevToken == "variable-3") return "variable-3";
+ return false;
+ }
+
+ function cpp14Literal(stream) {
+ stream.eatWhile(/[\w\.']/);
+ return "number";
}
function cpp11StringHook(stream, state) {
stream.backUp(1);
// Raw strings.
if (stream.match(/(R|u8R|uR|UR|LR)/)) {
- var match = stream.match(/"(.{0,16})\(/);
+ var match = stream.match(/"([^\s\\()]{0,16})\(/);
if (!match) {
return false;
}
@@ -225,6 +313,11 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return false;
}
+ function cppLooksLikeConstructor(word) {
+ var lastTwo = /(\w+)::(\w+)$/.exec(word);
+ return lastTwo && lastTwo[1] == lastTwo[2];
+ }
+
// C#-style strings where "" escapes a quote.
function tokenAtString(stream, state) {
var next;
@@ -240,23 +333,25 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
// C++11 raw string literal is "( anything )", where
// can be a string up to 16 characters long.
function tokenRawString(stream, state) {
- var closingSequence = new RegExp(".*?\\)" + state.cpp11RawStringDelim + '"');
- var match = stream.match(closingSequence);
- if (match) {
+ // Escape characters that have special regex meanings.
+ var delim = state.cpp11RawStringDelim.replace(/[^\w\s]/g, '\\$&');
+ var match = stream.match(new RegExp(".*?\\)" + delim + '"'));
+ if (match)
state.tokenize = null;
- } else {
+ else
stream.skipToEnd();
- }
return "string";
}
function def(mimes, mode) {
+ if (typeof mimes == "string") mimes = [mimes];
var words = [];
function add(obj) {
if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop))
words.push(prop);
}
add(mode.keywords);
+ add(mode.types);
add(mode.builtin);
add(mode.atoms);
if (words.length) {
@@ -271,39 +366,74 @@ 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"),
- atoms: words("null"),
- hooks: {"#": cppHook},
+ defKeywords: words("struct"),
+ typeFirstDefinitions: true,
+ atoms: words("null true false"),
+ 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 bool explicit new " +
+ 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 " +
- "wchar_t alignas alignof constexpr decltype nullptr noexcept thread_local final " +
+ "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"),
+ typeFirstDefinitions: true,
atoms: words("true false null"),
+ dontIndentStatements: /^template$/,
hooks: {
"#": cppHook,
+ "*": pointerHook,
"u": cpp11StringHook,
"U": cpp11StringHook,
"L": cpp11StringHook,
- "R": cpp11StringHook
+ "R": cpp11StringHook,
+ "0": cpp14Literal,
+ "1": cpp14Literal,
+ "2": cpp14Literal,
+ "3": cpp14Literal,
+ "4": cpp14Literal,
+ "5": cpp14Literal,
+ "6": cpp14Literal,
+ "7": cpp14Literal,
+ "8": cpp14Literal,
+ "9": cpp14Literal,
+ token: function(stream, state, style) {
+ if (style == "variable" && stream.peek() == "(" &&
+ (state.prevToken == ";" || state.prevToken == null ||
+ state.prevToken == "}") &&
+ cppLooksLikeConstructor(stream.current()))
+ return "def";
+ }
},
+ namespaceSeparator: "::",
modeProps: {fold: ["brace", "include"]}
});
- CodeMirror.defineMIME("text/x-java", {
+
+ def("text/x-java", {
name: "clike",
- keywords: words("abstract assert boolean break byte case catch char class const continue default " +
- "do double else enum extends final finally float for goto if implements import " +
- "instanceof int interface long native new package private protected public " +
- "return short static strictfp super switch synchronized this throw throws transient " +
- "try void volatile while"),
+ keywords: words("abstract assert break case catch class const continue default " +
+ "do else enum extends final finally float 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"),
+ types: words("byte short int long float double boolean char void Boolean Byte Character Double Float " +
+ "Integer Long Number Object Short String StringBuffer StringBuilder Void"),
blockKeywords: words("catch class do else finally for if switch try while"),
+ defKeywords: words("class interface package enum"),
+ typeFirstDefinitions: true,
atoms: words("true false null"),
+ endStatement: /^[;:]$/,
+ number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
@@ -312,20 +442,23 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
},
modeProps: {fold: ["brace", "import"]}
});
- CodeMirror.defineMIME("text/x-csharp", {
+
+ def("text/x-csharp", {
name: "clike",
- keywords: words("abstract as base break case catch checked class const continue" +
+ keywords: words("abstract as async await base break case catch checked class const continue" +
" default delegate do else enum event explicit extern finally fixed for" +
" foreach goto if implicit in interface internal is lock namespace new" +
" operator out override params private protected public readonly ref return sealed" +
" sizeof stackalloc static struct switch this throw try typeof unchecked" +
" unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
" global group into join let orderby partial remove select set value var yield"),
+ types: words("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func" +
+ " Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32" +
+ " UInt64 bool byte char decimal double short int long object" +
+ " sbyte float string ushort uint ulong"),
blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
- builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" +
- " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" +
- " UInt64 bool byte char decimal double short int long object" +
- " sbyte float string ushort uint ulong"),
+ defKeywords: words("class interface namespace struct var"),
+ typeFirstDefinitions: true,
atoms: words("true false null"),
hooks: {
"@": function(stream, state) {
@@ -338,58 +471,149 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
}
});
- CodeMirror.defineMIME("text/x-scala", {
+
+ function tokenTripleString(stream, state) {
+ var escaped = false;
+ while (!stream.eol()) {
+ if (!escaped && stream.match('"""')) {
+ state.tokenize = null;
+ break;
+ }
+ escaped = stream.next() == "\\" && !escaped;
+ }
+ return "string";
+ }
+
+ def("text/x-scala", {
name: "clike",
keywords: words(
/* scala */
- "abstract case catch class def do else extends false final finally for forSome if " +
+ "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 " +
- "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " +
+ "sealed super this throw trait try type val var while with yield _ : = => <- <: " +
"<% >: # @ " +
/* package scala */
"assert assume require print println printf readLine readBoolean readByte readShort " +
"readChar readInt readLong readFloat readDouble " +
+ ":: #:: "
+ ),
+ types: words(
"AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
- "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " +
+ "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " +
"Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
"Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
- "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " +
+ "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " +
/* package java.lang */
"Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
"Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
"Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
"StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
-
-
),
+ multiLineStrings: true,
blockKeywords: words("catch class do else finally for forSome if match switch try while"),
+ defKeywords: words("class def object package trait type val var"),
atoms: words("true false null"),
+ indentStatements: false,
+ indentSwitch: false,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
return "meta";
+ },
+ '"': function(stream, state) {
+ if (!stream.match('""')) return false;
+ state.tokenize = tokenTripleString;
+ return state.tokenize(stream, state);
+ },
+ "'": function(stream) {
+ stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ return "atom";
+ },
+ "=": function(stream, state) {
+ var cx = state.context
+ if (cx.type == "}" && cx.align && stream.eat(">")) {
+ state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev)
+ return "operator"
+ } else {
+ return false
+ }
+ }
+ },
+ modeProps: {closeBrackets: {triples: '"'}}
+ });
+
+ function tokenKotlinString(tripleString){
+ return function (stream, state) {
+ var escaped = false, next, end = false;
+ while (!stream.eol()) {
+ if (!tripleString && !escaped && stream.match('"') ) {end = true; break;}
+ if (tripleString && stream.match('"""')) {end = true; break;}
+ next = stream.next();
+ if(!escaped && next == "$" && stream.match('{'))
+ stream.skipTo("}");
+ escaped = !escaped && next == "\\" && !tripleString;
}
+ if (end || !tripleString)
+ state.tokenize = null;
+ return "string";
}
+ }
+
+ def("text/x-kotlin", {
+ name: "clike",
+ keywords: words(
+ /*keywords*/
+ "package as typealias class interface this super val " +
+ "var fun for is in This throw return " +
+ "break continue object if else while do try when !in !is as? " +
+
+ /*soft keywords*/
+ "file import where by get set abstract enum open inner override private public internal " +
+ "protected catch finally out final vararg reified dynamic companion constructor init " +
+ "sealed field property receiver param sparam lateinit data inline noinline tailrec " +
+ "external annotation crossinline const operator infix"
+ ),
+ types: words(
+ /* package java.lang */
+ "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
+ "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
+ "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
+ "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
+ ),
+ intendSwitch: false,
+ indentStatements: false,
+ multiLineStrings: true,
+ blockKeywords: words("catch class do else finally for if where try while enum"),
+ defKeywords: words("class val var object package interface fun"),
+ atoms: words("true false null this"),
+ hooks: {
+ '"': function(stream, state) {
+ state.tokenize = tokenKotlinString(stream.match('""'));
+ return state.tokenize(stream, state);
+ }
+ },
+ modeProps: {closeBrackets: {triples: '"'}}
});
+
def(["x-shader/x-vertex", "x-shader/x-fragment"], {
name: "clike",
- keywords: words("float int bool void " +
- "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
- "mat2 mat3 mat4 " +
- "sampler1D sampler2D sampler3D samplerCube " +
- "sampler1DShadow sampler2DShadow" +
+ keywords: words("sampler1D sampler2D sampler3D samplerCube " +
+ "sampler1DShadow sampler2DShadow " +
"const attribute uniform varying " +
"break continue discard return " +
"for while do if else struct " +
"in out inout"),
+ types: words("float int bool void " +
+ "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
+ "mat2 mat3 mat4"),
blockKeywords: words("for while do if else struct"),
builtin: words("radians degrees sin cos tan asin acos atan " +
"pow exp log exp2 sqrt inversesqrt " +
- "abs sign floor ceil fract mod min max clamp mix step smootstep " +
+ "abs sign floor ceil fract mod min max clamp mix step smoothstep " +
"length distance dot cross normalize ftransform faceforward " +
"reflect refract matrixCompMult " +
"lessThan lessThanEqual greaterThan greaterThanEqual " +
@@ -406,12 +630,12 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " +
"gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " +
"gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " +
- "gl_FogCoord " +
+ "gl_FogCoord gl_PointCoord " +
"gl_Position gl_PointSize gl_ClipVertex " +
"gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " +
"gl_TexCoord gl_FogFragCoord " +
"gl_FragCoord gl_FrontFacing " +
- "gl_FragColor gl_FragData gl_FragDepth " +
+ "gl_FragData gl_FragDepth " +
"gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " +
"gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " +
"gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " +
@@ -429,9 +653,134 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " +
"gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " +
"gl_MaxDrawBuffers"),
+ indentSwitch: false,
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
-}());
+
+ def("text/x-nesc", {
+ name: "clike",
+ 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"),
+ atoms: words("null true false"),
+ hooks: {"#": cppHook},
+ modeProps: {fold: ["brace", "include"]}
+ });
+
+ 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"),
+ hooks: {
+ "@": function(stream) {
+ stream.eatWhile(/[\w\$]/);
+ return "keyword";
+ },
+ "#": cppHook,
+ indent: function(_state, ctx, textAfter) {
+ if (ctx.type == "statement" && /^@\w/.test(textAfter)) return ctx.indented
+ }
+ },
+ modeProps: {fold: "brace"}
+ });
+
+ 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),
+ blockKeywords: words("case catch class else for foreach if switch try while"),
+ defKeywords: words("function local class"),
+ typeFirstDefinitions: true,
+ atoms: words("true false null"),
+ hooks: {"#": cppHook},
+ modeProps: {fold: ["brace", "include"]}
+ });
+
+ // Ceylon Strings need to deal with interpolation
+ var stringTokenizer = null;
+ function tokenCeylonString(type) {
+ return function(stream, state) {
+ var escaped = false, next, end = false;
+ while (!stream.eol()) {
+ if (!escaped && stream.match('"') &&
+ (type == "single" || stream.match('""'))) {
+ end = true;
+ break;
+ }
+ if (!escaped && stream.match('``')) {
+ stringTokenizer = tokenCeylonString(type);
+ end = true;
+ break;
+ }
+ next = stream.next();
+ escaped = type == "single" && !escaped && next == "\\";
+ }
+ if (end)
+ state.tokenize = null;
+ return "string";
+ }
+ }
+
+ def("text/x-ceylon", {
+ name: "clike",
+ keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" +
+ " exists extends finally for function given if import in interface is let module new" +
+ " nonempty object of out outer package return satisfies super switch then this throw" +
+ " try value void while"),
+ types: function(word) {
+ // In Ceylon all identifiers that start with an uppercase are types
+ var first = word.charAt(0);
+ return (first === first.toUpperCase() && first !== first.toLowerCase());
+ },
+ blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"),
+ defKeywords: words("class dynamic function interface module object package value"),
+ builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" +
+ " native optional sealed see serializable shared suppressWarnings tagged throws variable"),
+ isPunctuationChar: /[\[\]{}\(\),;\:\.`]/,
+ isOperatorChar: /[+\-*&%=<>!?|^~:\/]/,
+ numberStart: /[\d#$]/,
+ number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,
+ multiLineStrings: true,
+ typeFirstDefinitions: true,
+ atoms: words("true false null larger smaller equal empty finished"),
+ indentSwitch: false,
+ styleDefs: false,
+ hooks: {
+ "@": function(stream) {
+ stream.eatWhile(/[\w\$_]/);
+ return "meta";
+ },
+ '"': function(stream, state) {
+ state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single");
+ return state.tokenize(stream, state);
+ },
+ '`': function(stream, state) {
+ if (!stringTokenizer || !stream.match('`')) return false;
+ state.tokenize = stringTokenizer;
+ stringTokenizer = null;
+ return state.tokenize(stream, state);
+ },
+ "'": function(stream) {
+ stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ return "atom";
+ },
+ token: function(_stream, state, style) {
+ if ((style == "variable" || style == "variable-3") &&
+ state.prevToken == ".") {
+ return "variable-2";
+ }
+ }
+ },
+ modeProps: {
+ fold: ["brace", "import"],
+ closeBrackets: {triples: '"'}
+ }
+ });
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/index.html b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/index.html
index d892fddbdb..45c670ae58 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/index.html
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/index.html
@@ -7,15 +7,17 @@
+
+
+Objective-C example
+
+
+
Java example
+Scala example
+
+
+
+Kotlin mode
+
+
+
+Ceylon mode
+
+
+
Simple mode that tries to handle C-like languages as well as it
@@ -194,7 +350,11 @@ public class Class implements MyInterface {
directives are recognized.
MIME types defined: text/x-csrc
- (C code), text/x-c++src
(C++
- code), text/x-java
(Java
- code), text/x-csharp
(C#).
+ (C), text/x-c++src
(C++), text/x-java
+ (Java), text/x-csharp
(C#),
+ text/x-objectivec
(Objective-C),
+ text/x-scala
(Scala), text/x-vertex
+ x-shader/x-fragment
(shader programs),
+ text/x-squirrel
(Squirrel) and
+ text/x-ceylon
(Ceylon)
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/scala.html b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/scala.html
index e9acc049b3..aa04cf0f04 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/scala.html
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/scala.html
@@ -10,12 +10,12 @@
-
+
CodeMirror
Language modes
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/test.js b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/test.js
new file mode 100644
index 0000000000..bea85b8693
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/clike/test.js
@@ -0,0 +1,55 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function() {
+ var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-c");
+ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
+
+ MT("indent",
+ "[variable-3 void] [def foo]([variable-3 void*] [variable a], [variable-3 int] [variable b]) {",
+ " [variable-3 int] [variable c] [operator =] [variable b] [operator +]",
+ " [number 1];",
+ " [keyword return] [operator *][variable a];",
+ "}");
+
+ MT("indent_switch",
+ "[keyword switch] ([variable x]) {",
+ " [keyword case] [number 10]:",
+ " [keyword return] [number 20];",
+ " [keyword default]:",
+ " [variable printf]([string \"foo %c\"], [variable x]);",
+ "}");
+
+ MT("def",
+ "[variable-3 void] [def foo]() {}",
+ "[keyword struct] [def bar]{}",
+ "[variable-3 int] [variable-3 *][def baz]() {}");
+
+ MT("def_new_line",
+ "::[variable std]::[variable SomeTerribleType][operator <][variable T][operator >]",
+ "[def SomeLongMethodNameThatDoesntFitIntoOneLine]([keyword const] [variable MyType][operator &] [variable param]) {}")
+
+ MT("double_block",
+ "[keyword for] (;;)",
+ " [keyword for] (;;)",
+ " [variable x][operator ++];",
+ "[keyword return];");
+
+ MT("preprocessor",
+ "[meta #define FOO 3]",
+ "[variable-3 int] [variable foo];",
+ "[meta #define BAR\\]",
+ "[meta 4]",
+ "[variable-3 unsigned] [variable-3 int] [variable bar] [operator =] [number 8];",
+ "[meta #include ][comment // comment]")
+
+
+ var mode_cpp = CodeMirror.getMode({indentUnit: 2}, "text/x-c++src");
+ function MTCPP(name) { test.mode(name, mode_cpp, Array.prototype.slice.call(arguments, 1)); }
+
+ MTCPP("cpp14_literal",
+ "[number 10'000];",
+ "[number 0b10'000];",
+ "[number 0x10'000];",
+ "[string '100000'];");
+})();
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/coffeescript.js b/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/coffeescript.js
index c6da8f2a29..adf2184fd7 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/coffeescript.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/coffeescript.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
/**
* Link to the project's GitHub page:
* https://github.com/pickhardt/coffeescript-codemirror-mode
@@ -12,17 +15,17 @@
})(function(CodeMirror) {
"use strict";
-CodeMirror.defineMode("coffeescript", function(conf) {
+CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
var ERRORCLASS = "error";
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b");
}
- var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?)/;
+ var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
- var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/;
+ var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/;
var wordOperators = wordRegexp(["and", "or", "not",
"is", "isnt", "in",
@@ -31,7 +34,7 @@ CodeMirror.defineMode("coffeescript", function(conf) {
"switch", "try", "catch", "finally", "class"];
var commonKeywords = ["break", "by", "continue", "debugger", "delete",
"do", "in", "of", "new", "return", "then",
- "this", "throw", "when", "until"];
+ "this", "@", "throw", "when", "until", "extends"];
var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
@@ -142,6 +145,8 @@ CodeMirror.defineMode("coffeescript", function(conf) {
}
}
+
+
// Handle operators and delimiters
if (stream.match(operators) || stream.match(wordOperators)) {
return "operator";
@@ -154,6 +159,10 @@ CodeMirror.defineMode("coffeescript", function(conf) {
return "atom";
}
+ if (stream.match(atProp) || state.prop && stream.match(identifiers)) {
+ return "property";
+ }
+
if (stream.match(keywords)) {
return "keyword";
}
@@ -162,10 +171,6 @@ CodeMirror.defineMode("coffeescript", function(conf) {
return "variable";
}
- if (stream.match(properties)) {
- return "property";
- }
-
// Handle non-detected items
stream.next();
return ERRORCLASS;
@@ -188,7 +193,7 @@ CodeMirror.defineMode("coffeescript", function(conf) {
}
}
if (singleline) {
- if (conf.mode.singleLineStringErrors) {
+ if (parserConf.singleLineStringErrors) {
outclass = ERRORCLASS;
} else {
state.tokenize = tokenBase;
@@ -214,7 +219,7 @@ CodeMirror.defineMode("coffeescript", function(conf) {
type = type || "coffee";
var offset = 0, align = false, alignOffset = null;
for (var scope = state.scope; scope; scope = scope.prev) {
- if (scope.type === "coffee") {
+ if (scope.type === "coffee" || scope.type == "}") {
offset = scope.offset + conf.indentUnit;
break;
}
@@ -262,24 +267,11 @@ CodeMirror.defineMode("coffeescript", function(conf) {
var style = state.tokenize(stream, state);
var current = stream.current();
- // Handle "." connected identifiers
- if (current === ".") {
- style = state.tokenize(stream, state);
- current = stream.current();
- if (/^\.[\w$]+$/.test(current)) {
- return "variable";
- } else {
- return ERRORCLASS;
- }
- }
-
// Handle scope changes.
if (current === "return") {
- state.dedent += 1;
+ state.dedent = true;
}
- if (((current === "->" || current === "=>") &&
- !state.lambda &&
- !stream.peek())
+ if (((current === "->" || current === "=>") && stream.eol())
|| style === "indent") {
indent(stream, state);
}
@@ -307,9 +299,10 @@ CodeMirror.defineMode("coffeescript", function(conf) {
if (state.scope.type == current)
state.scope = state.scope.prev;
}
- if (state.dedent > 0 && stream.eol() && state.scope.type == "coffee") {
- if (state.scope.prev) state.scope = state.scope.prev;
- state.dedent -= 1;
+ if (state.dedent && stream.eol()) {
+ if (state.scope.type == "coffee" && state.scope.prev)
+ state.scope = state.scope.prev;
+ state.dedent = false;
}
return style;
@@ -320,8 +313,7 @@ CodeMirror.defineMode("coffeescript", function(conf) {
return {
tokenize: tokenBase,
scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
- lastToken: null,
- lambda: false,
+ prop: false,
dedent: 0
};
},
@@ -331,12 +323,9 @@ CodeMirror.defineMode("coffeescript", function(conf) {
if (fillAlign && stream.sol()) fillAlign.align = false;
var style = tokenLexer(stream, state);
- if (fillAlign && style && style != "comment") fillAlign.align = true;
-
- state.lastToken = {style:style, content: stream.current()};
-
- if (stream.eol() && stream.lambda) {
- state.lambda = false;
+ if (style && style != "comment") {
+ if (fillAlign) fillAlign.align = true;
+ state.prop = style == "punctuation" && stream.current() == "."
}
return style;
@@ -361,5 +350,6 @@ CodeMirror.defineMode("coffeescript", function(conf) {
});
CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
+CodeMirror.defineMIME("text/coffeescript", "coffeescript");
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/index.html b/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/index.html
index 6e6fde52e4..93a5f4f309 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/index.html
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/coffeescript/index.html
@@ -9,12 +9,12 @@
-
+
CodeMirror
Language modes
@@ -735,6 +735,6 @@ wrapper::value = -> this._wrapped
MIME types defined: text/x-coffeescript
.
- The CoffeeScript mode was written by Jeff Pickhardt (license ).
+ The CoffeeScript mode was written by Jeff Pickhardt.
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/css.js b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/css.js
index f8fc5cea21..ea7bd01d84 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/css.js
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/css.js
@@ -1,3 +1,6 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
@@ -9,17 +12,23 @@
"use strict";
CodeMirror.defineMode("css", function(config, parserConfig) {
+ var inline = parserConfig.inline
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
var indentUnit = config.indentUnit,
tokenHooks = parserConfig.tokenHooks,
+ documentTypes = parserConfig.documentTypes || {},
mediaTypes = parserConfig.mediaTypes || {},
mediaFeatures = parserConfig.mediaFeatures || {},
+ mediaValueKeywords = parserConfig.mediaValueKeywords || {},
propertyKeywords = parserConfig.propertyKeywords || {},
+ nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
+ fontProperties = parserConfig.fontProperties || {},
+ counterDescriptors = parserConfig.counterDescriptors || {},
colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {},
- fontProperties = parserConfig.fontProperties || {},
- allowNested = parserConfig.allowNested;
+ allowNested = parserConfig.allowNested,
+ supportsAtComponent = parserConfig.supportsAtComponent === true;
var type, override;
function ret(style, tp) { type = tp; return style; }
@@ -53,7 +62,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
if (/[\d.]/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
- } else if (stream.match(/^[^-]+-/)) {
+ } else if (stream.match(/^-[\w\\\-]+/)) {
+ stream.eatWhile(/[\w\\\-]/);
+ if (stream.match(/^\s*:/, false))
+ return ret("variable-2", "variable-definition");
+ return ret("variable-2", "variable");
+ } else if (stream.match(/^\w+-/)) {
return ret("meta", "meta");
}
} else if (/[,+>*\/]/.test(ch)) {
@@ -62,7 +76,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return ret("qualifier", "qualifier");
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch);
- } else if (ch == "u" && stream.match("rl(")) {
+ } else if ((ch == "u" && stream.match(/rl(-prefix)?\(/)) ||
+ (ch == "d" && stream.match("omain(")) ||
+ (ch == "r" && stream.match("egexp("))) {
stream.backUp(1);
state.tokenize = tokenParenthesized;
return ret("property", "word");
@@ -91,7 +107,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
function tokenParenthesized(stream, state) {
stream.next(); // Must be '('
- if (!stream.match(/\s*[\"\']/, false))
+ if (!stream.match(/\s*[\"\')]/, false))
state.tokenize = tokenString(")");
else
state.tokenize = null;
@@ -106,13 +122,14 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
this.prev = prev;
}
- function pushContext(state, stream, type) {
- state.context = new Context(type, stream.indentation() + indentUnit, state.context);
+ function pushContext(state, stream, type, indent) {
+ state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
return type;
}
function popContext(state) {
- state.context = state.context.prev;
+ if (state.context.prev)
+ state.context = state.context.prev;
return state.context.type;
}
@@ -144,10 +161,15 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) {
return popContext(state);
- } else if (type == "@media") {
- return pushContext(state, stream, "media");
- } else if (type == "@font-face") {
- return "font_face_before";
+ } else if (supportsAtComponent && /@component/.test(type)) {
+ return pushContext(state, stream, "atComponentBlock");
+ } else if (/^@(-moz-)?document$/.test(type)) {
+ return pushContext(state, stream, "documentTypes");
+ } else if (/^@(media|supports|(-moz-)?document|import)$/.test(type)) {
+ return pushContext(state, stream, "atBlock");
+ } else if (/^@(font-face|counter-style)/.test(type)) {
+ state.stateArg = type;
+ return "restricted_atBlock_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
return "keyframes";
} else if (type && type.charAt(0) == "@") {
@@ -163,18 +185,22 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
} else if (type == ":") {
return "pseudo";
} else if (allowNested && type == "(") {
- return pushContext(state, stream, "params");
+ return pushContext(state, stream, "parens");
}
return state.context.type;
};
states.block = function(type, stream, state) {
if (type == "word") {
- if (propertyKeywords.hasOwnProperty(stream.current().toLowerCase())) {
+ var word = stream.current().toLowerCase();
+ if (propertyKeywords.hasOwnProperty(word)) {
override = "property";
return "maybeprop";
+ } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
+ override = "string-2";
+ return "maybeprop";
} else if (allowNested) {
- override = stream.match(/^\s*:/, false) ? "property" : "tag";
+ override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
return "block";
} else {
override += " error";
@@ -201,7 +227,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
if (type == "}" || type == "{") return popAndPass(type, stream, state);
if (type == "(") return pushContext(state, stream, "parens");
- if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
+ if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {
override += " error";
} else if (type == "word") {
wordAsValue(stream);
@@ -220,6 +246,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
states.parens = function(type, stream, state) {
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == ")") return popContext(state);
+ if (type == "(") return pushContext(state, stream, "parens");
+ if (type == "interpolation") return pushContext(state, stream, "interpolation");
+ if (type == "word") wordAsValue(stream);
return "parens";
};
@@ -231,47 +260,86 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return pass(type, stream, state);
};
- states.media = function(type, stream, state) {
- if (type == "(") return pushContext(state, stream, "media_parens");
- if (type == "}") return popAndPass(type, stream, state);
+ states.documentTypes = function(type, stream, state) {
+ if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
+ override = "tag";
+ return state.context.type;
+ } else {
+ return states.atBlock(type, stream, state);
+ }
+ };
+
+ states.atBlock = function(type, stream, state) {
+ if (type == "(") return pushContext(state, stream, "atBlock_parens");
+ if (type == "}" || type == ";") return popAndPass(type, stream, state);
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
+ if (type == "interpolation") return pushContext(state, stream, "interpolation");
+
if (type == "word") {
var word = stream.current().toLowerCase();
- if (word == "only" || word == "not" || word == "and")
+ if (word == "only" || word == "not" || word == "and" || word == "or")
override = "keyword";
else if (mediaTypes.hasOwnProperty(word))
override = "attribute";
else if (mediaFeatures.hasOwnProperty(word))
override = "property";
+ else if (mediaValueKeywords.hasOwnProperty(word))
+ override = "keyword";
+ else if (propertyKeywords.hasOwnProperty(word))
+ override = "property";
+ else if (nonStandardPropertyKeywords.hasOwnProperty(word))
+ override = "string-2";
+ else if (valueKeywords.hasOwnProperty(word))
+ override = "atom";
+ else if (colorKeywords.hasOwnProperty(word))
+ override = "keyword";
else
override = "error";
}
return state.context.type;
};
- states.media_parens = function(type, stream, state) {
+ states.atComponentBlock = function(type, stream, state) {
+ if (type == "}")
+ return popAndPass(type, stream, state);
+ if (type == "{")
+ return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
+ if (type == "word")
+ override = "error";
+ return state.context.type;
+ };
+
+ states.atBlock_parens = function(type, stream, state) {
if (type == ")") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
- return states.media(type, stream, state);
+ return states.atBlock(type, stream, state);
};
- states.font_face_before = function(type, stream, state) {
+ states.restricted_atBlock_before = function(type, stream, state) {
if (type == "{")
- return pushContext(state, stream, "font_face");
+ return pushContext(state, stream, "restricted_atBlock");
+ if (type == "word" && state.stateArg == "@counter-style") {
+ override = "variable";
+ return "restricted_atBlock_before";
+ }
return pass(type, stream, state);
};
- states.font_face = function(type, stream, state) {
- if (type == "}") return popContext(state);
+ states.restricted_atBlock = function(type, stream, state) {
+ if (type == "}") {
+ state.stateArg = null;
+ return popContext(state);
+ }
if (type == "word") {
- if (!fontProperties.hasOwnProperty(stream.current().toLowerCase()))
+ if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
+ (state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
override = "error";
else
override = "property";
return "maybeprop";
}
- return "font_face";
+ return "restricted_atBlock";
};
states.keyframes = function(type, stream, state) {
@@ -291,22 +359,17 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
states.interpolation = function(type, stream, state) {
if (type == "}") return popContext(state);
if (type == "{" || type == ";") return popAndPass(type, stream, state);
- if (type != "variable") override = "error";
+ if (type == "word") override = "variable";
+ else if (type != "variable" && type != "(" && type != ")") override = "error";
return "interpolation";
};
- states.params = function(type, stream, state) {
- if (type == ")") return popContext(state);
- if (type == "{" || type == "}") return popAndPass(type, stream, state);
- if (type == "word") wordAsValue(stream);
- return "params";
- };
-
return {
startState: function(base) {
return {tokenize: null,
- state: "top",
- context: new Context("top", base || 0, null)};
+ state: inline ? "block" : "top",
+ stateArg: null,
+ context: new Context(inline ? "block" : "top", base || 0, null)};
},
token: function(stream, state) {
@@ -324,13 +387,19 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
indent: function(state, textAfter) {
var cx = state.context, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
- if (cx.type == "prop" && ch == "}") cx = cx.prev;
- if (cx.prev &&
- (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "font_face") ||
- ch == ")" && (cx.type == "parens" || cx.type == "params" || cx.type == "media_parens") ||
- ch == "{" && (cx.type == "at" || cx.type == "media"))) {
- indent = cx.indent - indentUnit;
- cx = cx.prev;
+ if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
+ if (cx.prev) {
+ if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
+ cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
+ // Resume indentation from parent context.
+ cx = cx.prev;
+ indent = cx.indent;
+ } else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
+ ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
+ // Dedent relative to current context.
+ indent = Math.max(0, cx.indent - indentUnit);
+ cx = cx.prev;
+ }
}
return indent;
},
@@ -350,6 +419,10 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return keys;
}
+ var documentTypes_ = [
+ "domain", "regexp", "url", "url-prefix"
+ ], documentTypes = keySet(documentTypes_);
+
var mediaTypes_ = [
"all", "aural", "braille", "handheld", "print", "projection", "screen",
"tty", "tv", "embossed"
@@ -363,17 +436,24 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
"max-color", "color-index", "min-color-index", "max-color-index",
"monochrome", "min-monochrome", "max-monochrome", "resolution",
- "min-resolution", "max-resolution", "scan", "grid"
+ "min-resolution", "max-resolution", "scan", "grid", "orientation",
+ "device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
+ "pointer", "any-pointer", "hover", "any-hover"
], mediaFeatures = keySet(mediaFeatures_);
+ var mediaValueKeywords_ = [
+ "landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
+ "interlace", "progressive"
+ ], mediaValueKeywords = keySet(mediaValueKeywords_);
+
var propertyKeywords_ = [
"align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-fill-mode",
"animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "appearance", "azimuth", "backface-visibility",
- "background", "background-attachment", "background-clip", "background-color",
- "background-image", "background-origin", "background-position",
+ "background", "background-attachment", "background-blend-mode", "background-clip",
+ "background-color", "background-image", "background-origin", "background-position",
"background-repeat", "background-size", "baseline-shift", "binding",
"bleed", "bookmark-label", "bookmark-level", "bookmark-state",
"bookmark-target", "border", "border-bottom", "border-bottom-color",
@@ -404,9 +484,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
"font-variant-ligatures", "font-variant-numeric", "font-variant-position",
"font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
- "grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end",
- "grid-column-start", "grid-row", "grid-row-end", "grid-row-start",
- "grid-template", "grid-template-areas", "grid-template-columns",
+ "grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap",
+ "grid-column-start", "grid-gap", "grid-row", "grid-row-end", "grid-row-gap",
+ "grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens",
"icon", "image-orientation", "image-rendering", "image-resolution",
"inline-box-align", "justify-content", "left", "letter-spacing",
@@ -417,7 +497,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"marker-offset", "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",
- "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline",
+ "nav-left", "nav-right", "nav-up", "object-fit", "object-position",
+ "opacity", "order", "orphans", "outline",
"outline-color", "outline-offset", "outline-style", "outline-width",
"overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
"padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
@@ -428,8 +509,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"region-break-before", "region-break-inside", "region-fragment",
"rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
"right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
- "ruby-position", "ruby-span", "shape-inside", "shape-outside", "size",
- "speak", "speak-as", "speak-header",
+ "ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin",
+ "shape-outside", "size", "speak", "speak-as", "speak-header",
"speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
"tab-size", "table-layout", "target", "target-name", "target-new",
"target-position", "text-align", "text-align-last", "text-decoration",
@@ -444,19 +525,37 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"vertical-align", "visibility", "voice-balance", "voice-duration",
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
"voice-volume", "volume", "white-space", "widows", "width", "word-break",
- "word-spacing", "word-wrap", "z-index", "zoom",
+ "word-spacing", "word-wrap", "z-index",
// SVG-specific
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
- "color-interpolation", "color-interpolation-filters", "color-profile",
+ "color-interpolation", "color-interpolation-filters",
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
"marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
- "glyph-orientation-vertical", "kerning", "text-anchor", "writing-mode"
+ "glyph-orientation-vertical", "text-anchor", "writing-mode"
], propertyKeywords = keySet(propertyKeywords_);
+ var nonStandardPropertyKeywords_ = [
+ "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
+ "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
+ "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
+ "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
+ "searchfield-results-decoration", "zoom"
+ ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
+
+ var fontProperties_ = [
+ "font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
+ "font-stretch", "font-weight", "font-style"
+ ], fontProperties = keySet(fontProperties_);
+
+ var counterDescriptors_ = [
+ "additive-symbols", "fallback", "negative", "pad", "prefix", "range",
+ "speak-as", "suffix", "symbols", "system"
+ ], counterDescriptors = keySet(counterDescriptors_);
+
var colorKeywords_ = [
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
@@ -479,54 +578,58 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
- "purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon",
- "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
+ "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
+ "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen"
], colorKeywords = keySet(colorKeywords_);
var valueKeywords_ = [
- "above", "absolute", "activeborder", "activecaption", "afar",
- "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate",
+ "above", "absolute", "activeborder", "additive", "activecaption", "afar",
+ "after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
- "arabic-indic", "armenian", "asterisks", "auto", "avoid", "avoid-column", "avoid-page",
+ "arabic-indic", "armenian", "asterisks", "attr", "auto", "avoid", "avoid-column", "avoid-page",
"avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
"bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
- "both", "bottom", "break", "break-all", "break-word", "button", "button-bevel",
- "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian",
+ "both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
+ "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
- "cell", "center", "checkbox", "circle", "cjk-earthly-branch",
+ "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
- "col-resize", "collapse", "column", "compact", "condensed", "contain", "content",
- "content-box", "context-menu", "continuous", "copy", "cover", "crop",
- "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal",
- "decimal-leading-zero", "default", "default-button", "destination-atop",
- "destination-in", "destination-out", "destination-over", "devanagari",
- "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted",
- "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
+ "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
+ "compact", "condensed", "contain", "content",
+ "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
+ "cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
+ "decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
+ "destination-in", "destination-out", "destination-over", "devanagari", "difference",
+ "disc", "discard", "disclosure-closed", "disclosure-open", "document",
+ "dot-dash", "dot-dot-dash",
+ "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
- "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et",
- "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed",
- "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes",
- "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
- "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
+ "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
+ "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
+ "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
+ "forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove",
+ "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
- "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
+ "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
- "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert",
- "italic", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer",
- "landscape", "lao", "large", "larger", "left", "level", "lighter",
- "line-through", "linear", "lines", "list-item", "listbox", "listitem",
+ "inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
+ "italic", "japanese-formal", "japanese-informal", "justify", "kannada",
+ "katakana", "katakana-iroha", "keep-all", "khmer",
+ "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
+ "landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
+ "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
- "lower-roman", "lowercase", "ltr", "malayalam", "match",
+ "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
"media-controls-background", "media-current-time-display",
"media-fullscreen-button", "media-mute-button", "media-play-button",
"media-return-to-realtime-button", "media-rewind-button",
@@ -535,48 +638,53 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"media-volume-slider-container", "media-volume-sliderthumb", "medium",
"menu", "menulist", "menulist-button", "menulist-text",
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
- "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
+ "mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
- "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
+ "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
- "painted", "page", "paused", "persian", "plus-darker", "plus-lighter", "pointer",
- "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button",
- "radio", "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region",
- "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba",
- "ridge", "right", "round", "row-resize", "rtl", "run-in", "running",
- "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield",
+ "painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
+ "pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
+ "progress", "push-button", "radial-gradient", "radio", "read-only",
+ "read-write", "read-write-plaintext-only", "rectangle", "region",
+ "relative", "repeat", "repeating-linear-gradient",
+ "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
+ "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
+ "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
+ "s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
+ "scroll", "scrollbar", "se-resize", "searchfield",
"searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration",
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
- "single", "skip-white-space", "slide", "slider-horizontal",
+ "simp-chinese-formal", "simp-chinese-informal", "single",
+ "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
- "small", "small-caps", "small-caption", "smaller", "solid", "somali",
- "source-atop", "source-in", "source-out", "source-over", "space", "square",
- "square-button", "start", "static", "status-bar", "stretch", "stroke",
- "sub", "subpixel-antialiased", "super", "sw-resize", "table",
+ "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
+ "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "spell-out", "square",
+ "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
+ "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table",
"table-caption", "table-cell", "table-column", "table-column-group",
"table-footer-group", "table-header-group", "table-row", "table-row-group",
+ "tamil",
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
+ "trad-chinese-formal", "trad-chinese-informal",
+ "translate", "translate3d", "translateX", "translateY", "translateZ",
"transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
- "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
+ "var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
- "window", "windowframe", "windowtext", "x-large", "x-small", "xor",
+ "window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
"xx-large", "xx-small"
], valueKeywords = keySet(valueKeywords_);
- var fontProperties_ = [
- "font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
- "font-stretch", "font-weight", "font-style"
- ], fontProperties = keySet(fontProperties_);
-
- var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_).concat(colorKeywords_).concat(valueKeywords_);
+ var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
+ .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
+ .concat(valueKeywords_);
CodeMirror.registerHelper("hintWords", "css", allWords);
function tokenCComment(stream, state) {
@@ -591,29 +699,18 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return ["comment", "comment"];
}
- function tokenSGMLComment(stream, state) {
- if (stream.skipTo("-->")) {
- stream.match("-->");
- state.tokenize = null;
- } else {
- stream.skipToEnd();
- }
- return ["comment", "comment"];
- }
-
CodeMirror.defineMIME("text/css", {
+ documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
+ mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
+ nonStandardPropertyKeywords: nonStandardPropertyKeywords,
+ fontProperties: fontProperties,
+ counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
- fontProperties: fontProperties,
tokenHooks: {
- "<": function(stream, state) {
- if (!stream.match("!--")) return false;
- state.tokenize = tokenSGMLComment;
- return tokenSGMLComment(stream, state);
- },
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
@@ -626,7 +723,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
CodeMirror.defineMIME("text/x-scss", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
+ mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
+ nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
@@ -644,7 +743,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
}
},
":": function(stream) {
- if (stream.match(/\s*{/))
+ if (stream.match(/\s*\{/))
return [null, "{"];
return false;
},
@@ -666,7 +765,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
CodeMirror.defineMIME("text/x-less", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
+ mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
+ nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
@@ -684,6 +785,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
}
},
"@": function(stream) {
+ if (stream.eat("{")) return [null, "interpolation"];
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
@@ -698,4 +800,26 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
helperType: "less"
});
+ CodeMirror.defineMIME("text/x-gss", {
+ documentTypes: documentTypes,
+ mediaTypes: mediaTypes,
+ mediaFeatures: mediaFeatures,
+ propertyKeywords: propertyKeywords,
+ nonStandardPropertyKeywords: nonStandardPropertyKeywords,
+ fontProperties: fontProperties,
+ counterDescriptors: counterDescriptors,
+ colorKeywords: colorKeywords,
+ valueKeywords: valueKeywords,
+ supportsAtComponent: true,
+ tokenHooks: {
+ "/": function(stream, state) {
+ if (!stream.eat("*")) return false;
+ state.tokenize = tokenCComment;
+ return tokenCComment(stream, state);
+ }
+ },
+ name: "css",
+ helperType: "gss"
+ });
+
});
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/gss.html b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/gss.html
new file mode 100644
index 0000000000..232fe8c12b
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/gss.html
@@ -0,0 +1,103 @@
+
+
+CodeMirror: Closure Stylesheets (GSS) mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+Closure Stylesheets (GSS) mode
+
+
+
+ A mode for Closure Stylesheets (GSS).
+ MIME type defined: text/x-gss
.
+
+ Parsing/Highlighting Tests: normal , verbose .
+
+
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/gss_test.js b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/gss_test.js
new file mode 100644
index 0000000000..d56e592803
--- /dev/null
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/gss_test.js
@@ -0,0 +1,17 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function() {
+ "use strict";
+
+ var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-gss");
+ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "gss"); }
+
+ MT("atComponent",
+ "[def @component] {",
+ "[tag foo] {",
+ " [property color]: [keyword black];",
+ "}",
+ "}");
+
+})();
diff --git a/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/index.html b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/index.html
index 80ef518e90..2d2b9b073c 100644
--- a/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/index.html
+++ b/wcfsetup/install/files/js/3rdParty/codemirror/mode/css/index.html
@@ -5,16 +5,19 @@
+
+
+